add bring your own key support
This commit is contained in:
@@ -8,6 +8,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -16,8 +17,9 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { Subject } from 'rxjs';
|
||||
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 { SiteStructure, GenerationVersion } from '../core/models/llm';
|
||||
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';
|
||||
|
||||
@@ -27,10 +29,14 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
|
||||
imports: [
|
||||
NgIf, NgFor, AsyncPipe, KeyValuePipe, DatePipe, FormsModule, RouterModule,
|
||||
MatToolbarModule, MatButtonModule, MatIconModule,
|
||||
MatInputModule, MatFormFieldModule, MatProgressSpinnerModule,
|
||||
MatInputModule, MatFormFieldModule, MatProgressSpinnerModule, MatSelectModule,
|
||||
MatSnackBarModule, MatCardModule,
|
||||
MatDividerModule, MatExpansionModule, MatDialogModule,
|
||||
],
|
||||
styles: [`
|
||||
.provider-select { width: 140px; }
|
||||
.provider-select .mat-mdc-form-field-subscript-wrapper { display: none; }
|
||||
`],
|
||||
template: `
|
||||
<div class="h-screen flex flex-col bg-gray-50">
|
||||
<mat-toolbar color="primary" class="h-14 flex items-center justify-between px-4">
|
||||
@@ -45,6 +51,8 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
|
||||
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="px-6 py-6 space-y-6">
|
||||
|
||||
@@ -64,15 +72,29 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
|
||||
<span class="text-xs text-gray-400">
|
||||
Describe the pages, layout, theme, and content you want.
|
||||
</span>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
[disabled]="!prompt.trim() || (session.generating$ | async)"
|
||||
(click)="generate()"
|
||||
>
|
||||
<mat-icon>auto_awesome</mat-icon>
|
||||
Generate
|
||||
</button>
|
||||
<div class="flex items-center gap-2">
|
||||
<mat-form-field appearance="outline" class="!mb-0 provider-select" *ngIf="configuredProviders.length > 1">
|
||||
<mat-label>Provider</mat-label>
|
||||
<mat-select [(ngModel)]="selectedProvider" class="text-sm">
|
||||
<mat-option *ngFor="let p of configuredProviders" [value]="p" class="capitalize">{{ p }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<span *ngIf="configuredProviders.length === 1" class="text-xs text-gray-500 capitalize mr-1">{{ configuredProviders[0] }}</span>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
[disabled]="!prompt.trim() || (session.generating$ | async) || configuredProviders.length === 0"
|
||||
(click)="generate()"
|
||||
>
|
||||
<mat-icon>auto_awesome</mat-icon>
|
||||
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>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@@ -180,7 +202,14 @@ import { DeleteVersionDialog } from './delete-version-dialog/delete-version-dial
|
||||
[disabled]="(session.generating$ | async) === true"
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
<div class="flex justify-end mt-2">
|
||||
<div class="flex justify-end items-center gap-2 mt-2">
|
||||
<mat-form-field appearance="outline" class="!mb-0 provider-select" *ngIf="configuredProviders.length > 1">
|
||||
<mat-label>Provider</mat-label>
|
||||
<mat-select [(ngModel)]="selectedProvider" class="text-sm">
|
||||
<mat-option *ngFor="let p of configuredProviders" [value]="p" class="capitalize">{{ p }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<span *ngIf="configuredProviders.length === 1" class="text-xs text-gray-500 capitalize mr-1">{{ configuredProviders[0] }}</span>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
@@ -261,6 +290,7 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private api = inject(LlmApiService);
|
||||
private llmConfigService = inject(LlmConfigService);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private dialog = inject(MatDialog);
|
||||
session = inject(LlmSessionService);
|
||||
@@ -270,6 +300,11 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
||||
showRefine = false;
|
||||
siteId: string | null = null;
|
||||
versions: GenerationVersion[] = [];
|
||||
llmKeyStatus: LLMKeyStatus = {};
|
||||
llmBaseUrls: Record<string, string> = {};
|
||||
llmModels: Record<string, string> = {};
|
||||
configuredProviders: string[] = [];
|
||||
selectedProvider = '';
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -285,10 +320,36 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
||||
this.versions = [];
|
||||
this.session.clearHistory();
|
||||
this.loadVersions();
|
||||
this.loadLlmConfig();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadLlmConfig(): void {
|
||||
this.llmConfigService.getStatus()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (status) => {
|
||||
this.llmKeyStatus = status;
|
||||
const urls: Record<string, string> = {};
|
||||
const models: Record<string, string> = {};
|
||||
this.configuredProviders = Object.keys(status).filter(k => {
|
||||
if (status[k]?.baseUrl) urls[k] = status[k].baseUrl!;
|
||||
if (status[k]?.model) models[k] = status[k].model!;
|
||||
return status[k]?.configured;
|
||||
});
|
||||
this.llmBaseUrls = urls;
|
||||
this.llmModels = models;
|
||||
if (this.configuredProviders.length === 1) {
|
||||
this.selectedProvider = this.configuredProviders[0];
|
||||
} else if (this.configuredProviders.length > 1 && !this.selectedProvider) {
|
||||
this.selectedProvider = this.configuredProviders[0];
|
||||
}
|
||||
},
|
||||
error: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
private loadVersions(): void {
|
||||
if (!this.siteId) return;
|
||||
this.session.setLoadingVersions(true);
|
||||
@@ -320,13 +381,21 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
||||
|
||||
|
||||
generate(): void {
|
||||
if (!this.prompt.trim() || !this.siteId) return;
|
||||
if (!this.prompt.trim() || !this.siteId || !this.selectedProvider) return;
|
||||
this.showRefine = false;
|
||||
this.session.setPrompt(this.prompt);
|
||||
this.session.setGenerating(true);
|
||||
this.session.clearError();
|
||||
|
||||
this.api.generate({ prompt: this.prompt, siteId: this.siteId })
|
||||
const baseUrl = this.llmBaseUrls[this.selectedProvider];
|
||||
const model = this.llmModels[this.selectedProvider];
|
||||
this.api.generate({
|
||||
prompt: this.prompt,
|
||||
siteId: this.siteId,
|
||||
provider: this.selectedProvider,
|
||||
baseUrl,
|
||||
model,
|
||||
})
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (data) => {
|
||||
@@ -344,13 +413,18 @@ export class LlmWorkspaceComponent implements OnInit, OnDestroy {
|
||||
|
||||
refine(): void {
|
||||
const result = this.session.currentResult;
|
||||
if (!result || !this.refinePrompt.trim() || !this.siteId) 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 model = this.llmModels[this.selectedProvider];
|
||||
this.api.refine({
|
||||
prompt: this.refinePrompt,
|
||||
siteId: this.siteId,
|
||||
provider: this.selectedProvider,
|
||||
baseUrl,
|
||||
model,
|
||||
currentStructure: result,
|
||||
})
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
|
||||
Reference in New Issue
Block a user