prune old sites
This commit is contained in:
@@ -26,4 +26,8 @@ export class LlmApiService {
|
||||
deleteVersion(versionId: string): Observable<void> {
|
||||
return this.http.delete<void>(`/api/llm/versions/${versionId}`);
|
||||
}
|
||||
|
||||
pruneVersions(siteId: string, keep: number = 10): Observable<{deleted: number}> {
|
||||
return this.http.post<{deleted: number}>(`/api/llm/versions/${siteId}/prune`, { keep });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +91,48 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
|
||||
Generate
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="configuredProviders.length === 0 && !(session.generating$ | async)" class="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p class="text-sm text-yellow-800">
|
||||
No API keys configured. Go to <a class="underline font-medium cursor-pointer" (click)="goToSettings()">Settings</a> to add an API key for an AI provider.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card *ngIf="configuredProviders.length === 0 && !(session.generating$ | async)" class="p-4" appearance="outlined">
|
||||
<mat-card-content class="!p-0">
|
||||
<h4 class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-4">Configure AI Provider</h4>
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
Add an API key to enable AI site generation. Keys are encrypted at rest.
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<mat-form-field appearance="outline" class="w-full">
|
||||
<mat-label>Provider</mat-label>
|
||||
<mat-select [(ngModel)]="setupProvider">
|
||||
<mat-option value="gemini">Gemini</mat-option>
|
||||
<mat-option value="openai">OpenAI / Compatible</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="w-full">
|
||||
<mat-label>API Key</mat-label>
|
||||
<input matInput [type]="setupKeyVisible ? 'text' : 'password'" [(ngModel)]="setupApiKey" />
|
||||
<button mat-icon-button matSuffix (click)="setupKeyVisible = !setupKeyVisible" type="button"
|
||||
[attr.aria-label]="setupKeyVisible ? 'Hide key' : 'Show key'">
|
||||
<mat-icon>{{ setupKeyVisible ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="w-full" *ngIf="setupProvider === 'openai'">
|
||||
<mat-label>Base URL (optional)</mat-label>
|
||||
<input matInput placeholder="https://api.deepseek.com/v1" [(ngModel)]="setupBaseUrl" />
|
||||
<mat-hint>For OpenAI-compatible providers like DeepSeek</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="w-full">
|
||||
<mat-label>Model (optional)</mat-label>
|
||||
<input matInput placeholder="{{ setupProvider === 'gemini' ? 'gemini-2.0-flash-exp' : 'gpt-4o-mini' }}" [(ngModel)]="setupModel" />
|
||||
<mat-hint>Override the default model name</mat-hint>
|
||||
</mat-form-field>
|
||||
<div class="flex justify-end">
|
||||
<button mat-flat-button color="primary" [disabled]="!setupApiKey.trim() || setupSaving" (click)="saveSetupKey()">
|
||||
<mat-icon>save</mat-icon>
|
||||
{{ setupSaving ? 'Saving...' : 'Save' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
@@ -123,7 +161,19 @@ 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">{{ (session.versions$ | async)?.length || 0 }} version{{ ((session.versions$ | async)?.length || 0) !== 1 ? 's' : '' }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
*ngIf="((session.versions$ | async)?.length || 0) > 10"
|
||||
mat-stroked-button
|
||||
class="!text-[10px] !leading-none !min-w-0 !px-2 !py-0.5 !h-auto"
|
||||
color="warn"
|
||||
(click)="pruneOldVersions()"
|
||||
title="Keep only the 10 most recent versions"
|
||||
>
|
||||
Clear old
|
||||
</button>
|
||||
<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">
|
||||
<mat-spinner diameter="20"></mat-spinner>
|
||||
@@ -275,7 +325,7 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!(session.generating$ | async) && !(session.currentResult$ | async) && !(session.error$ | async)" class="flex flex-col items-center justify-center py-16 text-gray-400">
|
||||
<div *ngIf="configuredProviders.length > 0 && !(session.generating$ | async) && !(session.currentResult$ | async) && !(session.error$ | async)" class="flex flex-col items-center justify-center py-16 text-gray-400">
|
||||
<mat-icon class="text-6xl mb-4" style="width: auto; height: auto; font-size: 4rem;">auto_awesome</mat-icon>
|
||||
<h3 class="text-xl font-medium text-gray-500 mb-2">Describe your dream site</h3>
|
||||
<p class="text-sm text-gray-400 text-center max-w-md">
|
||||
@@ -306,6 +356,14 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
||||
llmModels: Record<string, string> = {};
|
||||
configuredProviders: string[] = [];
|
||||
selectedProvider = '';
|
||||
|
||||
setupProvider = 'gemini';
|
||||
setupApiKey = '';
|
||||
setupBaseUrl = '';
|
||||
setupModel = '';
|
||||
setupSaving = false;
|
||||
setupKeyVisible = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -446,6 +504,23 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
pruneOldVersions(): void {
|
||||
if (!this.siteId) return;
|
||||
this.api.pruneVersions(this.siteId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.loadVersions();
|
||||
if (result.deleted > 0) {
|
||||
this.snackBar.open(`Cleaned up ${result.deleted} old versions`, 'Close', { duration: 3000 });
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.snackBar.open('Failed to clean up versions', 'Close', { duration: 3000 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
switchToVersion(version: GenerationVersion): void {
|
||||
if (version.id === this.session.currentVersionId) return;
|
||||
this.session.setGenerating(true);
|
||||
@@ -502,6 +577,30 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
||||
return prompt.length > maxLen ? prompt.slice(0, maxLen) + '...' : prompt;
|
||||
}
|
||||
|
||||
saveSetupKey(): void {
|
||||
const key = this.setupApiKey?.trim();
|
||||
const baseUrl = this.setupBaseUrl?.trim() || undefined;
|
||||
const model = this.setupModel?.trim() || undefined;
|
||||
if (!key) return;
|
||||
this.setupSaving = true;
|
||||
this.llmConfigService.saveKey(this.setupProvider, key, baseUrl, model)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.setupSaving = false;
|
||||
this.setupApiKey = '';
|
||||
this.setupBaseUrl = '';
|
||||
this.setupModel = '';
|
||||
this.snackBar.open(`${this.setupProvider} API key saved`, 'Close', { duration: 2000 });
|
||||
this.loadLlmConfig();
|
||||
},
|
||||
error: () => {
|
||||
this.setupSaving = false;
|
||||
this.snackBar.open('Failed to save API key', 'Close', { duration: 3000 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
|
||||
Reference in New Issue
Block a user