@swarmclawai/swarmclaw 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { SettingsSectionProps } from './types'
|
|
4
|
+
|
|
5
|
+
interface EmbeddingSectionProps extends SettingsSectionProps {
|
|
6
|
+
credList: Array<{ id: string; name: string; provider: string }>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function EmbeddingSection({ appSettings, patchSettings, inputClass, credList }: EmbeddingSectionProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="mb-10">
|
|
12
|
+
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
13
|
+
Embeddings
|
|
14
|
+
</h3>
|
|
15
|
+
<p className="text-[12px] text-text-3 mb-5">
|
|
16
|
+
Enable semantic search for agent memory. Requires an embedding model provider.
|
|
17
|
+
</p>
|
|
18
|
+
<div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
19
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Provider</label>
|
|
20
|
+
<div className="grid grid-cols-4 gap-2 mb-5">
|
|
21
|
+
{[
|
|
22
|
+
{ id: null, name: 'Off' },
|
|
23
|
+
{ id: 'local' as const, name: 'Local (Free)' },
|
|
24
|
+
{ id: 'openai' as const, name: 'OpenAI' },
|
|
25
|
+
{ id: 'ollama' as const, name: 'Ollama' },
|
|
26
|
+
].map((p) => (
|
|
27
|
+
<button
|
|
28
|
+
key={String(p.id)}
|
|
29
|
+
onClick={() => patchSettings({ embeddingProvider: p.id, embeddingModel: null, embeddingCredentialId: null })}
|
|
30
|
+
className={`py-3 px-3 rounded-[12px] text-center cursor-pointer transition-all text-[13px] font-600 border
|
|
31
|
+
${(appSettings.embeddingProvider || null) === p.id
|
|
32
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
33
|
+
: 'bg-bg border-white/[0.06] text-text-2 hover:bg-surface-2'}`}
|
|
34
|
+
style={{ fontFamily: 'inherit' }}
|
|
35
|
+
>
|
|
36
|
+
{p.name}
|
|
37
|
+
</button>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{appSettings.embeddingProvider === 'local' && (
|
|
42
|
+
<p className="text-[12px] text-text-3/80 mb-5">
|
|
43
|
+
Runs <span className="text-text-2 font-600">all-MiniLM-L6-v2</span> locally in Node.js — no API key, no cost, works offline. Model downloads once (~23MB).
|
|
44
|
+
</p>
|
|
45
|
+
)}
|
|
46
|
+
|
|
47
|
+
{appSettings.embeddingProvider === 'openai' && (
|
|
48
|
+
<>
|
|
49
|
+
<div className="mb-5">
|
|
50
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Model</label>
|
|
51
|
+
<select
|
|
52
|
+
value={appSettings.embeddingModel || 'text-embedding-3-small'}
|
|
53
|
+
onChange={(e) => patchSettings({ embeddingModel: e.target.value })}
|
|
54
|
+
className={`${inputClass} appearance-none cursor-pointer`}
|
|
55
|
+
style={{ fontFamily: 'inherit' }}
|
|
56
|
+
>
|
|
57
|
+
<option value="text-embedding-3-small">text-embedding-3-small</option>
|
|
58
|
+
<option value="text-embedding-3-large">text-embedding-3-large</option>
|
|
59
|
+
</select>
|
|
60
|
+
</div>
|
|
61
|
+
<div>
|
|
62
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">API Key</label>
|
|
63
|
+
{credList.filter((c) => c.provider === 'openai').length > 0 ? (
|
|
64
|
+
<select
|
|
65
|
+
value={appSettings.embeddingCredentialId || ''}
|
|
66
|
+
onChange={(e) => patchSettings({ embeddingCredentialId: e.target.value || null })}
|
|
67
|
+
className={`${inputClass} appearance-none cursor-pointer`}
|
|
68
|
+
style={{ fontFamily: 'inherit' }}
|
|
69
|
+
>
|
|
70
|
+
<option value="">Select a key...</option>
|
|
71
|
+
{credList.filter((c) => c.provider === 'openai').map((c) => (
|
|
72
|
+
<option key={c.id} value={c.id}>{c.name}</option>
|
|
73
|
+
))}
|
|
74
|
+
</select>
|
|
75
|
+
) : (
|
|
76
|
+
<p className="text-[12px] text-text-3/60">No OpenAI API keys configured.</p>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
{appSettings.embeddingProvider === 'ollama' && (
|
|
83
|
+
<div>
|
|
84
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Model</label>
|
|
85
|
+
<input
|
|
86
|
+
type="text"
|
|
87
|
+
value={appSettings.embeddingModel || 'nomic-embed-text'}
|
|
88
|
+
onChange={(e) => patchSettings({ embeddingModel: e.target.value })}
|
|
89
|
+
placeholder="nomic-embed-text"
|
|
90
|
+
className={inputClass}
|
|
91
|
+
style={{ fontFamily: 'inherit' }}
|
|
92
|
+
/>
|
|
93
|
+
<p className="text-[11px] text-text-3/60 mt-2">Uses your local Ollama instance for embeddings</p>
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
6
|
+
import type { SettingsSectionProps } from './types'
|
|
7
|
+
|
|
8
|
+
export function HeartbeatSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
|
|
9
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
10
|
+
const [disablingHeartbeats, setDisablingHeartbeats] = useState(false)
|
|
11
|
+
const [heartbeatBulkNotice, setHeartbeatBulkNotice] = useState('')
|
|
12
|
+
|
|
13
|
+
const handleDisableAllHeartbeats = async () => {
|
|
14
|
+
if (disablingHeartbeats) return
|
|
15
|
+
setDisablingHeartbeats(true)
|
|
16
|
+
setHeartbeatBulkNotice('')
|
|
17
|
+
try {
|
|
18
|
+
const result = await api<{
|
|
19
|
+
ok: boolean
|
|
20
|
+
updatedSessions: number
|
|
21
|
+
cancelledQueued: number
|
|
22
|
+
abortedRunning: number
|
|
23
|
+
}>('POST', '/sessions/heartbeat', { action: 'disable_all' })
|
|
24
|
+
await loadSessions()
|
|
25
|
+
setHeartbeatBulkNotice(
|
|
26
|
+
`Stopped heartbeat on ${result.updatedSessions} session(s); cancelled ${result.cancelledQueued} queued run(s), aborted ${result.abortedRunning} running run(s).`,
|
|
27
|
+
)
|
|
28
|
+
} catch (err: any) {
|
|
29
|
+
setHeartbeatBulkNotice(err?.message || 'Failed to disable heartbeat for all sessions.')
|
|
30
|
+
} finally {
|
|
31
|
+
setDisablingHeartbeats(false)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="mb-10">
|
|
37
|
+
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
38
|
+
Heartbeat
|
|
39
|
+
</h3>
|
|
40
|
+
<p className="text-[12px] text-text-3 mb-5">
|
|
41
|
+
Configure ongoing heartbeat checks for long-lived sessions.
|
|
42
|
+
</p>
|
|
43
|
+
<div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
44
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-5">
|
|
45
|
+
<div>
|
|
46
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Heartbeat Interval</label>
|
|
47
|
+
<input
|
|
48
|
+
type="text"
|
|
49
|
+
value={appSettings.heartbeatInterval ?? appSettings.heartbeatIntervalSec ?? '30m'}
|
|
50
|
+
onChange={(e) => {
|
|
51
|
+
const val = e.target.value.trim()
|
|
52
|
+
patchSettings({ heartbeatInterval: val || null })
|
|
53
|
+
}}
|
|
54
|
+
placeholder="30m"
|
|
55
|
+
className={inputClass}
|
|
56
|
+
style={{ fontFamily: 'inherit' }}
|
|
57
|
+
/>
|
|
58
|
+
<p className="text-[11px] text-text-3/60 mt-2">Duration string (e.g. 30m, 1h) or seconds. Set to 0 to disable.</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div>
|
|
61
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Heartbeat Prompt</label>
|
|
62
|
+
<input
|
|
63
|
+
type="text"
|
|
64
|
+
value={appSettings.heartbeatPrompt || ''}
|
|
65
|
+
onChange={(e) => patchSettings({ heartbeatPrompt: e.target.value || null })}
|
|
66
|
+
placeholder="Leave blank for default"
|
|
67
|
+
className={inputClass}
|
|
68
|
+
style={{ fontFamily: 'inherit' }}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-5">
|
|
74
|
+
<div>
|
|
75
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Heartbeat Model</label>
|
|
76
|
+
<input
|
|
77
|
+
type="text"
|
|
78
|
+
value={appSettings.heartbeatModel || ''}
|
|
79
|
+
onChange={(e) => patchSettings({ heartbeatModel: e.target.value || null })}
|
|
80
|
+
placeholder="Leave blank for session default"
|
|
81
|
+
className={inputClass}
|
|
82
|
+
style={{ fontFamily: 'inherit' }}
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
<div>
|
|
86
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Ack Threshold (chars)</label>
|
|
87
|
+
<input
|
|
88
|
+
type="number"
|
|
89
|
+
min={0}
|
|
90
|
+
value={appSettings.heartbeatAckMaxChars ?? 300}
|
|
91
|
+
onChange={(e) => {
|
|
92
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
93
|
+
patchSettings({ heartbeatAckMaxChars: Number.isFinite(n) ? Math.max(0, n) : 300 })
|
|
94
|
+
}}
|
|
95
|
+
className={inputClass}
|
|
96
|
+
style={{ fontFamily: 'inherit' }}
|
|
97
|
+
/>
|
|
98
|
+
<p className="text-[11px] text-text-3/60 mt-2">Responses under this length are suppressed as HEARTBEAT_OK.</p>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-5">
|
|
103
|
+
<div>
|
|
104
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Show OK Messages</label>
|
|
105
|
+
<button
|
|
106
|
+
onClick={() => patchSettings({ heartbeatShowOk: !(appSettings.heartbeatShowOk ?? false) })}
|
|
107
|
+
className={`px-3 py-2 rounded-[10px] border text-[12px] font-600 transition-colors cursor-pointer ${
|
|
108
|
+
appSettings.heartbeatShowOk
|
|
109
|
+
? 'border-emerald-400/25 bg-emerald-500/10 text-emerald-300'
|
|
110
|
+
: 'border-white/[0.08] bg-white/[0.03] text-text-3'
|
|
111
|
+
}`}
|
|
112
|
+
style={{ fontFamily: 'inherit' }}
|
|
113
|
+
>
|
|
114
|
+
{appSettings.heartbeatShowOk ? 'On' : 'Off'}
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
<div>
|
|
118
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Show Alert Messages</label>
|
|
119
|
+
<button
|
|
120
|
+
onClick={() => patchSettings({ heartbeatShowAlerts: !(appSettings.heartbeatShowAlerts ?? true) })}
|
|
121
|
+
className={`px-3 py-2 rounded-[10px] border text-[12px] font-600 transition-colors cursor-pointer ${
|
|
122
|
+
(appSettings.heartbeatShowAlerts ?? true)
|
|
123
|
+
? 'border-emerald-400/25 bg-emerald-500/10 text-emerald-300'
|
|
124
|
+
: 'border-white/[0.08] bg-white/[0.03] text-text-3'
|
|
125
|
+
}`}
|
|
126
|
+
style={{ fontFamily: 'inherit' }}
|
|
127
|
+
>
|
|
128
|
+
{(appSettings.heartbeatShowAlerts ?? true) ? 'On' : 'Off'}
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
<div>
|
|
132
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Delivery Target</label>
|
|
133
|
+
<input
|
|
134
|
+
type="text"
|
|
135
|
+
value={appSettings.heartbeatTarget || ''}
|
|
136
|
+
onChange={(e) => patchSettings({ heartbeatTarget: e.target.value || null })}
|
|
137
|
+
placeholder="none, last, or channel ID"
|
|
138
|
+
className={inputClass}
|
|
139
|
+
style={{ fontFamily: 'inherit' }}
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div>
|
|
145
|
+
<p className="text-[11px] text-text-3/60 mt-2">
|
|
146
|
+
Internal ping text used for ongoing sessions. Leave blank to use the default.
|
|
147
|
+
</p>
|
|
148
|
+
<div className="mt-4 flex items-center gap-2.5">
|
|
149
|
+
<button
|
|
150
|
+
onClick={handleDisableAllHeartbeats}
|
|
151
|
+
disabled={disablingHeartbeats}
|
|
152
|
+
className="px-3.5 py-2 rounded-[10px] border border-rose-400/25 bg-rose-500/10 text-rose-300 hover:bg-rose-500/16 transition-colors cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed text-[12px] font-600"
|
|
153
|
+
style={{ fontFamily: 'inherit' }}
|
|
154
|
+
>
|
|
155
|
+
{disablingHeartbeats ? 'Stopping\u2026' : 'Stop All Session Heartbeats'}
|
|
156
|
+
</button>
|
|
157
|
+
<span className="text-[11px] text-text-3/70">
|
|
158
|
+
Disables heartbeat on every session and cancels queued heartbeat runs.
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
{heartbeatBulkNotice && (
|
|
162
|
+
<p className="text-[11px] text-text-3/70 mt-2">{heartbeatBulkNotice}</p>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { SettingsSectionProps } from './types'
|
|
4
|
+
|
|
5
|
+
export function MemorySection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
|
|
6
|
+
return (
|
|
7
|
+
<div className="mb-10">
|
|
8
|
+
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
9
|
+
Memory Retrieval
|
|
10
|
+
</h3>
|
|
11
|
+
<p className="text-[12px] text-text-3 mb-5">
|
|
12
|
+
Guardrails for memory graph traversal and lookup fan-out. These limits are enforced server-side.
|
|
13
|
+
</p>
|
|
14
|
+
<div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
15
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
16
|
+
<div>
|
|
17
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">
|
|
18
|
+
Reference Depth
|
|
19
|
+
</label>
|
|
20
|
+
<input
|
|
21
|
+
type="number"
|
|
22
|
+
min={0}
|
|
23
|
+
max={12}
|
|
24
|
+
value={appSettings.memoryReferenceDepth ?? appSettings.memoryMaxDepth ?? 3}
|
|
25
|
+
onChange={(e) => {
|
|
26
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
27
|
+
const depth = Number.isFinite(n) ? Math.max(0, Math.min(12, n)) : 3
|
|
28
|
+
patchSettings({ memoryReferenceDepth: depth, memoryMaxDepth: depth })
|
|
29
|
+
}}
|
|
30
|
+
className={inputClass}
|
|
31
|
+
style={{ fontFamily: 'inherit' }}
|
|
32
|
+
/>
|
|
33
|
+
<p className="text-[11px] text-text-3/60 mt-2">How far linked memory traversal can go.</p>
|
|
34
|
+
</div>
|
|
35
|
+
<div>
|
|
36
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">
|
|
37
|
+
Max Per Lookup
|
|
38
|
+
</label>
|
|
39
|
+
<input
|
|
40
|
+
type="number"
|
|
41
|
+
min={1}
|
|
42
|
+
max={200}
|
|
43
|
+
value={appSettings.maxMemoriesPerLookup ?? appSettings.memoryMaxPerLookup ?? 20}
|
|
44
|
+
onChange={(e) => {
|
|
45
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
46
|
+
const perLookup = Number.isFinite(n) ? Math.max(1, Math.min(200, n)) : 20
|
|
47
|
+
patchSettings({ maxMemoriesPerLookup: perLookup, memoryMaxPerLookup: perLookup })
|
|
48
|
+
}}
|
|
49
|
+
className={inputClass}
|
|
50
|
+
style={{ fontFamily: 'inherit' }}
|
|
51
|
+
/>
|
|
52
|
+
<p className="text-[11px] text-text-3/60 mt-2">Total memories returned in one retrieval call.</p>
|
|
53
|
+
</div>
|
|
54
|
+
<div>
|
|
55
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">
|
|
56
|
+
Max Linked Expansion
|
|
57
|
+
</label>
|
|
58
|
+
<input
|
|
59
|
+
type="number"
|
|
60
|
+
min={0}
|
|
61
|
+
max={1000}
|
|
62
|
+
value={appSettings.maxLinkedMemoriesExpanded ?? 60}
|
|
63
|
+
onChange={(e) => {
|
|
64
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
65
|
+
const linked = Number.isFinite(n) ? Math.max(0, Math.min(1000, n)) : 60
|
|
66
|
+
patchSettings({ maxLinkedMemoriesExpanded: linked })
|
|
67
|
+
}}
|
|
68
|
+
className={inputClass}
|
|
69
|
+
style={{ fontFamily: 'inherit' }}
|
|
70
|
+
/>
|
|
71
|
+
<p className="text-[11px] text-text-3/60 mt-2">Caps how many linked nodes can be expanded per lookup.</p>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
4
|
+
import type { SettingsSectionProps } from './types'
|
|
5
|
+
|
|
6
|
+
const NON_LANGGRAPH_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli'])
|
|
7
|
+
|
|
8
|
+
export function OrchestratorSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
|
|
9
|
+
const providers = useAppStore((s) => s.providers)
|
|
10
|
+
const credentials = useAppStore((s) => s.credentials)
|
|
11
|
+
const credList = Object.values(credentials)
|
|
12
|
+
|
|
13
|
+
const lgProviders = providers.filter((p) => !NON_LANGGRAPH_PROVIDER_IDS.has(String(p.id)))
|
|
14
|
+
const hasConfiguredLgProvider = !!appSettings.langGraphProvider && lgProviders.some((p) => p.id === appSettings.langGraphProvider)
|
|
15
|
+
const lgProvider = hasConfiguredLgProvider ? appSettings.langGraphProvider! : (lgProviders[0]?.id || 'anthropic')
|
|
16
|
+
const lgProviderInfo = lgProviders.find((p) => p.id === lgProvider) || providers.find((p) => p.id === lgProvider)
|
|
17
|
+
const lgCredentials = credList.filter((c) => c.provider === lgProvider)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="mb-10">
|
|
21
|
+
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
22
|
+
Orchestrator Engine
|
|
23
|
+
</h3>
|
|
24
|
+
<p className="text-[12px] text-text-3 mb-5">
|
|
25
|
+
The LLM provider used by orchestrators for tool calling, agent generation, and task delegation.
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
29
|
+
{/* Provider picker */}
|
|
30
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Provider</label>
|
|
31
|
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 mb-5">
|
|
32
|
+
{lgProviders.map((p) => (
|
|
33
|
+
<button
|
|
34
|
+
key={p.id}
|
|
35
|
+
onClick={() => patchSettings({ langGraphProvider: p.id, langGraphModel: '', langGraphCredentialId: null, langGraphEndpoint: null })}
|
|
36
|
+
className={`py-3 px-3 rounded-[12px] text-center cursor-pointer transition-all text-[13px] font-600 border
|
|
37
|
+
${lgProvider === p.id
|
|
38
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
39
|
+
: 'bg-bg border-white/[0.06] text-text-2 hover:bg-surface-2'}`}
|
|
40
|
+
style={{ fontFamily: 'inherit' }}
|
|
41
|
+
>
|
|
42
|
+
{p.name}
|
|
43
|
+
</button>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
{lgProviders.length === 0 && (
|
|
47
|
+
<p className="text-[12px] text-text-3/60 mb-5">
|
|
48
|
+
No orchestration-compatible providers available. Add an API provider in Providers.
|
|
49
|
+
</p>
|
|
50
|
+
)}
|
|
51
|
+
|
|
52
|
+
{/* Model picker */}
|
|
53
|
+
{lgProviderInfo && lgProviderInfo.models.length > 0 && (
|
|
54
|
+
<div className="mb-5">
|
|
55
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Model</label>
|
|
56
|
+
<select
|
|
57
|
+
value={appSettings.langGraphModel || lgProviderInfo.models[0]}
|
|
58
|
+
onChange={(e) => patchSettings({ langGraphModel: e.target.value })}
|
|
59
|
+
className={`${inputClass} appearance-none cursor-pointer`}
|
|
60
|
+
style={{ fontFamily: 'inherit' }}
|
|
61
|
+
>
|
|
62
|
+
{lgProviderInfo.models.map((m) => (
|
|
63
|
+
<option key={m} value={m}>{m}</option>
|
|
64
|
+
))}
|
|
65
|
+
</select>
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{(lgProviderInfo?.requiresEndpoint || !!appSettings.langGraphEndpoint) && (
|
|
70
|
+
<div className="mb-5">
|
|
71
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Endpoint Override</label>
|
|
72
|
+
<input
|
|
73
|
+
type="text"
|
|
74
|
+
value={appSettings.langGraphEndpoint || ''}
|
|
75
|
+
onChange={(e) => patchSettings({ langGraphEndpoint: e.target.value || null })}
|
|
76
|
+
placeholder={lgProviderInfo?.defaultEndpoint || 'https://api.example.com/v1'}
|
|
77
|
+
className={inputClass}
|
|
78
|
+
style={{ fontFamily: 'inherit' }}
|
|
79
|
+
/>
|
|
80
|
+
<p className="text-[11px] text-text-3/60 mt-2">Leave empty to use the provider default endpoint.</p>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{/* API Key picker */}
|
|
85
|
+
<div>
|
|
86
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">API Key</label>
|
|
87
|
+
{lgCredentials.length > 0 ? (
|
|
88
|
+
<select
|
|
89
|
+
value={appSettings.langGraphCredentialId || ''}
|
|
90
|
+
onChange={(e) => patchSettings({ langGraphCredentialId: e.target.value || null })}
|
|
91
|
+
className={`${inputClass} appearance-none cursor-pointer`}
|
|
92
|
+
style={{ fontFamily: 'inherit' }}
|
|
93
|
+
>
|
|
94
|
+
<option value="">Select a key...</option>
|
|
95
|
+
{lgCredentials.map((c) => (
|
|
96
|
+
<option key={c.id} value={c.id}>{c.name}</option>
|
|
97
|
+
))}
|
|
98
|
+
</select>
|
|
99
|
+
) : (
|
|
100
|
+
<p className="text-[12px] text-text-3/60">
|
|
101
|
+
No {lgProvider} API keys configured. Add one below in the Providers section.
|
|
102
|
+
</p>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { createCredential, deleteCredential } from '@/lib/sessions'
|
|
6
|
+
import { toast } from 'sonner'
|
|
7
|
+
import type { ProviderType } from '@/types'
|
|
8
|
+
import type { SettingsSectionProps } from './types'
|
|
9
|
+
|
|
10
|
+
export function ProvidersSection({ inputClass }: SettingsSectionProps) {
|
|
11
|
+
const providers = useAppStore((s) => s.providers)
|
|
12
|
+
const credentials = useAppStore((s) => s.credentials)
|
|
13
|
+
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
14
|
+
|
|
15
|
+
const credList = Object.values(credentials)
|
|
16
|
+
|
|
17
|
+
const [addProvider, setAddProvider] = useState<ProviderType | null>(null)
|
|
18
|
+
const [newName, setNewName] = useState('')
|
|
19
|
+
const [newKey, setNewKey] = useState('')
|
|
20
|
+
const [deleting, setDeleting] = useState<string | null>(null)
|
|
21
|
+
|
|
22
|
+
const handleAdd = async () => {
|
|
23
|
+
if (!addProvider || !newKey.trim()) return
|
|
24
|
+
try {
|
|
25
|
+
await createCredential(addProvider, newName || `${addProvider} key`, newKey)
|
|
26
|
+
await loadCredentials()
|
|
27
|
+
setAddProvider(null)
|
|
28
|
+
setNewName('')
|
|
29
|
+
setNewKey('')
|
|
30
|
+
toast.success('API key added')
|
|
31
|
+
} catch (err: unknown) {
|
|
32
|
+
toast.error(err instanceof Error ? err.message : 'Failed to add API key')
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const handleDelete = async (id: string) => {
|
|
37
|
+
try {
|
|
38
|
+
await deleteCredential(id)
|
|
39
|
+
await loadCredentials()
|
|
40
|
+
setDeleting(null)
|
|
41
|
+
toast.success('API key deleted')
|
|
42
|
+
} catch (err: unknown) {
|
|
43
|
+
toast.error(err instanceof Error ? err.message : 'Failed to delete API key')
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
{/* Providers */}
|
|
50
|
+
<div className="mb-8">
|
|
51
|
+
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-5">
|
|
52
|
+
Providers
|
|
53
|
+
</h3>
|
|
54
|
+
<div className="space-y-4">
|
|
55
|
+
{providers.map((p) => {
|
|
56
|
+
const providerCreds = credList.filter((c) => c.provider === p.id)
|
|
57
|
+
return (
|
|
58
|
+
<div key={p.id} className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
59
|
+
<div className="flex items-center justify-between mb-3">
|
|
60
|
+
<span className="font-display text-[17px] font-600 tracking-[-0.01em]">{p.name}</span>
|
|
61
|
+
<span className={`text-[12px] font-600 px-3 py-1 rounded-[8px]
|
|
62
|
+
${p.requiresApiKey
|
|
63
|
+
? providerCreds.length > 0 ? 'text-success bg-success/[0.1]' : 'text-text-3 bg-white/[0.04]'
|
|
64
|
+
: 'text-success bg-success/[0.1]'}`}>
|
|
65
|
+
{p.requiresApiKey
|
|
66
|
+
? providerCreds.length > 0 ? 'Connected' : 'No key'
|
|
67
|
+
: p.optionalApiKey
|
|
68
|
+
? providerCreds.length > 0 ? 'Local + Cloud' : 'Local'
|
|
69
|
+
: p.requiresEndpoint ? 'Local' : 'Built-in'}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="text-[13px] text-text-2/50 font-mono">
|
|
73
|
+
{p.models.slice(0, 3).join(', ')}
|
|
74
|
+
{p.models.length > 3 && ` +${p.models.length - 3} more`}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{(p.requiresApiKey || p.optionalApiKey) && providerCreds.length > 0 && (
|
|
78
|
+
<div className="mt-5 space-y-2.5">
|
|
79
|
+
{providerCreds.map((cred) => (
|
|
80
|
+
<div key={cred.id} className="flex items-center gap-3 py-3 px-4 rounded-[12px] bg-bg border border-white/[0.06]">
|
|
81
|
+
<span className="text-[14px] font-500 flex-1 truncate">{cred.name}</span>
|
|
82
|
+
{deleting === cred.id ? (
|
|
83
|
+
<div className="flex gap-2">
|
|
84
|
+
<button
|
|
85
|
+
onClick={() => setDeleting(null)}
|
|
86
|
+
className="px-3 py-1.5 text-[13px] font-600 bg-transparent border-none text-text-3 cursor-pointer hover:text-text-2 transition-colors"
|
|
87
|
+
style={{ fontFamily: 'inherit' }}
|
|
88
|
+
>
|
|
89
|
+
Keep
|
|
90
|
+
</button>
|
|
91
|
+
<button
|
|
92
|
+
onClick={() => handleDelete(cred.id)}
|
|
93
|
+
className="px-3 py-1.5 text-[13px] font-600 bg-danger text-white border-none cursor-pointer rounded-[8px] transition-colors hover:brightness-110"
|
|
94
|
+
style={{ fontFamily: 'inherit' }}
|
|
95
|
+
>
|
|
96
|
+
Delete
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
) : (
|
|
100
|
+
<button
|
|
101
|
+
onClick={() => setDeleting(cred.id)}
|
|
102
|
+
className="px-3 py-1.5 text-[13px] font-500 bg-transparent border-none text-text-3 cursor-pointer hover:text-danger transition-colors"
|
|
103
|
+
style={{ fontFamily: 'inherit' }}
|
|
104
|
+
>
|
|
105
|
+
Remove
|
|
106
|
+
</button>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
{(p.requiresApiKey || p.optionalApiKey) && (
|
|
114
|
+
<button
|
|
115
|
+
onClick={() => setAddProvider(p.id)}
|
|
116
|
+
className="mt-5 w-full py-3 rounded-[12px] border border-dashed border-white/[0.1]
|
|
117
|
+
bg-transparent text-text-3 text-[13px] font-600 cursor-pointer
|
|
118
|
+
hover:border-accent-bright/30 hover:text-accent-bright hover:bg-accent-soft transition-all duration-200"
|
|
119
|
+
style={{ fontFamily: 'inherit' }}
|
|
120
|
+
>
|
|
121
|
+
+ Add API Key{p.optionalApiKey && !p.requiresApiKey ? ' (for cloud)' : ''}
|
|
122
|
+
</button>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{p.requiresEndpoint && (
|
|
126
|
+
<div className="mt-5 text-[13px] text-text-3/50 font-mono">
|
|
127
|
+
Endpoint: {(p as any).defaultEndpoint || 'http://localhost:11434'}
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
})}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Add key form */}
|
|
137
|
+
{addProvider && (
|
|
138
|
+
<div className="mb-8 p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
139
|
+
<div className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-4">
|
|
140
|
+
New {providers.find((p) => p.id === addProvider)?.name} API Key
|
|
141
|
+
</div>
|
|
142
|
+
<div className="space-y-4">
|
|
143
|
+
<input
|
|
144
|
+
type="text"
|
|
145
|
+
value={newName}
|
|
146
|
+
onChange={(e) => setNewName(e.target.value)}
|
|
147
|
+
placeholder="Key name (optional)"
|
|
148
|
+
className={inputClass}
|
|
149
|
+
style={{ fontFamily: 'inherit' }}
|
|
150
|
+
/>
|
|
151
|
+
<input
|
|
152
|
+
type="password"
|
|
153
|
+
value={newKey}
|
|
154
|
+
onChange={(e) => setNewKey(e.target.value)}
|
|
155
|
+
placeholder="sk-..."
|
|
156
|
+
className={inputClass}
|
|
157
|
+
style={{ fontFamily: 'inherit' }}
|
|
158
|
+
/>
|
|
159
|
+
<div className="flex gap-3 pt-2">
|
|
160
|
+
<button
|
|
161
|
+
onClick={() => { setAddProvider(null); setNewName(''); setNewKey('') }}
|
|
162
|
+
className="flex-1 py-3 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px] font-600 cursor-pointer hover:bg-surface-2 transition-colors"
|
|
163
|
+
style={{ fontFamily: 'inherit' }}
|
|
164
|
+
>
|
|
165
|
+
Cancel
|
|
166
|
+
</button>
|
|
167
|
+
<button
|
|
168
|
+
onClick={handleAdd}
|
|
169
|
+
disabled={!newKey.trim()}
|
|
170
|
+
className="flex-1 py-3 rounded-[14px] border-none bg-[#6366F1] text-white text-[14px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
|
|
171
|
+
style={{ fontFamily: 'inherit' }}
|
|
172
|
+
>
|
|
173
|
+
Save Key
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</>
|
|
180
|
+
)
|
|
181
|
+
}
|