optimize site generation
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.krrishg.config;
|
package com.krrishg.config;
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtException;
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import org.apache.catalina.connector.ClientAbortException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -35,6 +36,13 @@ public class GlobalExceptionHandler {
|
|||||||
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, message);
|
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ClientAbortException.class)
|
||||||
|
public void handleClientAbort(ClientAbortException ex) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Client disconnected: {}", ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ProblemDetail handleGeneral(Exception ex) {
|
public ProblemDetail handleGeneral(Exception ex) {
|
||||||
log.error("Unhandled exception", ex);
|
log.error("Unhandled exception", ex);
|
||||||
|
|||||||
93
frontend/src/app/llm-workspace/generation.service.ts
Normal file
93
frontend/src/app/llm-workspace/generation.service.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
|
||||||
|
import { LlmApiService } from '../core/services/llm-api.service';
|
||||||
|
import { LlmSessionService } from './llm-session.service';
|
||||||
|
import { LLMGenerateRequest, LLMRefineRequest, SiteStructure } from '../core/models/llm';
|
||||||
|
|
||||||
|
export interface GenerationState {
|
||||||
|
status: 'idle' | 'generating' | 'completed' | 'error';
|
||||||
|
result: SiteStructure | null;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class GenerationService {
|
||||||
|
private api = inject(LlmApiService);
|
||||||
|
private session = inject(LlmSessionService);
|
||||||
|
private snackBar = inject(MatSnackBar);
|
||||||
|
|
||||||
|
private generationSubscription: Subscription | null = null;
|
||||||
|
private stateSubject = new BehaviorSubject<GenerationState>({
|
||||||
|
status: 'idle', result: null, error: null,
|
||||||
|
});
|
||||||
|
private refreshVersionsSubject = new Subject<string>();
|
||||||
|
|
||||||
|
readonly state$ = this.stateSubject.asObservable();
|
||||||
|
readonly refreshVersions$ = this.refreshVersionsSubject.asObservable();
|
||||||
|
|
||||||
|
generate(request: LLMGenerateRequest, siteId: string): void {
|
||||||
|
this.cancel();
|
||||||
|
this.stateSubject.next({ status: 'generating', result: null, error: null });
|
||||||
|
this.session.setGenerating(true);
|
||||||
|
this.session.clearError();
|
||||||
|
|
||||||
|
this.generationSubscription = this.api.generate(request).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.stateSubject.next({ status: 'completed', result: data, error: null });
|
||||||
|
this.session.setResult(data);
|
||||||
|
this.session.setCurrentVersionId(null);
|
||||||
|
this.refreshVersionsSubject.next(siteId);
|
||||||
|
this.snackBar.open('Site generated successfully!', 'Close', { duration: 5000 });
|
||||||
|
this.generationSubscription = null;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
const msg = err.error?.error || err.statusText || 'Failed to connect to server';
|
||||||
|
this.stateSubject.next({ status: 'error', result: null, error: msg });
|
||||||
|
this.session.setError(msg);
|
||||||
|
this.snackBar.open('Generation failed: ' + msg, 'Dismiss', { duration: 8000 });
|
||||||
|
this.generationSubscription = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refine(request: LLMRefineRequest, siteId: string): void {
|
||||||
|
this.cancel();
|
||||||
|
this.stateSubject.next({ status: 'generating', result: null, error: null });
|
||||||
|
this.session.setGenerating(true);
|
||||||
|
this.session.clearError();
|
||||||
|
|
||||||
|
this.generationSubscription = this.api.refine(request).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.stateSubject.next({ status: 'completed', result: data, error: null });
|
||||||
|
this.session.setResult(data);
|
||||||
|
this.session.setCurrentVersionId(null);
|
||||||
|
this.refreshVersionsSubject.next(siteId);
|
||||||
|
this.snackBar.open('Site refined successfully!', 'Close', { duration: 5000 });
|
||||||
|
this.generationSubscription = null;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
const msg = err.error?.error || err.statusText || 'Failed to connect to server';
|
||||||
|
this.stateSubject.next({ status: 'error', result: null, error: msg });
|
||||||
|
this.session.setError(msg);
|
||||||
|
this.snackBar.open('Refinement failed: ' + msg, 'Dismiss', { duration: 8000 });
|
||||||
|
this.generationSubscription = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(): void {
|
||||||
|
if (this.generationSubscription) {
|
||||||
|
this.generationSubscription.unsubscribe();
|
||||||
|
this.generationSubscription = null;
|
||||||
|
}
|
||||||
|
this.stateSubject.next({ status: 'idle', result: null, error: null });
|
||||||
|
this.session.setGenerating(false);
|
||||||
|
this.session.clearError();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetState(): void {
|
||||||
|
this.cancel();
|
||||||
|
this.stateSubject.next({ status: 'idle', result: null, error: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { takeUntil } from 'rxjs/operators';
|
|||||||
import { LlmApiService } from '../core/services/llm-api.service';
|
import { LlmApiService } from '../core/services/llm-api.service';
|
||||||
import { LlmConfigService } from '../core/services/llm-config.service';
|
import { LlmConfigService } from '../core/services/llm-config.service';
|
||||||
import { LlmSessionService } from './llm-session.service';
|
import { LlmSessionService } from './llm-session.service';
|
||||||
|
import { GenerationService } from './generation.service';
|
||||||
import { SiteStructure, GenerationVersion, LLMKeyStatus } from '../core/models/llm';
|
import { SiteStructure, GenerationVersion, LLMKeyStatus } from '../core/models/llm';
|
||||||
import { PreviewDialogComponent } from './preview-dialog/preview-dialog.component';
|
import { PreviewDialogComponent } from './preview-dialog/preview-dialog.component';
|
||||||
import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dialog';
|
import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dialog';
|
||||||
@@ -122,14 +123,14 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
|
|||||||
<mat-card-content class="!p-0">
|
<mat-card-content class="!p-0">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h4 class="text-sm font-semibold text-gray-500 uppercase tracking-wide">History</h4>
|
<h4 class="text-sm font-semibold text-gray-500 uppercase tracking-wide">History</h4>
|
||||||
<span class="text-xs text-gray-400">{{ versions.length }} version{{ versions.length !== 1 ? 's' : '' }}</span>
|
<span class="text-xs text-gray-400">{{ (session.versions$ | async)?.length || 0 }} version{{ ((session.versions$ | async)?.length || 0) !== 1 ? 's' : '' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="session.loadingVersions$ | async" class="flex justify-center py-4">
|
<div *ngIf="session.loadingVersions$ | async" class="flex justify-center py-4">
|
||||||
<mat-spinner diameter="20"></mat-spinner>
|
<mat-spinner diameter="20"></mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1 max-h-[calc(100vh-20rem)] overflow-y-auto overflow-x-hidden">
|
<div class="space-y-1 max-h-[calc(100vh-20rem)] overflow-y-auto overflow-x-hidden">
|
||||||
<div
|
<div
|
||||||
*ngFor="let v of versions"
|
*ngFor="let v of (session.versions$ | async) || []"
|
||||||
(click)="switchToVersion(v)"
|
(click)="switchToVersion(v)"
|
||||||
class="w-full p-3 rounded-lg border transition-colors duration-150 flex flex-col gap-0.5 cursor-pointer group min-w-0"
|
class="w-full p-3 rounded-lg border transition-colors duration-150 flex flex-col gap-0.5 cursor-pointer group min-w-0"
|
||||||
[class.border-blue-500]="isCurrentVersion(v.id)"
|
[class.border-blue-500]="isCurrentVersion(v.id)"
|
||||||
@@ -151,7 +152,7 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
|
|||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-600 truncate">{{ truncatePrompt(v.prompt, 50) }}</p>
|
<p class="text-xs text-gray-600 truncate">{{ truncatePrompt(v.prompt, 50) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<p *ngIf="versions.length === 0 && !(session.loadingVersions$ | async)" class="text-xs text-gray-400 py-2 text-center">
|
<p *ngIf="((session.versions$ | async) || []).length === 0 && !(session.loadingVersions$ | async)" class="text-xs text-gray-400 py-2 text-center">
|
||||||
No versions yet
|
No versions yet
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -293,13 +294,13 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
|||||||
private llmConfigService = inject(LlmConfigService);
|
private llmConfigService = inject(LlmConfigService);
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
|
private generationService = inject(GenerationService);
|
||||||
session = inject(LlmSessionService);
|
session = inject(LlmSessionService);
|
||||||
|
|
||||||
prompt = '';
|
prompt = '';
|
||||||
refinePrompt = '';
|
refinePrompt = '';
|
||||||
showRefine = false;
|
showRefine = false;
|
||||||
siteId: string | null = null;
|
siteId: string | null = null;
|
||||||
versions: GenerationVersion[] = [];
|
|
||||||
llmKeyStatus: LLMKeyStatus = {};
|
llmKeyStatus: LLMKeyStatus = {};
|
||||||
llmBaseUrls: Record<string, string> = {};
|
llmBaseUrls: Record<string, string> = {};
|
||||||
llmModels: Record<string, string> = {};
|
llmModels: Record<string, string> = {};
|
||||||
@@ -308,6 +309,10 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
|||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.generationService.refreshVersions$
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(() => this.loadVersions());
|
||||||
|
|
||||||
this.route.paramMap
|
this.route.paramMap
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe(params => {
|
.subscribe(params => {
|
||||||
@@ -317,7 +322,6 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
|||||||
this.prompt = '';
|
this.prompt = '';
|
||||||
this.refinePrompt = '';
|
this.refinePrompt = '';
|
||||||
this.showRefine = false;
|
this.showRefine = false;
|
||||||
this.versions = [];
|
|
||||||
this.session.clearHistory();
|
this.session.clearHistory();
|
||||||
this.loadVersions();
|
this.loadVersions();
|
||||||
this.loadLlmConfig();
|
this.loadLlmConfig();
|
||||||
@@ -357,7 +361,6 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
|||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (v) => {
|
next: (v) => {
|
||||||
this.versions = v;
|
|
||||||
this.session.setVersions(v);
|
this.session.setVersions(v);
|
||||||
if (v.length > 0 && !this.session.currentResult) {
|
if (v.length > 0 && !this.session.currentResult) {
|
||||||
this.api.restoreVersion(v[0].id)
|
this.api.restoreVersion(v[0].id)
|
||||||
@@ -384,68 +387,39 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
|||||||
if (!this.prompt.trim() || !this.siteId || !this.selectedProvider) return;
|
if (!this.prompt.trim() || !this.siteId || !this.selectedProvider) return;
|
||||||
this.showRefine = false;
|
this.showRefine = false;
|
||||||
this.session.setPrompt(this.prompt);
|
this.session.setPrompt(this.prompt);
|
||||||
this.session.setGenerating(true);
|
|
||||||
this.session.clearError();
|
|
||||||
|
|
||||||
const baseUrl = this.llmBaseUrls[this.selectedProvider];
|
const baseUrl = this.llmBaseUrls[this.selectedProvider];
|
||||||
const model = this.llmModels[this.selectedProvider];
|
const model = this.llmModels[this.selectedProvider];
|
||||||
this.api.generate({
|
this.generationService.generate({
|
||||||
prompt: this.prompt,
|
prompt: this.prompt,
|
||||||
siteId: this.siteId,
|
siteId: this.siteId,
|
||||||
provider: this.selectedProvider,
|
provider: this.selectedProvider,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
model,
|
model,
|
||||||
})
|
}, this.siteId);
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe({
|
|
||||||
next: (data) => {
|
|
||||||
this.session.setResult(data);
|
|
||||||
this.session.setCurrentVersionId(null);
|
|
||||||
this.loadVersions();
|
|
||||||
this.snackBar.open('Site generated successfully!', 'Close', { duration: 3000 });
|
|
||||||
},
|
|
||||||
error: (err) => {
|
|
||||||
const msg = err.error?.error || err.statusText || 'Failed to connect to server';
|
|
||||||
this.session.setError(msg);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refine(): void {
|
refine(): void {
|
||||||
const result = this.session.currentResult;
|
const result = this.session.currentResult;
|
||||||
if (!result || !this.refinePrompt.trim() || !this.siteId || !this.selectedProvider) return;
|
if (!result || !this.refinePrompt.trim() || !this.siteId || !this.selectedProvider) return;
|
||||||
this.session.setGenerating(true);
|
|
||||||
this.session.clearError();
|
|
||||||
|
|
||||||
const baseUrl = this.llmBaseUrls[this.selectedProvider];
|
const baseUrl = this.llmBaseUrls[this.selectedProvider];
|
||||||
const model = this.llmModels[this.selectedProvider];
|
const model = this.llmModels[this.selectedProvider];
|
||||||
this.api.refine({
|
this.generationService.refine({
|
||||||
prompt: this.refinePrompt,
|
prompt: this.refinePrompt,
|
||||||
siteId: this.siteId,
|
siteId: this.siteId,
|
||||||
provider: this.selectedProvider,
|
provider: this.selectedProvider,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
model,
|
model,
|
||||||
currentStructure: result,
|
currentStructure: result,
|
||||||
})
|
}, this.siteId);
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe({
|
this.refinePrompt = '';
|
||||||
next: (data) => {
|
this.showRefine = false;
|
||||||
this.session.setResult(data);
|
|
||||||
this.session.setCurrentVersionId(null);
|
|
||||||
this.loadVersions();
|
|
||||||
this.refinePrompt = '';
|
|
||||||
this.showRefine = false;
|
|
||||||
this.snackBar.open('Site refined successfully!', 'Close', { duration: 3000 });
|
|
||||||
},
|
|
||||||
error: (err) => {
|
|
||||||
const msg = err.error?.error || err.statusText || 'Failed to connect to server';
|
|
||||||
this.session.setError(msg);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteVersion(version: GenerationVersion): void {
|
deleteVersion(version: GenerationVersion): void {
|
||||||
if (this.versions.length <= 1) {
|
if (this.session.versions.length <= 1) {
|
||||||
this.snackBar.open('Cannot delete the last version', 'Close', { duration: 3000 });
|
this.snackBar.open('Cannot delete the last version', 'Close', { duration: 3000 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -520,7 +494,7 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get currentVersionPrompt(): string | null {
|
get currentVersionPrompt(): string | null {
|
||||||
const version = this.versions.find(v => v.id === this.session.currentVersionId);
|
const version = this.session.versions.find(v => v.id === this.session.currentVersionId);
|
||||||
return version ? version.prompt : null;
|
return version ? version.prompt : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user