diff --git a/tools/ui/src/lib/components/app/backends/BackendCard.svelte b/tools/ui/src/lib/components/app/backends/BackendCard.svelte new file mode 100644 index 0000000000..c9c40132f7 --- /dev/null +++ b/tools/ui/src/lib/components/app/backends/BackendCard.svelte @@ -0,0 +1,89 @@ + + + + + + + + + {backend.name} + + {displayUrl} + + + + + {protocolLabel} + + + + + {#if isLocal} + Built-in + {:else} + + onToggle?.(value)} /> + + + {backend.enabled ? 'Enabled' : 'Disabled'} + + + + + onEdit?.()} size="sm" variant="ghost"> + + + + (showDelete = true)} + size="sm" + variant="ghost" + > + + + + {/if} + + + + (showDelete = false)} + onConfirm={() => { + showDelete = false; + onDelete?.(); + }} + title="Delete backend?" + variant="destructive" +/> diff --git a/tools/ui/src/lib/components/app/backends/BackendForm.svelte b/tools/ui/src/lib/components/app/backends/BackendForm.svelte new file mode 100644 index 0000000000..641e217b50 --- /dev/null +++ b/tools/ui/src/lib/components/app/backends/BackendForm.svelte @@ -0,0 +1,152 @@ + + + + + + Base URL * + + + onChange({ baseUrl: e.currentTarget.value })} + placeholder="https://api.example.com" + type="url" + value={backend.baseUrl} + /> + + {#if urlError} + {urlError} + {/if} + + + + + Display name + + + onChange({ name: e.currentTarget.value })} + placeholder="Name shown in the model selector" + type="text" + value={backend.name} + /> + + + + API format + + onChange({ protocol: value as BackendProtocol })} + type="single" + value={backend.protocol} + > + {protocolLabel} + + + {#each PROTOCOL_OPTIONS as option (option.value)} + {option.label} + {/each} + + + + + + + API key + + + onChange({ apiKey: e.currentTarget.value || undefined })} + placeholder="Optional" + type="password" + value={backend.apiKey ?? ''} + /> + + + {#if isAnthropic} + Sent as the x-api-key header. + {:else} + Sent as a Bearer token. + {/if} + + + + + + {#if showAdvanced} + + {:else} + + {/if} + + Advanced + + + + + + + Chat completions path + + + onChange({ chatPath: e.currentTarget.value || undefined })} + placeholder={DEFAULT_BACKEND_CHAT_PATH} + type="text" + value={backend.chatPath ?? ''} + /> + + + + + Models path + + + onChange({ modelsPath: e.currentTarget.value || undefined })} + placeholder={DEFAULT_BACKEND_MODELS_PATH} + type="text" + value={backend.modelsPath ?? ''} + /> + + + + + diff --git a/tools/ui/src/lib/components/app/backends/DialogBackendForm.svelte b/tools/ui/src/lib/components/app/backends/DialogBackendForm.svelte new file mode 100644 index 0000000000..a7e437b5a2 --- /dev/null +++ b/tools/ui/src/lib/components/app/backends/DialogBackendForm.svelte @@ -0,0 +1,191 @@ + + + + + + {isEdit ? 'Edit backend' : 'Add backend'} + + Connect an OpenAI- or Anthropic-compatible endpoint. + + + {#if !isEdit} + + {#each BACKEND_PRESETS as preset (preset.id)} + applyPreset(preset)} + size="sm" + variant={selectedPresetId === preset.id ? 'secondary' : 'outline'} + > + {preset.name} + + {/each} + + {/if} + + + + + + + {#if testing || testResult} + + {#if testing} + + + Testing connection... + {:else if testResult?.ok} + + + + Connected. + {testResult.modelCount ?? 0} + model{(testResult.modelCount ?? 0) === 1 ? '' : 's'} available. + + {:else} + + + {testResult?.error ?? 'Connection failed'} + {/if} + + {/if} + + + handleOpenChange(false)} size="sm" variant="secondary"> + Cancel + + + + Test connection + + + + {isEdit ? 'Save' : 'Add'} + + + + + diff --git a/tools/ui/src/lib/components/app/backends/SettingsBackends.svelte b/tools/ui/src/lib/components/app/backends/SettingsBackends.svelte new file mode 100644 index 0000000000..abcc61a53e --- /dev/null +++ b/tools/ui/src/lib/components/app/backends/SettingsBackends.svelte @@ -0,0 +1,71 @@ + + + + + + + + {#each backendsStore.external as backend (backend.id)} + backendsStore.removeBackend(backend.id)} + onEdit={() => handleEdit(backend)} + onToggle={(enabled) => backendsStore.updateBackend(backend.id, { enabled })} + /> + {/each} + + + + + + + + Add another backend + + Connect an OpenAI- or Anthropic-compatible endpoint. + + + + + + + Add Backend + + + + diff --git a/tools/ui/src/lib/components/app/backends/index.ts b/tools/ui/src/lib/components/app/backends/index.ts new file mode 100644 index 0000000000..809cb6b3c5 --- /dev/null +++ b/tools/ui/src/lib/components/app/backends/index.ts @@ -0,0 +1,4 @@ +export { default as BackendCard } from './BackendCard.svelte'; +export { default as BackendForm } from './BackendForm.svelte'; +export { default as DialogBackendForm } from './DialogBackendForm.svelte'; +export { default as SettingsBackends } from './SettingsBackends.svelte'; diff --git a/tools/ui/src/lib/components/app/index.ts b/tools/ui/src/lib/components/app/index.ts index 4914c743a5..aa42a18f15 100644 --- a/tools/ui/src/lib/components/app/index.ts +++ b/tools/ui/src/lib/components/app/index.ts @@ -1,4 +1,5 @@ export * from './actions'; +export * from './backends'; export * from './badges'; export * from './chat'; export * from './content'; diff --git a/tools/ui/src/lib/components/app/settings/SettingsChat/SettingsChat.svelte b/tools/ui/src/lib/components/app/settings/SettingsChat/SettingsChat.svelte index 8793900759..670a5ce769 100644 --- a/tools/ui/src/lib/components/app/settings/SettingsChat/SettingsChat.svelte +++ b/tools/ui/src/lib/components/app/settings/SettingsChat/SettingsChat.svelte @@ -1,5 +1,6 @@
{backend.name}
{displayUrl}
{urlError}
+ {#if isAnthropic} + Sent as the x-api-key header. + {:else} + Sent as a Bearer token. + {/if} +