ciyidogan commited on
Commit
a19d796
·
verified ·
1 Parent(s): e213720

Upload 2 files

Browse files
flare-ui/src/app/components/spark/spark.component.scss ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .status-ready {
2
+ background-color: #4caf50;
3
+ color: white;
4
+ }
5
+
6
+ .status-loading {
7
+ background-color: #ff9800;
8
+ color: white;
9
+ }
10
+
11
+ .status-error {
12
+ background-color: #f44336;
13
+ color: white;
14
+ }
15
+
16
+ .status-unloaded {
17
+ background-color: #9e9e9e;
18
+ color: white;
19
+ }
flare-ui/src/app/components/spark/spark.component.ts ADDED
@@ -0,0 +1,546 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { MatCardModule } from '@angular/material/card';
5
+ import { MatFormFieldModule } from '@angular/material/form-field';
6
+ import { MatSelectModule } from '@angular/material/select';
7
+ import { MatButtonModule } from '@angular/material/button';
8
+ import { MatIconModule } from '@angular/material/icon';
9
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
10
+ import { MatExpansionModule } from '@angular/material/expansion';
11
+ import { MatTableModule } from '@angular/material/table';
12
+ import { MatChipsModule } from '@angular/material/chips';
13
+ import { MatDividerModule } from '@angular/material/divider';
14
+ import { ApiService } from '../../services/api.service';
15
+ import { MatSnackBar } from '@angular/material/snack-bar';
16
+
17
+ interface SparkResponse {
18
+ type: string;
19
+ timestamp: Date;
20
+ request?: any;
21
+ response?: any;
22
+ error?: string;
23
+ }
24
+
25
+ interface SparkProject {
26
+ project_name: string;
27
+ version: number;
28
+ enabled: boolean;
29
+ status: string;
30
+ last_accessed: string;
31
+ base_model: string;
32
+ has_adapter: boolean;
33
+ }
34
+
35
+ @Component({
36
+ selector: 'app-spark',
37
+ standalone: true,
38
+ imports: [
39
+ CommonModule,
40
+ FormsModule,
41
+ MatCardModule,
42
+ MatFormFieldModule,
43
+ MatSelectModule,
44
+ MatButtonModule,
45
+ MatIconModule,
46
+ MatProgressSpinnerModule,
47
+ MatExpansionModule,
48
+ MatTableModule,
49
+ MatChipsModule,
50
+ MatDividerModule
51
+ ],
52
+ template: `
53
+ <div class="spark-container">
54
+ <mat-card>
55
+ <mat-card-header>
56
+ <mat-card-title>
57
+ <mat-icon>flash_on</mat-icon>
58
+ Spark Integration
59
+ </mat-card-title>
60
+ <mat-card-subtitle>
61
+ Manage Spark LLM service integration
62
+ </mat-card-subtitle>
63
+ </mat-card-header>
64
+
65
+ <mat-card-content>
66
+ <mat-form-field appearance="outline" class="project-select">
67
+ <mat-label>Select Project</mat-label>
68
+ <mat-select [(ngModel)]="selectedProject" (selectionChange)="onProjectChange()">
69
+ <mat-option *ngFor="let project of projects" [value]="project.name">
70
+ {{ project.name }} {{ project.caption ? '- ' + project.caption : '' }}
71
+ </mat-option>
72
+ </mat-select>
73
+ <mat-icon matPrefix>folder</mat-icon>
74
+ </mat-form-field>
75
+
76
+ <div class="action-buttons">
77
+ <button mat-raised-button color="primary"
78
+ (click)="projectStartup()"
79
+ [disabled]="!selectedProject || loading">
80
+ <mat-icon>rocket_launch</mat-icon>
81
+ Project Startup
82
+ </button>
83
+
84
+ <button mat-raised-button
85
+ (click)="getProjectStatus()"
86
+ [disabled]="loading">
87
+ <mat-icon>info</mat-icon>
88
+ Get Project Status
89
+ </button>
90
+
91
+ <button mat-raised-button color="accent"
92
+ (click)="enableProject()"
93
+ [disabled]="!selectedProject || loading">
94
+ <mat-icon>power</mat-icon>
95
+ Enable Project
96
+ </button>
97
+
98
+ <button mat-raised-button
99
+ (click)="disableProject()"
100
+ [disabled]="!selectedProject || loading">
101
+ <mat-icon>power_off</mat-icon>
102
+ Disable Project
103
+ </button>
104
+
105
+ <button mat-raised-button color="warn"
106
+ (click)="deleteProject()"
107
+ [disabled]="!selectedProject || loading">
108
+ <mat-icon>delete</mat-icon>
109
+ Delete Project
110
+ </button>
111
+ </div>
112
+
113
+ @if (loading) {
114
+ <div class="loading-indicator">
115
+ <mat-spinner diameter="40"></mat-spinner>
116
+ <p>Processing request...</p>
117
+ </div>
118
+ }
119
+
120
+ @if (responses.length > 0) {
121
+ <mat-divider class="section-divider"></mat-divider>
122
+
123
+ <h3>Response History</h3>
124
+
125
+ <div class="response-list">
126
+ @for (response of responses; track response.timestamp) {
127
+ <mat-expansion-panel [expanded]="$index === 0">
128
+ <mat-expansion-panel-header>
129
+ <mat-panel-title>
130
+ <mat-chip [class]="response.error ? 'error-chip' : 'success-chip'">
131
+ {{ response.type }}
132
+ </mat-chip>
133
+ <span class="timestamp">{{ response.timestamp | date:'HH:mm:ss' }}</span>
134
+ </mat-panel-title>
135
+ </mat-expansion-panel-header>
136
+
137
+ @if (response.request) {
138
+ <div class="response-section">
139
+ <h4>Request:</h4>
140
+ <pre class="json-display">{{ response.request | json }}</pre>
141
+ </div>
142
+ }
143
+
144
+ @if (response.response) {
145
+ <div class="response-section">
146
+ <h4>Response:</h4>
147
+ @if (response.type === 'Get Project Status' && response.response.projects) {
148
+ <table mat-table [dataSource]="response.response.projects" class="projects-table">
149
+ <ng-container matColumnDef="project_name">
150
+ <th mat-header-cell *matHeaderCellDef>Project</th>
151
+ <td mat-cell *matCellDef="let project">{{ project.project_name }}</td>
152
+ </ng-container>
153
+
154
+ <ng-container matColumnDef="version">
155
+ <th mat-header-cell *matHeaderCellDef>Version</th>
156
+ <td mat-cell *matCellDef="let project">v{{ project.version }}</td>
157
+ </ng-container>
158
+
159
+ <ng-container matColumnDef="status">
160
+ <th mat-header-cell *matHeaderCellDef>Status</th>
161
+ <td mat-cell *matCellDef="let project">
162
+ <mat-chip [class]="getStatusClass(project.status)">
163
+ {{ project.status }}
164
+ </mat-chip>
165
+ </td>
166
+ </ng-container>
167
+
168
+ <ng-container matColumnDef="enabled">
169
+ <th mat-header-cell *matHeaderCellDef>Enabled</th>
170
+ <td mat-cell *matCellDef="let project">
171
+ <mat-icon [color]="project.enabled ? 'primary' : ''">
172
+ {{ project.enabled ? 'check_circle' : 'cancel' }}
173
+ </mat-icon>
174
+ </td>
175
+ </ng-container>
176
+
177
+ <ng-container matColumnDef="base_model">
178
+ <th mat-header-cell *matHeaderCellDef>Base Model</th>
179
+ <td mat-cell *matCellDef="let project" class="model-cell">
180
+ {{ project.base_model }}
181
+ </td>
182
+ </ng-container>
183
+
184
+ <ng-container matColumnDef="last_accessed">
185
+ <th mat-header-cell *matHeaderCellDef>Last Accessed</th>
186
+ <td mat-cell *matCellDef="let project">{{ project.last_accessed }}</td>
187
+ </ng-container>
188
+
189
+ <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
190
+ <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
191
+ </table>
192
+ } @else {
193
+ <pre class="json-display">{{ response.response | json }}</pre>
194
+ }
195
+ </div>
196
+ }
197
+
198
+ @if (response.error) {
199
+ <div class="response-section error">
200
+ <h4>Error:</h4>
201
+ <pre class="json-display error-text">{{ response.error }}</pre>
202
+ </div>
203
+ }
204
+ </mat-expansion-panel>
205
+ }
206
+ </div>
207
+ }
208
+ </mat-card-content>
209
+ </mat-card>
210
+ </div>
211
+ `,
212
+ styles: [`
213
+ .spark-container {
214
+ max-width: 1200px;
215
+ margin: 0 auto;
216
+ }
217
+
218
+ mat-card-header {
219
+ margin-bottom: 24px;
220
+
221
+ mat-card-title {
222
+ display: flex;
223
+ align-items: center;
224
+ gap: 8px;
225
+ font-size: 24px;
226
+
227
+ mat-icon {
228
+ font-size: 28px;
229
+ width: 28px;
230
+ height: 28px;
231
+ }
232
+ }
233
+ }
234
+
235
+ .project-select {
236
+ width: 100%;
237
+ max-width: 400px;
238
+ margin-bottom: 24px;
239
+ }
240
+
241
+ .action-buttons {
242
+ display: flex;
243
+ gap: 16px;
244
+ flex-wrap: wrap;
245
+ margin-bottom: 24px;
246
+
247
+ button {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 8px;
251
+ }
252
+ }
253
+
254
+ .loading-indicator {
255
+ display: flex;
256
+ flex-direction: column;
257
+ align-items: center;
258
+ gap: 16px;
259
+ padding: 32px;
260
+
261
+ p {
262
+ color: #666;
263
+ font-size: 14px;
264
+ }
265
+ }
266
+
267
+ .section-divider {
268
+ margin: 32px 0;
269
+ }
270
+
271
+ .response-list {
272
+ margin-top: 16px;
273
+
274
+ mat-expansion-panel {
275
+ margin-bottom: 16px;
276
+ }
277
+
278
+ mat-panel-title {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 12px;
282
+
283
+ .timestamp {
284
+ margin-left: auto;
285
+ color: #666;
286
+ font-size: 14px;
287
+ }
288
+ }
289
+ }
290
+
291
+ .response-section {
292
+ margin: 16px 0;
293
+
294
+ h4 {
295
+ margin-bottom: 8px;
296
+ color: #666;
297
+ }
298
+
299
+ &.error {
300
+ h4 {
301
+ color: #f44336;
302
+ }
303
+ }
304
+ }
305
+
306
+ .json-display {
307
+ background-color: #f5f5f5;
308
+ padding: 16px;
309
+ border-radius: 4px;
310
+ font-family: 'Consolas', 'Monaco', monospace;
311
+ font-size: 13px;
312
+ overflow-x: auto;
313
+ white-space: pre-wrap;
314
+ word-break: break-word;
315
+
316
+ &.error-text {
317
+ background-color: #ffebee;
318
+ color: #c62828;
319
+ }
320
+ }
321
+
322
+ .projects-table {
323
+ width: 100%;
324
+ background: #fafafa;
325
+
326
+ .model-cell {
327
+ font-size: 12px;
328
+ max-width: 200px;
329
+ overflow: hidden;
330
+ text-overflow: ellipsis;
331
+ white-space: nowrap;
332
+ }
333
+ }
334
+
335
+ mat-chip {
336
+ font-size: 12px;
337
+ min-height: 24px;
338
+ padding: 4px 12px;
339
+
340
+ &.success-chip {
341
+ background-color: #4caf50;
342
+ color: white;
343
+ }
344
+
345
+ &.error-chip {
346
+ background-color: #f44336;
347
+ color: white;
348
+ }
349
+ }
350
+
351
+ ::ng-deep {
352
+ .mat-mdc-progress-spinner {
353
+ --mdc-circular-progress-active-indicator-color: #3f51b5;
354
+ }
355
+ }
356
+ `]
357
+ })
358
+ export class SparkComponent implements OnInit {
359
+ projects: any[] = [];
360
+ selectedProject: string = '';
361
+ loading = false;
362
+ responses: SparkResponse[] = [];
363
+ displayedColumns: string[] = ['project_name', 'version', 'status', 'enabled', 'base_model', 'last_accessed'];
364
+
365
+ constructor(
366
+ private apiService: ApiService,
367
+ private snackBar: MatSnackBar
368
+ ) {}
369
+
370
+ ngOnInit() {
371
+ this.loadProjects();
372
+ }
373
+
374
+ loadProjects() {
375
+ this.apiService.getProjects().subscribe({
376
+ next: (projects) => {
377
+ this.projects = projects.filter((p: any) => p.enabled && !p.deleted);
378
+ },
379
+ error: (err) => {
380
+ this.snackBar.open('Failed to load projects', 'Close', {
381
+ duration: 5000,
382
+ panelClass: 'error-snackbar'
383
+ });
384
+ }
385
+ });
386
+ }
387
+
388
+ onProjectChange() {
389
+ // Clear previous responses when project changes
390
+ this.responses = [];
391
+ }
392
+
393
+ private addResponse(type: string, request?: any, response?: any, error?: string) {
394
+ this.responses.unshift({
395
+ type,
396
+ timestamp: new Date(),
397
+ request,
398
+ response,
399
+ error
400
+ });
401
+
402
+ // Keep only last 10 responses
403
+ if (this.responses.length > 10) {
404
+ this.responses.pop();
405
+ }
406
+ }
407
+
408
+ projectStartup() {
409
+ if (!this.selectedProject) return;
410
+
411
+ this.loading = true;
412
+ const request = { project_name: this.selectedProject };
413
+
414
+ this.apiService.sparkStartup(this.selectedProject).subscribe({
415
+ next: (response) => {
416
+ this.addResponse('Project Startup', request, response);
417
+ this.snackBar.open(response.message || 'Startup initiated', 'Close', {
418
+ duration: 3000
419
+ });
420
+ this.loading = false;
421
+ },
422
+ error: (err) => {
423
+ this.addResponse('Project Startup', request, null, err.error?.detail || err.message);
424
+ this.snackBar.open(err.error?.detail || 'Startup failed', 'Close', {
425
+ duration: 5000,
426
+ panelClass: 'error-snackbar'
427
+ });
428
+ this.loading = false;
429
+ }
430
+ });
431
+ }
432
+
433
+ getProjectStatus() {
434
+ this.loading = true;
435
+
436
+ this.apiService.sparkGetProjects().subscribe({
437
+ next: (response) => {
438
+ this.addResponse('Get Project Status', null, response);
439
+ this.loading = false;
440
+ },
441
+ error: (err) => {
442
+ this.addResponse('Get Project Status', null, null, err.error?.detail || err.message);
443
+ this.snackBar.open(err.error?.detail || 'Failed to get status', 'Close', {
444
+ duration: 5000,
445
+ panelClass: 'error-snackbar'
446
+ });
447
+ this.loading = false;
448
+ }
449
+ });
450
+ }
451
+
452
+ enableProject() {
453
+ if (!this.selectedProject) return;
454
+
455
+ this.loading = true;
456
+ const request = { project_name: this.selectedProject };
457
+
458
+ this.apiService.sparkEnableProject(this.selectedProject).subscribe({
459
+ next: (response) => {
460
+ this.addResponse('Enable Project', request, response);
461
+ this.snackBar.open(response.message || 'Project enabled', 'Close', {
462
+ duration: 3000
463
+ });
464
+ this.loading = false;
465
+ },
466
+ error: (err) => {
467
+ this.addResponse('Enable Project', request, null, err.error?.detail || err.message);
468
+ this.snackBar.open(err.error?.detail || 'Enable failed', 'Close', {
469
+ duration: 5000,
470
+ panelClass: 'error-snackbar'
471
+ });
472
+ this.loading = false;
473
+ }
474
+ });
475
+ }
476
+
477
+ disableProject() {
478
+ if (!this.selectedProject) return;
479
+
480
+ this.loading = true;
481
+ const request = { project_name: this.selectedProject };
482
+
483
+ this.apiService.sparkDisableProject(this.selectedProject).subscribe({
484
+ next: (response) => {
485
+ this.addResponse('Disable Project', request, response);
486
+ this.snackBar.open(response.message || 'Project disabled', 'Close', {
487
+ duration: 3000
488
+ });
489
+ this.loading = false;
490
+ },
491
+ error: (err) => {
492
+ this.addResponse('Disable Project', request, null, err.error?.detail || err.message);
493
+ this.snackBar.open(err.error?.detail || 'Disable failed', 'Close', {
494
+ duration: 5000,
495
+ panelClass: 'error-snackbar'
496
+ });
497
+ this.loading = false;
498
+ }
499
+ });
500
+ }
501
+
502
+ deleteProject() {
503
+ if (!this.selectedProject) return;
504
+
505
+ if (!confirm(`Are you sure you want to delete "${this.selectedProject}" from Spark?`)) {
506
+ return;
507
+ }
508
+
509
+ this.loading = true;
510
+ const request = { project_name: this.selectedProject };
511
+
512
+ this.apiService.sparkDeleteProject(this.selectedProject).subscribe({
513
+ next: (response) => {
514
+ this.addResponse('Delete Project', request, response);
515
+ this.snackBar.open(response.message || 'Project deleted', 'Close', {
516
+ duration: 3000
517
+ });
518
+ this.loading = false;
519
+ this.selectedProject = '';
520
+ },
521
+ error: (err) => {
522
+ this.addResponse('Delete Project', request, null, err.error?.detail || err.message);
523
+ this.snackBar.open(err.error?.detail || 'Delete failed', 'Close', {
524
+ duration: 5000,
525
+ panelClass: 'error-snackbar'
526
+ });
527
+ this.loading = false;
528
+ }
529
+ });
530
+ }
531
+
532
+ getStatusClass(status: string): string {
533
+ switch (status) {
534
+ case 'ready':
535
+ return 'status-ready';
536
+ case 'loading':
537
+ return 'status-loading';
538
+ case 'error':
539
+ return 'status-error';
540
+ case 'unloaded':
541
+ return 'status-unloaded';
542
+ default:
543
+ return '';
544
+ }
545
+ }
546
+ }