import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatTableModule } from '@angular/material/table';
import { MatChipsModule } from '@angular/material/chips';
import { MatDividerModule } from '@angular/material/divider';
import { ApiService } from '../../services/api.service';
import { MatSnackBar } from '@angular/material/snack-bar';
interface SparkResponse {
type: string;
timestamp: Date;
request?: any;
response?: any;
error?: string;
}
interface SparkProject {
project_name: string;
version: number;
enabled: boolean;
status: string;
last_accessed: string;
base_model: string;
has_adapter: boolean;
}
@Component({
selector: 'app-spark',
standalone: true,
imports: [
CommonModule,
FormsModule,
MatCardModule,
MatFormFieldModule,
MatSelectModule,
MatButtonModule,
MatIconModule,
MatProgressSpinnerModule,
MatExpansionModule,
MatTableModule,
MatChipsModule,
MatDividerModule
],
template: `
flash_on
Spark Integration
Manage Spark LLM service integration
Select Project
{{ project.name }} {{ project.caption ? '- ' + project.caption : '' }}
folder
@if (loading) {
}
@if (responses.length > 0) {
Response History
@for (response of responses; track response.timestamp) {
{{ response.type }}
{{ response.timestamp | date:'HH:mm:ss' }}
@if (response.request) {
Request:
{{ response.request | json }}
}
@if (response.response) {
Response:
@if (response.type === 'Get Project Status' && response.response.projects) {
Project |
{{ project.project_name }} |
Version |
v{{ project.version }} |
Status |
{{ project.status }}
|
Enabled |
{{ project.enabled ? 'check_circle' : 'cancel' }}
|
Base Model |
{{ project.base_model }}
|
Last Accessed |
{{ project.last_accessed }} |
} @else {
{{ response.response | json }}
}
}
@if (response.error) {
Error:
{{ response.error }}
}
}
}
`,
styles: [`
.spark-container {
max-width: 1200px;
margin: 0 auto;
}
mat-card-header {
margin-bottom: 24px;
mat-card-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 24px;
mat-icon {
font-size: 28px;
width: 28px;
height: 28px;
}
}
}
.project-select {
width: 100%;
max-width: 400px;
margin-bottom: 24px;
}
.action-buttons {
display: flex;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 24px;
button {
display: flex;
align-items: center;
gap: 8px;
}
}
.loading-indicator {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 32px;
p {
color: #666;
font-size: 14px;
}
}
.section-divider {
margin: 32px 0;
}
.response-list {
margin-top: 16px;
mat-expansion-panel {
margin-bottom: 16px;
}
mat-panel-title {
display: flex;
align-items: center;
gap: 12px;
.timestamp {
margin-left: auto;
color: #666;
font-size: 14px;
}
}
}
.response-section {
margin: 16px 0;
h4 {
margin-bottom: 8px;
color: #666;
}
&.error {
h4 {
color: #f44336;
}
}
}
.json-display {
background-color: #f5f5f5;
padding: 16px;
border-radius: 4px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
&.error-text {
background-color: #ffebee;
color: #c62828;
}
}
.projects-table {
width: 100%;
background: #fafafa;
.model-cell {
font-size: 12px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
mat-chip {
font-size: 12px;
min-height: 24px;
padding: 4px 12px;
&.success-chip {
background-color: #4caf50;
color: white;
}
&.error-chip {
background-color: #f44336;
color: white;
}
}
::ng-deep {
.mat-mdc-progress-spinner {
--mdc-circular-progress-active-indicator-color: #3f51b5;
}
}
`]
})
export class SparkComponent implements OnInit {
projects: any[] = [];
selectedProject: string = '';
loading = false;
responses: SparkResponse[] = [];
displayedColumns: string[] = ['project_name', 'version', 'status', 'enabled', 'base_model', 'last_accessed'];
constructor(
private apiService: ApiService,
private snackBar: MatSnackBar
) {}
ngOnInit() {
this.loadProjects();
}
loadProjects() {
this.apiService.getProjects().subscribe({
next: (projects) => {
this.projects = projects.filter((p: any) => p.enabled && !p.deleted);
},
error: (err) => {
this.snackBar.open('Failed to load projects', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
}
});
}
onProjectChange() {
// Clear previous responses when project changes
this.responses = [];
}
private addResponse(type: string, request?: any, response?: any, error?: string) {
this.responses.unshift({
type,
timestamp: new Date(),
request,
response,
error
});
// Keep only last 10 responses
if (this.responses.length > 10) {
this.responses.pop();
}
}
projectStartup() {
if (!this.selectedProject) return;
this.loading = true;
const request = { project_name: this.selectedProject };
this.apiService.sparkStartup(this.selectedProject).subscribe({
next: (response) => {
this.addResponse('Project Startup', request, response);
this.snackBar.open(response.message || 'Startup initiated', 'Close', {
duration: 3000
});
this.loading = false;
},
error: (err) => {
this.addResponse('Project Startup', request, null, err.error?.detail || err.message);
this.snackBar.open(err.error?.detail || 'Startup failed', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
this.loading = false;
}
});
}
getProjectStatus() {
this.loading = true;
this.apiService.sparkGetProjects().subscribe({
next: (response) => {
this.addResponse('Get Project Status', null, response);
this.loading = false;
},
error: (err) => {
this.addResponse('Get Project Status', null, null, err.error?.detail || err.message);
this.snackBar.open(err.error?.detail || 'Failed to get status', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
this.loading = false;
}
});
}
enableProject() {
if (!this.selectedProject) return;
this.loading = true;
const request = { project_name: this.selectedProject };
this.apiService.sparkEnableProject(this.selectedProject).subscribe({
next: (response) => {
this.addResponse('Enable Project', request, response);
this.snackBar.open(response.message || 'Project enabled', 'Close', {
duration: 3000
});
this.loading = false;
},
error: (err) => {
this.addResponse('Enable Project', request, null, err.error?.detail || err.message);
this.snackBar.open(err.error?.detail || 'Enable failed', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
this.loading = false;
}
});
}
disableProject() {
if (!this.selectedProject) return;
this.loading = true;
const request = { project_name: this.selectedProject };
this.apiService.sparkDisableProject(this.selectedProject).subscribe({
next: (response) => {
this.addResponse('Disable Project', request, response);
this.snackBar.open(response.message || 'Project disabled', 'Close', {
duration: 3000
});
this.loading = false;
},
error: (err) => {
this.addResponse('Disable Project', request, null, err.error?.detail || err.message);
this.snackBar.open(err.error?.detail || 'Disable failed', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
this.loading = false;
}
});
}
deleteProject() {
if (!this.selectedProject) return;
if (!confirm(`Are you sure you want to delete "${this.selectedProject}" from Spark?`)) {
return;
}
this.loading = true;
const request = { project_name: this.selectedProject };
this.apiService.sparkDeleteProject(this.selectedProject).subscribe({
next: (response) => {
this.addResponse('Delete Project', request, response);
this.snackBar.open(response.message || 'Project deleted', 'Close', {
duration: 3000
});
this.loading = false;
this.selectedProject = '';
},
error: (err) => {
this.addResponse('Delete Project', request, null, err.error?.detail || err.message);
this.snackBar.open(err.error?.detail || 'Delete failed', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
this.loading = false;
}
});
}
getStatusClass(status: string): string {
switch (status) {
case 'ready':
return 'status-ready';
case 'loading':
return 'status-loading';
case 'error':
return 'status-error';
case 'unloaded':
return 'status-unloaded';
default:
return '';
}
}
trackByTimestamp(index: number, response: SparkResponse): Date {
return response.timestamp;
}
}