flare / flare-ui /src /app /components /spark /spark.component.ts
ciyidogan's picture
Upload 118 files
9f79da5 verified
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: `
<div class="spark-container">
<mat-card>
<mat-card-header>
<mat-card-title>
<mat-icon>flash_on</mat-icon>
Spark Integration
</mat-card-title>
<mat-card-subtitle>
Manage Spark LLM service integration
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-form-field appearance="outline" class="project-select">
<mat-label>Select Project</mat-label>
<mat-select [(ngModel)]="selectedProject" (selectionChange)="onProjectChange()">
<mat-option *ngFor="let project of projects" [value]="project.name">
{{ project.name }} {{ project.caption ? '- ' + project.caption : '' }}
</mat-option>
</mat-select>
<mat-icon matPrefix>folder</mat-icon>
</mat-form-field>
<div class="action-buttons">
<button mat-raised-button color="primary"
(click)="projectStartup()"
[disabled]="!selectedProject || loading">
<mat-icon>rocket_launch</mat-icon>
Project Startup
</button>
<button mat-raised-button
(click)="getProjectStatus()"
[disabled]="!selectedProject || loading">
<mat-icon>info</mat-icon>
Get Project Status
</button>
<button mat-raised-button color="accent"
(click)="enableProject()"
[disabled]="!selectedProject || loading">
<mat-icon>power</mat-icon>
Enable Project
</button>
<button mat-raised-button
(click)="disableProject()"
[disabled]="!selectedProject || loading">
<mat-icon>power_off</mat-icon>
Disable Project
</button>
<button mat-raised-button color="warn"
(click)="deleteProject()"
[disabled]="!selectedProject || loading">
<mat-icon>delete</mat-icon>
Delete Project
</button>
</div>
@if (loading) {
<div class="loading-indicator">
<mat-spinner diameter="40"></mat-spinner>
<p>Processing request...</p>
</div>
}
@if (responses.length > 0) {
<mat-divider class="section-divider"></mat-divider>
<h3>Response History</h3>
<div class="response-list">
@for (response of responses; track response.timestamp) {
<mat-expansion-panel [expanded]="$index === 0">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-chip [class]="response.error ? 'error-chip' : 'success-chip'">
{{ response.type }}
</mat-chip>
<span class="timestamp">{{ response.timestamp | date:'HH:mm:ss' }}</span>
</mat-panel-title>
</mat-expansion-panel-header>
@if (response.request) {
<div class="response-section">
<h4>Request:</h4>
<pre class="json-display">{{ response.request | json }}</pre>
</div>
}
@if (response.response) {
<div class="response-section">
<h4>Response:</h4>
@if (response.type === 'Get Project Status' && response.response.projects) {
<table mat-table [dataSource]="response.response.projects" class="projects-table">
<ng-container matColumnDef="project_name">
<th mat-header-cell *matHeaderCellDef>Project</th>
<td mat-cell *matCellDef="let project">{{ project.project_name }}</td>
</ng-container>
<ng-container matColumnDef="version">
<th mat-header-cell *matHeaderCellDef>Version</th>
<td mat-cell *matCellDef="let project">v{{ project.version }}</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>Status</th>
<td mat-cell *matCellDef="let project">
<mat-chip [class]="getStatusClass(project.status)">
{{ project.status }}
</mat-chip>
</td>
</ng-container>
<ng-container matColumnDef="enabled">
<th mat-header-cell *matHeaderCellDef>Enabled</th>
<td mat-cell *matCellDef="let project">
<mat-icon [color]="project.enabled ? 'primary' : ''">
{{ project.enabled ? 'check_circle' : 'cancel' }}
</mat-icon>
</td>
</ng-container>
<ng-container matColumnDef="base_model">
<th mat-header-cell *matHeaderCellDef>Base Model</th>
<td mat-cell *matCellDef="let project" class="model-cell">
{{ project.base_model }}
</td>
</ng-container>
<ng-container matColumnDef="last_accessed">
<th mat-header-cell *matHeaderCellDef>Last Accessed</th>
<td mat-cell *matCellDef="let project">{{ project.last_accessed }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
} @else {
<pre class="json-display">{{ response.response | json }}</pre>
}
</div>
}
@if (response.error) {
<div class="response-section error">
<h4>Error:</h4>
<pre class="json-display error-text">{{ response.error }}</pre>
</div>
}
</mat-expansion-panel>
}
</div>
}
</mat-card-content>
</mat-card>
</div>
`,
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;
}
}