semantic update
This commit is contained in:
@@ -7,67 +7,74 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { requestApi } from '../../lib/api';
|
||||
import { onMount } from "svelte";
|
||||
import { t } from "../../lib/i18n";
|
||||
import { requestApi } from "../../lib/api";
|
||||
|
||||
/** @type {Array} */
|
||||
let {
|
||||
provider,
|
||||
config = {},
|
||||
} = $props();
|
||||
|
||||
let { providers = [], onSave = () => {} } = $props();
|
||||
|
||||
let editingProvider = null;
|
||||
let showForm = false;
|
||||
|
||||
let formData = {
|
||||
name: '',
|
||||
provider_type: 'openai',
|
||||
base_url: 'https://api.openai.com/v1',
|
||||
api_key: '',
|
||||
default_model: 'gpt-4o',
|
||||
is_active: true
|
||||
name: "",
|
||||
provider_type: "openai",
|
||||
base_url: "https://api.openai.com/v1",
|
||||
api_key: "",
|
||||
default_model: "gpt-4o",
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
let testStatus = { type: '', message: '' };
|
||||
let testStatus = { type: "", message: "" };
|
||||
let isTesting = false;
|
||||
|
||||
function resetForm() {
|
||||
formData = {
|
||||
name: '',
|
||||
provider_type: 'openai',
|
||||
base_url: 'https://api.openai.com/v1',
|
||||
api_key: '',
|
||||
default_model: 'gpt-4o',
|
||||
is_active: true
|
||||
name: "",
|
||||
provider_type: "openai",
|
||||
base_url: "https://api.openai.com/v1",
|
||||
api_key: "",
|
||||
default_model: "gpt-4o",
|
||||
is_active: true,
|
||||
};
|
||||
editingProvider = null;
|
||||
testStatus = { type: '', message: '' };
|
||||
testStatus = { type: "", message: "" };
|
||||
}
|
||||
|
||||
function handleEdit(provider) {
|
||||
editingProvider = provider;
|
||||
formData = { ...provider, api_key: '' }; // Don't populate key for security
|
||||
formData = { ...provider, api_key: "" }; // Don't populate key for security
|
||||
showForm = true;
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
console.log("[ProviderConfig][Action] Testing connection", formData);
|
||||
isTesting = true;
|
||||
testStatus = { type: 'info', message: $t.llm.testing };
|
||||
|
||||
testStatus = { type: "info", message: $t.llm.testing };
|
||||
|
||||
try {
|
||||
const endpoint = editingProvider ? `/llm/providers/${editingProvider.id}/test` : '/llm/providers/test';
|
||||
const result = await requestApi(endpoint, 'POST', formData);
|
||||
|
||||
const endpoint = editingProvider
|
||||
? `/llm/providers/${editingProvider.id}/test`
|
||||
: "/llm/providers/test";
|
||||
const result = await requestApi(endpoint, "POST", formData);
|
||||
|
||||
if (result.success) {
|
||||
testStatus = { type: 'success', message: $t.llm.connection_success };
|
||||
testStatus = { type: "success", message: $t.llm.connection_success };
|
||||
} else {
|
||||
testStatus = { type: 'error', message: $t.llm.connection_failed.replace('{error}', result.error || 'Unknown error') };
|
||||
testStatus = {
|
||||
type: "error",
|
||||
message: $t.llm.connection_failed.replace(
|
||||
"{error}",
|
||||
result.error || "Unknown error",
|
||||
),
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
testStatus = { type: 'error', message: $t.llm.connection_failed.replace('{error}', err.message) };
|
||||
testStatus = {
|
||||
type: "error",
|
||||
message: $t.llm.connection_failed.replace("{error}", err.message),
|
||||
};
|
||||
} finally {
|
||||
isTesting = false;
|
||||
}
|
||||
@@ -75,8 +82,10 @@
|
||||
|
||||
async function handleSubmit() {
|
||||
console.log("[ProviderConfig][Action] Submitting provider config");
|
||||
const method = editingProvider ? 'PUT' : 'POST';
|
||||
const endpoint = editingProvider ? `/llm/providers/${editingProvider.id}` : '/llm/providers';
|
||||
const method = editingProvider ? "PUT" : "POST";
|
||||
const endpoint = editingProvider
|
||||
? `/llm/providers/${editingProvider.id}`
|
||||
: "/llm/providers";
|
||||
|
||||
// When editing, only include api_key if user entered a new one
|
||||
const submitData = { ...formData };
|
||||
@@ -97,9 +106,9 @@
|
||||
|
||||
async function toggleActive(provider) {
|
||||
try {
|
||||
await requestApi(`/llm/providers/${provider.id}`, 'PUT', {
|
||||
await requestApi(`/llm/providers/${provider.id}`, "PUT", {
|
||||
...provider,
|
||||
is_active: !provider.is_active
|
||||
is_active: !provider.is_active,
|
||||
});
|
||||
onSave();
|
||||
} catch (err) {
|
||||
@@ -111,28 +120,53 @@
|
||||
<div class="p-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-xl font-bold">{$t.llm.providers_title}</h2>
|
||||
<button
|
||||
<button
|
||||
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition"
|
||||
on:click={() => { resetForm(); showForm = true; }}
|
||||
on:click={() => {
|
||||
resetForm();
|
||||
showForm = true;
|
||||
}}
|
||||
>
|
||||
{$t.llm.add_provider}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if showForm}
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div
|
||||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
|
||||
>
|
||||
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
|
||||
<h3 class="text-lg font-semibold mb-4">{editingProvider ? $t.llm.edit_provider : $t.llm.new_provider}</h3>
|
||||
|
||||
<h3 class="text-lg font-semibold mb-4">
|
||||
{editingProvider ? $t.llm.edit_provider : $t.llm.new_provider}
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="provider-name" class="block text-sm font-medium text-gray-700">{$t.llm.name}</label>
|
||||
<input id="provider-name" type="text" bind:value={formData.name} class="mt-1 block w-full border rounded-md p-2" placeholder="e.g. My OpenAI" />
|
||||
<label
|
||||
for="provider-name"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>{$t.llm.name}</label
|
||||
>
|
||||
<input
|
||||
id="provider-name"
|
||||
type="text"
|
||||
bind:value={formData.name}
|
||||
class="mt-1 block w-full border rounded-md p-2"
|
||||
placeholder="e.g. My OpenAI"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="provider-type" class="block text-sm font-medium text-gray-700">{$t.llm.type}</label>
|
||||
<select id="provider-type" bind:value={formData.provider_type} class="mt-1 block w-full border rounded-md p-2">
|
||||
<label
|
||||
for="provider-type"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>{$t.llm.type}</label
|
||||
>
|
||||
<select
|
||||
id="provider-type"
|
||||
bind:value={formData.provider_type}
|
||||
class="mt-1 block w-full border rounded-md p-2"
|
||||
>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="kilo">Kilo</option>
|
||||
@@ -140,47 +174,88 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="provider-base-url" class="block text-sm font-medium text-gray-700">{$t.llm.base_url}</label>
|
||||
<input id="provider-base-url" type="text" bind:value={formData.base_url} class="mt-1 block w-full border rounded-md p-2" />
|
||||
<label
|
||||
for="provider-base-url"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>{$t.llm.base_url}</label
|
||||
>
|
||||
<input
|
||||
id="provider-base-url"
|
||||
type="text"
|
||||
bind:value={formData.base_url}
|
||||
class="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="provider-api-key" class="block text-sm font-medium text-gray-700">{$t.llm.api_key}</label>
|
||||
<input id="provider-api-key" type="password" bind:value={formData.api_key} class="mt-1 block w-full border rounded-md p-2" placeholder={editingProvider ? "••••••••" : "sk-..."} />
|
||||
<label
|
||||
for="provider-api-key"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>{$t.llm.api_key}</label
|
||||
>
|
||||
<input
|
||||
id="provider-api-key"
|
||||
type="password"
|
||||
bind:value={formData.api_key}
|
||||
class="mt-1 block w-full border rounded-md p-2"
|
||||
placeholder={editingProvider ? "••••••••" : "sk-..."}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="provider-default-model" class="block text-sm font-medium text-gray-700">{$t.llm.default_model}</label>
|
||||
<input id="provider-default-model" type="text" bind:value={formData.default_model} class="mt-1 block w-full border rounded-md p-2" placeholder="gpt-4o" />
|
||||
<label
|
||||
for="provider-default-model"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>{$t.llm.default_model}</label
|
||||
>
|
||||
<input
|
||||
id="provider-default-model"
|
||||
type="text"
|
||||
bind:value={formData.default_model}
|
||||
class="mt-1 block w-full border rounded-md p-2"
|
||||
placeholder="gpt-4o"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="provider-active" type="checkbox" bind:checked={formData.is_active} class="mr-2" />
|
||||
<label for="provider-active" class="text-sm font-medium text-gray-700">{$t.llm.active}</label>
|
||||
<input
|
||||
id="provider-active"
|
||||
type="checkbox"
|
||||
bind:checked={formData.is_active}
|
||||
class="mr-2"
|
||||
/>
|
||||
<label
|
||||
for="provider-active"
|
||||
class="text-sm font-medium text-gray-700">{$t.llm.active}</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if testStatus.message}
|
||||
<div class={`mt-4 p-2 rounded text-sm ${testStatus.type === 'success' ? 'bg-green-100 text-green-800' : testStatus.type === 'error' ? 'bg-red-100 text-red-800' : 'bg-blue-100 text-blue-800'}`}>
|
||||
<div
|
||||
class={`mt-4 p-2 rounded text-sm ${testStatus.type === "success" ? "bg-green-100 text-green-800" : testStatus.type === "error" ? "bg-red-100 text-red-800" : "bg-blue-100 text-blue-800"}`}
|
||||
>
|
||||
{testStatus.message}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-6 flex justify-between gap-2">
|
||||
<button
|
||||
<button
|
||||
class="px-4 py-2 border rounded hover:bg-gray-50 flex-1"
|
||||
on:click={() => { showForm = false; }}
|
||||
on:click={() => {
|
||||
showForm = false;
|
||||
}}
|
||||
>
|
||||
{$t.llm.cancel}
|
||||
</button>
|
||||
<button
|
||||
<button
|
||||
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex-1"
|
||||
disabled={isTesting}
|
||||
on:click={testConnection}
|
||||
>
|
||||
{isTesting ? $t.llm.testing : $t.llm.test}
|
||||
</button>
|
||||
<button
|
||||
<button
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex-1"
|
||||
on:click={handleSubmit}
|
||||
>
|
||||
@@ -193,37 +268,45 @@
|
||||
|
||||
<div class="grid gap-4">
|
||||
{#each providers as provider}
|
||||
<div class="border rounded-lg p-4 flex justify-between items-center bg-white shadow-sm">
|
||||
<div
|
||||
class="border rounded-lg p-4 flex justify-between items-center bg-white shadow-sm"
|
||||
>
|
||||
<div>
|
||||
<div class="font-bold flex items-center gap-2">
|
||||
{provider.name}
|
||||
<span class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}>
|
||||
{provider.is_active ? $t.llm.active : 'Inactive'}
|
||||
<span
|
||||
class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}
|
||||
>
|
||||
{provider.is_active ? $t.llm.active : "Inactive"}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">{provider.provider_type} • {provider.default_model}</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{provider.provider_type} • {provider.default_model}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
<button
|
||||
class="text-sm text-blue-600 hover:underline"
|
||||
on:click={() => handleEdit(provider)}
|
||||
>
|
||||
{$t.common.edit}
|
||||
</button>
|
||||
<button
|
||||
class={`text-sm ${provider.is_active ? 'text-orange-600' : 'text-green-600'} hover:underline`}
|
||||
<button
|
||||
class={`text-sm ${provider.is_active ? "text-orange-600" : "text-green-600"} hover:underline`}
|
||||
on:click={() => toggleActive(provider)}
|
||||
>
|
||||
{provider.is_active ? 'Deactivate' : 'Activate'}
|
||||
{provider.is_active ? "Deactivate" : "Activate"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center py-8 text-gray-500 border-2 border-dashed rounded-lg">
|
||||
<div
|
||||
class="text-center py-8 text-gray-500 border-2 border-dashed rounded-lg"
|
||||
>
|
||||
{$t.llm.no_providers}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- [/DEF:ProviderConfig:Component] -->
|
||||
<!-- [/DEF:ProviderConfig:Component] -->
|
||||
|
||||
Reference in New Issue
Block a user