diff --git a/web/src/routes/api/admin/ai/aliases/[id]/+server.ts b/web/src/routes/api/admin/ai/aliases/[id]/+server.ts index 5ee83a1..e77e06f 100644 --- a/web/src/routes/api/admin/ai/aliases/[id]/+server.ts +++ b/web/src/routes/api/admin/ai/aliases/[id]/+server.ts @@ -7,12 +7,10 @@ export const PATCH: RequestHandler = async ({ params, request, locals }) => { if (!locals.workspace || !locals.user) error(401); const body = await request.json(); - const updates: Record = {}; - if ('description' in body) updates.description = body.description; - if ('is_active' in body) updates.is_active = body.is_active; const [row] = await sql` UPDATE ai_model_aliases SET + alias = COALESCE(${body.alias ?? null}, alias), description = COALESCE(${body.description ?? null}, description), is_active = COALESCE(${body.is_active ?? null}, is_active), updated_at = now() diff --git a/web/src/routes/server-admin/ai/+page.server.ts b/web/src/routes/server-admin/ai/+page.server.ts index 6f342d0..00e04f7 100644 --- a/web/src/routes/server-admin/ai/+page.server.ts +++ b/web/src/routes/server-admin/ai/+page.server.ts @@ -30,13 +30,14 @@ export const load: PageServerLoad = async () => { const usage = await sql` SELECT model_alias, + model_actual, count(*)::int AS call_count, sum(prompt_tokens)::int AS prompt_tokens, sum(completion_tokens)::int AS completion_tokens, sum(total_tokens)::int AS total_tokens FROM ai_usage_log WHERE created_at > now() - interval '30 days' - GROUP BY model_alias + GROUP BY model_alias, model_actual ORDER BY total_tokens DESC `; diff --git a/web/src/routes/server-admin/ai/+page.svelte b/web/src/routes/server-admin/ai/+page.svelte index 5a73665..ede2efd 100644 --- a/web/src/routes/server-admin/ai/+page.svelte +++ b/web/src/routes/server-admin/ai/+page.svelte @@ -1,1373 +1,1509 @@ - - -
-
-

AI-administrasjon

- {aliases.length} aliaser / {providers.length} leverandører / {totalTokens.toLocaleString('nb-NO')} tokens (30d) -
- - - {#if keysLoaded} -
- {#each apiKeys as key} - - {key.name.replace('_API_KEY', '')} - {key.configured ? '\u2713' : '\u2717'} - - {/each} -
- {/if} - - {#if errorMsg} -
{errorMsg}
- {/if} - - -
-
-

Modellkatalog (OpenRouter)

-
- {#if catalogLoaded} - - {/if} - -
-
- - {#if catalogLoaded} - {#if groupedByProvider.length === 0} -

Ingen modeller matcher søket.

- {:else} - {#each groupedByProvider as [providerName, models] (providerName)} -
- - - {#if expandedProviders.has(providerName)} -
-
- Modell - Kontekst - Prompt/M - Kompl./M - -
- - {#each models as model (model.id)} -
- {model.name} - {formatCtx(model.context_length)} - {formatPrice(model.prompt_price_per_m)} - {formatPrice(model.completion_price_per_m)} - - {#if addingFromCatalog === model.id} - - {:else} - - {/if} - -
- {/each} -
- {/if} -
- {/each} - {/if} - {/if} -
- - -
-

Modellaliaser

-
-
- Alias - Beskrivelse - Leverandører - Aktiv - -
- - {#each aliases as alias (alias.id)} - {@const ap = providersForAlias(alias.id)} -
- (expandedAlias = expandedAlias === alias.id ? null : alias.id)} - > - {alias.alias} - - {alias.description ?? '\u2014'} - {ap.length} - - - - - {#if saving === alias.id} - ... - {:else if saved === alias.id} - OK - {/if} - -
- - {#if expandedAlias === alias.id} -
- {#each ap as provider (provider.id)} -
- #{provider.priority} - {provider.litellm_model} - {provider.api_key_env} - - - - - - {#if saving === provider.id} - ... - {:else if saved === provider.id} - OK - {/if} - -
- {/each} - -
- -
- - {#if catalogLoaded} - - {/if} -
- - -
- - {#if showCatalogPicker && catalogLoaded} -
- -
- {#each catalogPickerFiltered as model (model.id)} - - {/each} -
-
- {/if} -
- {/if} - {/each} -
- -
- - - -
-
- - -
-

Jobbruting

-
-
- Jobbtype - Modellalias - Beskrivelse - -
- - {#each routing as route (route.job_type)} -
- {route.job_type} - - - - {route.description ?? '\u2014'} - - {#if saving === route.job_type} - ... - {:else if saved === route.job_type} - OK - {/if} - -
- {/each} -
- -
- - - - -
-
- - -
-

System-prompts

-
-
- Action - Beskrivelse - Tegn - Oppdatert - -
- - {#each prompts as prompt (prompt.action)} -
- {prompt.action} - {prompt.description ?? '\u2014'} - {prompt.system_prompt.length} - {new Date(prompt.updated_at).toLocaleDateString('nb-NO')} - - {#if saving === prompt.action} - ... - {:else if saved === prompt.action} - OK - {:else} - - {/if} - -
- - {#if editingPrompt === prompt.action} -
- - -
- {/if} - {/each} -
-
- - -
-

Tokenforbruk (siste 30 dager)

- {#if usage.length === 0} -

Ingen AI-kall registrert ennå.

- {:else} -
-
- Modellalias - Kall - Prompt - Completion - Totalt -
- - {#each usage as row} -
- {row.model_alias} - {row.call_count} - {row.prompt_tokens.toLocaleString('nb-NO')} - {row.completion_tokens.toLocaleString('nb-NO')} - {row.total_tokens.toLocaleString('nb-NO')} -
- {/each} -
- {/if} -
- - -
-

Konfigurasjon

-
- - - {#if configMsg} - {configMsg} - {/if} -

Genererer LiteLLM config.yaml fra databasen. «Generer + restart» aktiverer endringene umiddelbart.

-
-
-
- - + + +
+
+

AI-administrasjon

+ {aliases.length} aliaser / {providers.length} leverandører / {totalTokens.toLocaleString('nb-NO')} tokens (30d) +
+ + + {#if keysLoaded} +
+ {#each apiKeys as key} + + {key.name.replace('_API_KEY', '')} + {key.configured ? '\u2713' : '\u2717'} + + {/each} +
+ {/if} + + {#if errorMsg} +
{errorMsg}
+ {/if} + + +
+
+

Modellkatalog (OpenRouter)

+
+ {#if catalogLoaded} + + {/if} + +
+
+ + {#if catalogLoaded} + {#if groupedByProvider.length === 0} +

Ingen modeller matcher søket.

+ {:else} + {#each groupedByProvider as [providerName, models] (providerName)} +
+ + + {#if expandedProviders.has(providerName)} +
+
+ Modell + Kontekst + Prompt/M + Kompl./M + +
+ + {#each models as model (model.id)} +
+ {model.name} + {formatCtx(model.context_length)} + {formatPrice(model.prompt_price_per_m)} + {formatPrice(model.completion_price_per_m)} + + {#if addingFromCatalog === model.id} + + {:else} + + {/if} + +
+ {/each} +
+ {/if} +
+ {/each} + {/if} + {/if} +
+ + +
+

Modellaliaser

+
+
+ Alias + Beskrivelse + Leverandører + Aktiv + +
+ + {#each aliases as alias (alias.id)} + {@const ap = providersForAlias(alias.id)} + {#if editingAlias === alias.id} +
+ + + + + + + + + {#if saving === alias.id} + ... + {/if} + +
+ {:else} +
+ (expandedAlias = expandedAlias === alias.id ? null : alias.id)} + > + {alias.alias} + + {alias.description ?? '\u2014'} + {ap.length} + + + + + + {#if saving === alias.id} + ... + {:else if saved === alias.id} + OK + {/if} + +
+ {/if} + + {#if expandedAlias === alias.id} +
+ {#each ap as provider (provider.id)} +
+ #{provider.priority} + {provider.litellm_model} + {provider.api_key_env} + + + + + + {#if saving === provider.id} + ... + {:else if saved === provider.id} + OK + {/if} + +
+ {/each} + +
+ +
+ + {#if catalogLoaded} + + {/if} +
+ + +
+ + {#if showCatalogPicker && catalogLoaded} +
+ +
+ {#each catalogPickerFiltered as model (model.id)} + + {/each} +
+
+ {/if} +
+ {/if} + {/each} +
+ +
+ + + +
+
+ + +
+

Jobbruting

+
+
+ Jobbtype + Modellalias + Beskrivelse + +
+ + {#each routing as route (route.job_type)} +
+ {route.job_type} + + + + {route.description ?? '\u2014'} + + {#if saving === route.job_type} + ... + {:else if saved === route.job_type} + OK + {/if} + +
+ {/each} +
+ +
+ + + + +
+
+ + +
+

System-prompts

+
+
+ Action + Beskrivelse + Tegn + Oppdatert + +
+ + {#each prompts as prompt (prompt.action)} +
+ {prompt.action} + {prompt.description ?? '\u2014'} + {prompt.system_prompt.length} + {new Date(prompt.updated_at).toLocaleDateString('nb-NO')} + + {#if saving === prompt.action} + ... + {:else if saved === prompt.action} + OK + {:else} + + {/if} + +
+ + {#if editingPrompt === prompt.action} +
+ + +
+ {/if} + {/each} +
+
+ + +
+
+

Tokenforbruk (siste 30 dager)

+ {#if totalEstimatedCost !== null} + Estimert totalkostnad: ${totalEstimatedCost.toFixed(2)} + {/if} +
+ {#if usage.length === 0} +

Ingen AI-kall registrert ennå.

+ {:else} +
+
+ Alias + Modell + Kall + Prompt + Kompl. + Totalt + Est. $ +
+ + {#each usage as row} + {@const cost = estimateCost(row)} +
+ {row.model_alias} + {row.model_actual ?? '\u2014'} + {row.call_count} + {row.prompt_tokens.toLocaleString('nb-NO')} + {row.completion_tokens.toLocaleString('nb-NO')} + {row.total_tokens.toLocaleString('nb-NO')} + {cost !== null ? `$${cost.toFixed(2)}` : '\u2014'} +
+ {/each} +
+ {#if !catalogLoaded} +

Last inn modellkatalogen for å se estimerte kostnader.

+ {/if} + {/if} +
+ + +
+

Konfigurasjon

+
+ + + {#if configMsg} + {configMsg} + {/if} +

Genererer LiteLLM config.yaml fra databasen. «Generer + restart» aktiverer endringene umiddelbart.

+
+
+
+ +