optimize site generation

This commit is contained in:
Krrish Ghimire
2026-07-10 15:20:25 +05:45
parent a3f9cef883
commit e705f240d4
3 changed files with 119 additions and 44 deletions

View 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 });
}
}

View File

@@ -19,6 +19,7 @@ import { takeUntil } from 'rxjs/operators';
import { LlmApiService } from '../core/services/llm-api.service';
import { LlmConfigService } from '../core/services/llm-config.service';
import { LlmSessionService } from './llm-session.service';
import { GenerationService } from './generation.service';
import { SiteStructure, GenerationVersion, LLMKeyStatus } from '../core/models/llm';
import { PreviewDialogComponent } from './preview-dialog/preview-dialog.component';
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">
<div class="flex items-center justify-between mb-3">
<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 *ngIf="session.loadingVersions$ | async" class="flex justify-center py-4">
<mat-spinner diameter="20"></mat-spinner>
</div>
<div class="space-y-1 max-h-[calc(100vh-20rem)] overflow-y-auto overflow-x-hidden">
<div
*ngFor="let v of versions"
*ngFor="let v of (session.versions$ | async) || []"
(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.border-blue-500]="isCurrentVersion(v.id)"
@@ -151,7 +152,7 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
</div>
<p class="text-xs text-gray-600 truncate">{{ truncatePrompt(v.prompt, 50) }}</p>
</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
</p>
</div>
@@ -293,13 +294,13 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
private llmConfigService = inject(LlmConfigService);
private snackBar = inject(MatSnackBar);
private dialog = inject(MatDialog);
private generationService = inject(GenerationService);
session = inject(LlmSessionService);
prompt = '';
refinePrompt = '';
showRefine = false;
siteId: string | null = null;
versions: GenerationVersion[] = [];
llmKeyStatus: LLMKeyStatus = {};
llmBaseUrls: Record<string, string> = {};
llmModels: Record<string, string> = {};
@@ -308,6 +309,10 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit(): void {
this.generationService.refreshVersions$
.pipe(takeUntil(this.destroy$))
.subscribe(() => this.loadVersions());
this.route.paramMap
.pipe(takeUntil(this.destroy$))
.subscribe(params => {
@@ -317,7 +322,6 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
this.prompt = '';
this.refinePrompt = '';
this.showRefine = false;
this.versions = [];
this.session.clearHistory();
this.loadVersions();
this.loadLlmConfig();
@@ -357,7 +361,6 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (v) => {
this.versions = v;
this.session.setVersions(v);
if (v.length > 0 && !this.session.currentResult) {
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;
this.showRefine = false;
this.session.setPrompt(this.prompt);
this.session.setGenerating(true);
this.session.clearError();
const baseUrl = this.llmBaseUrls[this.selectedProvider];
const model = this.llmModels[this.selectedProvider];
this.api.generate({
this.generationService.generate({
prompt: this.prompt,
siteId: this.siteId,
provider: this.selectedProvider,
baseUrl,
model,
})
.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);
},
});
}, this.siteId);
}
refine(): void {
const result = this.session.currentResult;
if (!result || !this.refinePrompt.trim() || !this.siteId || !this.selectedProvider) return;
this.session.setGenerating(true);
this.session.clearError();
const baseUrl = this.llmBaseUrls[this.selectedProvider];
const model = this.llmModels[this.selectedProvider];
this.api.refine({
this.generationService.refine({
prompt: this.refinePrompt,
siteId: this.siteId,
provider: this.selectedProvider,
baseUrl,
model,
currentStructure: result,
})
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (data) => {
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);
},
});
}, this.siteId);
this.refinePrompt = '';
this.showRefine = false;
}
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 });
return;
}
@@ -520,7 +494,7 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
}
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;
}