316 lines
12 KiB
Svelte
316 lines
12 KiB
Svelte
<!-- [DEF:LLMSettingsPage:Component] -->
|
|
<!--
|
|
@TIER: STANDARD
|
|
@SEMANTICS: admin, llm, settings, provider, configuration
|
|
@PURPOSE: Admin settings page for LLM provider configuration.
|
|
@LAYER: UI
|
|
@RELATION: CALLS -> frontend/src/components/llm/ProviderConfig.svelte
|
|
-->
|
|
|
|
<script>
|
|
import { onMount } from 'svelte';
|
|
import ProviderConfig from '../../../../components/llm/ProviderConfig.svelte';
|
|
import { t } from '../../../../lib/i18n';
|
|
import { addToast } from '../../../../lib/toasts';
|
|
import { requestApi } from '../../../../lib/api';
|
|
|
|
let providers = [];
|
|
let loading = true;
|
|
let savingPrompts = false;
|
|
let plannerProvider = '';
|
|
let plannerModel = '';
|
|
let bindings = {
|
|
dashboard_validation: '',
|
|
documentation: '',
|
|
git_commit: '',
|
|
};
|
|
let prompts = {
|
|
documentation_prompt: '',
|
|
dashboard_validation_prompt: '',
|
|
git_commit_prompt: '',
|
|
};
|
|
|
|
const DEFAULT_LLM_PROMPTS = {
|
|
dashboard_validation_prompt:
|
|
"Analyze the attached dashboard screenshot and the following execution logs for health and visual issues.\\n\\nLogs:\\n{logs}\\n\\nProvide the analysis in JSON format with the following structure:\\n{\\n \\\"status\\\": \\\"PASS\\\" | \\\"WARN\\\" | \\\"FAIL\\\",\\n \\\"summary\\\": \\\"Short summary of findings\\\",\\n \\\"issues\\\": [\\n {\\n \\\"severity\\\": \\\"WARN\\\" | \\\"FAIL\\\",\\n \\\"message\\\": \\\"Description of the issue\\\",\\n \\\"location\\\": \\\"Optional location info (e.g. chart name)\\\"\\n }\\n ]\\n}",
|
|
documentation_prompt:
|
|
"Generate professional documentation for the following dataset and its columns.\\nDataset: {dataset_name}\\nColumns: {columns_json}\\n\\nProvide the documentation in JSON format:\\n{\\n \\\"dataset_description\\\": \\\"General description of the dataset\\\",\\n \\\"column_descriptions\\\": [\\n {\\n \\\"name\\\": \\\"column_name\\\",\\n \\\"description\\\": \\\"Generated description\\\"\\n }\\n ]\\n}",
|
|
git_commit_prompt:
|
|
"Generate a concise and professional git commit message based on the following diff and recent history.\\nUse Conventional Commits format (e.g., feat: ..., fix: ..., docs: ...).\\n\\nRecent History:\\n{history}\\n\\nDiff:\\n{diff}\\n\\nCommit Message:",
|
|
};
|
|
const DEFAULT_LLM_PROVIDER_BINDINGS = {
|
|
dashboard_validation: '',
|
|
documentation: '',
|
|
git_commit: '',
|
|
};
|
|
|
|
function isMultimodalModel(modelName) {
|
|
const token = (modelName || '').toLowerCase();
|
|
if (!token) return false;
|
|
return (
|
|
token.includes('gpt-4o') ||
|
|
token.includes('gpt-4.1') ||
|
|
token.includes('vision') ||
|
|
token.includes('vl') ||
|
|
token.includes('gemini') ||
|
|
token.includes('claude-3') ||
|
|
token.includes('claude-sonnet-4')
|
|
);
|
|
}
|
|
|
|
function getProviderById(providerId) {
|
|
if (!providerId) return null;
|
|
return providers.find((item) => item.id === providerId) || null;
|
|
}
|
|
|
|
async function fetchProviders() {
|
|
loading = true;
|
|
try {
|
|
const [providerList, consolidatedSettings] = await Promise.all([
|
|
requestApi('/llm/providers'),
|
|
requestApi('/settings/consolidated'),
|
|
]);
|
|
providers = providerList;
|
|
prompts = {
|
|
...DEFAULT_LLM_PROMPTS,
|
|
...(consolidatedSettings?.llm?.prompts || {}),
|
|
};
|
|
bindings = {
|
|
...DEFAULT_LLM_PROVIDER_BINDINGS,
|
|
...(consolidatedSettings?.llm?.provider_bindings || {}),
|
|
};
|
|
plannerProvider = consolidatedSettings?.llm?.assistant_planner_provider || '';
|
|
plannerModel = consolidatedSettings?.llm?.assistant_planner_model || '';
|
|
} catch (err) {
|
|
console.error("Failed to fetch providers", err);
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
async function saveSettings() {
|
|
savingPrompts = true;
|
|
try {
|
|
const current = await requestApi('/settings/consolidated');
|
|
const payload = {
|
|
...current,
|
|
llm: {
|
|
...(current?.llm || {}),
|
|
prompts: {
|
|
...DEFAULT_LLM_PROMPTS,
|
|
...prompts,
|
|
},
|
|
provider_bindings: {
|
|
...DEFAULT_LLM_PROVIDER_BINDINGS,
|
|
...bindings,
|
|
},
|
|
assistant_planner_provider: plannerProvider || '',
|
|
assistant_planner_model: plannerModel || '',
|
|
},
|
|
};
|
|
await requestApi('/settings/consolidated', 'PATCH', payload);
|
|
addToast($t.settings?.save_success , 'success');
|
|
} catch (err) {
|
|
console.error('[LLMSettingsPage][Coherence:Failed] Failed to save llm settings', err);
|
|
addToast($t.settings?.save_failed , 'error');
|
|
} finally {
|
|
savingPrompts = false;
|
|
}
|
|
}
|
|
|
|
onMount(fetchProviders);
|
|
</script>
|
|
|
|
<div class="max-w-4xl mx-auto py-8 px-4">
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold text-gray-900">{$t.settings?.llm }</h1>
|
|
<p class="mt-2 text-gray-600">
|
|
{$t.settings?.llm_description ||
|
|
"Configure LLM providers for dashboard validation, documentation generation, and git assistance."}
|
|
</p>
|
|
</div>
|
|
|
|
{#if loading}
|
|
<div class="flex justify-center py-12">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
{:else}
|
|
<ProviderConfig {providers} onSave={fetchProviders} />
|
|
|
|
<div class="mt-6 rounded-lg border border-gray-200 bg-white p-4">
|
|
<h2 class="text-lg font-semibold text-gray-900">
|
|
{$t.settings?.llm_chatbot_settings_title }
|
|
</h2>
|
|
<p class="mt-1 text-sm text-gray-600">
|
|
{$t.settings?.llm_chatbot_settings_description ||
|
|
'Select provider and optional model override for assistant intent planning.'}
|
|
</p>
|
|
|
|
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div>
|
|
<label for="admin-planner-provider" class="block text-sm font-medium text-gray-700">
|
|
{$t.settings?.llm_chatbot_provider }
|
|
</label>
|
|
<select
|
|
id="admin-planner-provider"
|
|
bind:value={plannerProvider}
|
|
class="mt-1 block w-full rounded-md border border-gray-300 p-2 text-sm"
|
|
>
|
|
<option value="">{$t.dashboard?.use_default }</option>
|
|
{#each providers as provider}
|
|
<option value={provider.id}>
|
|
{provider.name} ({provider.default_model})
|
|
</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="admin-planner-model" class="block text-sm font-medium text-gray-700">
|
|
{$t.settings?.llm_chatbot_model }
|
|
</label>
|
|
<input
|
|
id="admin-planner-model"
|
|
type="text"
|
|
bind:value={plannerModel}
|
|
placeholder={$t.settings?.llm_chatbot_model_placeholder }
|
|
class="mt-1 block w-full rounded-md border border-gray-300 p-2 text-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 rounded-lg border border-gray-200 bg-white p-4">
|
|
<h2 class="text-lg font-semibold text-gray-900">
|
|
{$t.settings?.llm_provider_bindings_title }
|
|
</h2>
|
|
<p class="mt-1 text-sm text-gray-600">
|
|
{$t.settings?.llm_provider_bindings_description ||
|
|
'Select which provider is used by default for each LLM task.'}
|
|
</p>
|
|
|
|
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div>
|
|
<label for="admin-binding-dashboard-validation" class="block text-sm font-medium text-gray-700">
|
|
{$t.settings?.llm_binding_dashboard_validation }
|
|
</label>
|
|
<select
|
|
id="admin-binding-dashboard-validation"
|
|
bind:value={bindings.dashboard_validation}
|
|
class="mt-1 block w-full rounded-md border border-gray-300 p-2 text-sm"
|
|
>
|
|
<option value="">{$t.dashboard?.use_default }</option>
|
|
{#each providers as provider}
|
|
<option value={provider.id}>
|
|
{provider.name} ({provider.default_model})
|
|
</option>
|
|
{/each}
|
|
</select>
|
|
{#if bindings.dashboard_validation && !isMultimodalModel(getProviderById(bindings.dashboard_validation)?.default_model)}
|
|
<p class="mt-1 text-xs text-amber-700">
|
|
{$t.settings?.llm_multimodal_warning ||
|
|
'Dashboard validation requires a multimodal model (image input).'}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
|
|
<div>
|
|
<label for="admin-binding-documentation" class="block text-sm font-medium text-gray-700">
|
|
{$t.settings?.llm_binding_documentation }
|
|
</label>
|
|
<select
|
|
id="admin-binding-documentation"
|
|
bind:value={bindings.documentation}
|
|
class="mt-1 block w-full rounded-md border border-gray-300 p-2 text-sm"
|
|
>
|
|
<option value="">{$t.dashboard?.use_default }</option>
|
|
{#each providers as provider}
|
|
<option value={provider.id}>
|
|
{provider.name} ({provider.default_model})
|
|
</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="md:col-span-2">
|
|
<label for="admin-binding-git-commit" class="block text-sm font-medium text-gray-700">
|
|
{$t.settings?.llm_binding_git_commit }
|
|
</label>
|
|
<select
|
|
id="admin-binding-git-commit"
|
|
bind:value={bindings.git_commit}
|
|
class="mt-1 block w-full rounded-md border border-gray-300 p-2 text-sm"
|
|
>
|
|
<option value="">{$t.dashboard?.use_default }</option>
|
|
{#each providers as provider}
|
|
<option value={provider.id}>
|
|
{provider.name} ({provider.default_model})
|
|
</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 rounded-lg border border-gray-200 bg-gray-50 p-4">
|
|
<h2 class="text-lg font-semibold text-gray-900">
|
|
{$t.settings?.llm_prompts_title }
|
|
</h2>
|
|
<p class="mt-1 text-sm text-gray-600">
|
|
{$t.settings?.llm_prompts_description ||
|
|
'Edit reusable prompts used for documentation, dashboard validation, and git commit generation.'}
|
|
</p>
|
|
|
|
<div class="mt-4 space-y-4">
|
|
<div>
|
|
<label for="admin-documentation-prompt" class="block text-sm font-medium text-gray-700">
|
|
{$t.settings?.llm_prompt_documentation }
|
|
</label>
|
|
<textarea
|
|
id="admin-documentation-prompt"
|
|
bind:value={prompts.documentation_prompt}
|
|
rows="8"
|
|
class="mt-1 block w-full rounded-md border border-gray-300 p-2 font-mono text-xs"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="admin-dashboard-validation-prompt" class="block text-sm font-medium text-gray-700">
|
|
{$t.settings?.llm_prompt_dashboard_validation }
|
|
</label>
|
|
<textarea
|
|
id="admin-dashboard-validation-prompt"
|
|
bind:value={prompts.dashboard_validation_prompt}
|
|
rows="10"
|
|
class="mt-1 block w-full rounded-md border border-gray-300 p-2 font-mono text-xs"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="admin-git-commit-prompt" class="block text-sm font-medium text-gray-700">
|
|
{$t.settings?.llm_prompt_git_commit }
|
|
</label>
|
|
<textarea
|
|
id="admin-git-commit-prompt"
|
|
bind:value={prompts.git_commit_prompt}
|
|
rows="8"
|
|
class="mt-1 block w-full rounded-md border border-gray-300 p-2 font-mono text-xs"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 flex justify-end">
|
|
<button
|
|
class="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-60"
|
|
disabled={savingPrompts}
|
|
on:click={saveSettings}
|
|
>
|
|
{savingPrompts ? '...' : ($t.settings?.save_llm_prompts )}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- [/DEF:LLMSettingsPage:Component] -->
|