@harbinger-ai/harbinger 0.1.3 → 0.1.5
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/lib/bounty/actions.js +45 -0
- package/lib/chat/actions.js +501 -20
- package/lib/chat/components/agents-page.js +942 -159
- package/lib/chat/components/agents-page.jsx +907 -233
- package/lib/chat/components/icons.js +105 -0
- package/lib/chat/components/icons.jsx +129 -0
- package/lib/chat/components/page-layout.js +41 -2
- package/lib/chat/components/page-layout.jsx +40 -2
- package/lib/chat/components/settings-providers-page.js +647 -112
- package/lib/chat/components/settings-providers-page.jsx +641 -134
- package/lib/chat/components/targets-page.js +554 -96
- package/lib/chat/components/targets-page.jsx +464 -114
- package/package.json +1 -1
|
@@ -1,65 +1,121 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
4
4
|
import { motion, AnimatePresence } from "framer-motion";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CpuIcon,
|
|
7
|
+
CheckIcon,
|
|
8
|
+
SpinnerIcon,
|
|
9
|
+
EyeIcon,
|
|
10
|
+
EyeOffIcon,
|
|
11
|
+
XIcon,
|
|
12
|
+
RefreshIcon,
|
|
13
|
+
WifiIcon,
|
|
14
|
+
ServerIcon,
|
|
15
|
+
ScanIcon,
|
|
16
|
+
GlobeIcon,
|
|
17
|
+
ZapIcon
|
|
18
|
+
} from "./icons.js";
|
|
6
19
|
import {
|
|
7
20
|
getLlmProviders,
|
|
8
21
|
saveLlmProvider,
|
|
9
22
|
setActiveProvider,
|
|
10
23
|
testLlmConnection,
|
|
11
|
-
getActiveProvider
|
|
24
|
+
getActiveProvider,
|
|
25
|
+
scanLocalProviders,
|
|
26
|
+
getLocalModels
|
|
12
27
|
} from "../actions.js";
|
|
13
|
-
const
|
|
14
|
-
anthropic:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
const CLOUD_PROVIDERS = {
|
|
29
|
+
anthropic: {
|
|
30
|
+
name: "Anthropic",
|
|
31
|
+
icon: "A",
|
|
32
|
+
color: "#d4a574",
|
|
33
|
+
keyPrefix: "sk-ant-",
|
|
34
|
+
keyEnv: "ANTHROPIC_API_KEY",
|
|
35
|
+
models: [
|
|
36
|
+
"claude-sonnet-4-20250514",
|
|
37
|
+
"claude-opus-4-20250514",
|
|
38
|
+
"claude-haiku-4-5-20251001",
|
|
39
|
+
"claude-3-5-sonnet-20241022"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
openai: {
|
|
43
|
+
name: "OpenAI",
|
|
44
|
+
icon: "O",
|
|
45
|
+
color: "#74aa9c",
|
|
46
|
+
keyPrefix: "sk-",
|
|
47
|
+
keyEnv: "OPENAI_API_KEY",
|
|
48
|
+
models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "o1", "o1-mini", "o3-mini"]
|
|
49
|
+
},
|
|
50
|
+
google: {
|
|
51
|
+
name: "Google",
|
|
52
|
+
icon: "G",
|
|
53
|
+
color: "#4285f4",
|
|
54
|
+
keyPrefix: "AI",
|
|
55
|
+
keyEnv: "GOOGLE_API_KEY",
|
|
56
|
+
models: ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash"]
|
|
57
|
+
}
|
|
18
58
|
};
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
59
|
+
const LOCAL_PROVIDERS = {
|
|
60
|
+
ollama: { name: "Ollama", icon: "\u{1F999}", defaultUrl: "http://localhost:11434" },
|
|
61
|
+
lmstudio: { name: "LM Studio", icon: "\u{1F52C}", defaultUrl: "http://localhost:1234/v1" },
|
|
62
|
+
localai: { name: "LocalAI", icon: "\u{1F916}", defaultUrl: "http://localhost:8080/v1" },
|
|
63
|
+
llamacpp: { name: "llama.cpp", icon: "\u{1F527}", defaultUrl: "http://localhost:8081" },
|
|
64
|
+
vllm: { name: "vLLM", icon: "\u26A1", defaultUrl: "http://localhost:8000/v1" },
|
|
65
|
+
jan: { name: "Jan", icon: "\u{1F310}", defaultUrl: "http://localhost:1337/v1" }
|
|
24
66
|
};
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
67
|
+
function TrafficLights({ label }) {
|
|
68
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
70
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
71
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
72
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
73
|
+
] }),
|
|
74
|
+
label && /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider ml-1", children: label })
|
|
75
|
+
] });
|
|
76
|
+
}
|
|
77
|
+
function ProviderCard({ id, meta, config, isActive, isLocal, detected, onEdit, index }) {
|
|
78
|
+
const hasKey = !isLocal && (config?.hasKey || !!config?.apiKey);
|
|
79
|
+
const isDetected = isLocal && detected;
|
|
28
80
|
return /* @__PURE__ */ jsx(
|
|
29
81
|
motion.div,
|
|
30
82
|
{
|
|
31
83
|
initial: { opacity: 0, y: 8 },
|
|
32
84
|
animate: { opacity: 1, y: 0 },
|
|
33
|
-
transition: { duration: 0.25, delay: index * 0.
|
|
85
|
+
transition: { duration: 0.25, delay: index * 0.04 },
|
|
34
86
|
className: `rounded-lg border bg-[--card] transition-all cursor-pointer hover:border-[--cyan]/20 ${isActive ? "border-[--cyan]/30 shadow-[0_0_15px_oklch(0.7_0.17_195/8%)]" : "border-white/[0.06]"}`,
|
|
35
87
|
onClick: onEdit,
|
|
36
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-
|
|
37
|
-
/* @__PURE__ */
|
|
38
|
-
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
39
|
-
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
40
|
-
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
41
|
-
] }),
|
|
42
|
-
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-[--cyan]/10 p-2", children: /* @__PURE__ */ jsx(CpuIcon, { size: 16, className: "text-[--cyan]" }) }),
|
|
88
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3.5", children: [
|
|
89
|
+
/* @__PURE__ */ jsx("div", { className: `shrink-0 w-9 h-9 rounded-md flex items-center justify-center text-sm font-bold ${isLocal ? "bg-emerald-500/10 text-emerald-400" : "bg-[--cyan]/10 text-[--cyan]"}`, children: isLocal ? /* @__PURE__ */ jsx("span", { className: "text-base", children: meta.icon }) : /* @__PURE__ */ jsx("span", { className: "font-mono", children: meta.icon }) }),
|
|
43
90
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
44
|
-
/* @__PURE__ */
|
|
45
|
-
|
|
91
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
92
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium", children: meta.name }),
|
|
93
|
+
isLocal && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 px-1.5 py-0 text-[9px] font-mono", children: "local" })
|
|
94
|
+
] }),
|
|
95
|
+
config?.model && /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono text-muted-foreground mt-0.5 truncate", children: config.model }),
|
|
96
|
+
isLocal && detected?.models?.length > 0 && !config?.model && /* @__PURE__ */ jsxs("p", { className: "text-[10px] font-mono text-muted-foreground mt-0.5", children: [
|
|
97
|
+
detected.models.length,
|
|
98
|
+
" model(s) available"
|
|
99
|
+
] })
|
|
46
100
|
] }),
|
|
47
101
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
48
|
-
/* @__PURE__ */
|
|
49
|
-
|
|
102
|
+
isLocal ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
103
|
+
/* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${isDetected ? "bg-emerald-400 animate-pulse" : "bg-muted-foreground/40"}` }),
|
|
104
|
+
/* @__PURE__ */ jsx("span", { className: `text-[10px] font-mono ${isDetected ? "text-emerald-400" : "text-muted-foreground"}`, children: isDetected ? "running" : "not found" })
|
|
105
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
106
|
+
/* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${hasKey ? "bg-[--success]" : "bg-muted-foreground/40"}` }),
|
|
107
|
+
/* @__PURE__ */ jsx("span", { className: `text-[10px] font-mono ${hasKey ? "text-[--success]" : "text-muted-foreground"}`, children: hasKey ? "configured" : "no key" })
|
|
108
|
+
] }),
|
|
50
109
|
isActive && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 px-2 py-0.5 text-[10px] font-mono font-medium", children: "active" })
|
|
51
110
|
] })
|
|
52
111
|
] })
|
|
53
112
|
}
|
|
54
113
|
);
|
|
55
114
|
}
|
|
56
|
-
function
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const [apiKey, setApiKey] = useState(config?.apiKey || "");
|
|
60
|
-
const [model, setModel] = useState(config?.model || models[0] || "");
|
|
115
|
+
function CloudSetupForm({ provider, meta, config, isActive, onSave, onSetActive, onClose }) {
|
|
116
|
+
const [apiKey, setApiKey] = useState("");
|
|
117
|
+
const [model, setModel] = useState(config?.model || meta.models[0] || "");
|
|
61
118
|
const [maxTokens, setMaxTokens] = useState(config?.maxTokens || 4096);
|
|
62
|
-
const [baseUrl, setBaseUrl] = useState(config?.baseUrl || "");
|
|
63
119
|
const [showKey, setShowKey] = useState(false);
|
|
64
120
|
const [saving, setSaving] = useState(false);
|
|
65
121
|
const [testing, setTesting] = useState(false);
|
|
@@ -67,7 +123,11 @@ function ProviderSetupForm({ provider, config, isActive, onSave, onSetActive, on
|
|
|
67
123
|
async function handleSave() {
|
|
68
124
|
setSaving(true);
|
|
69
125
|
try {
|
|
70
|
-
|
|
126
|
+
const saveConfig = { model, maxTokens: parseInt(maxTokens) };
|
|
127
|
+
if (apiKey && !apiKey.includes("***")) {
|
|
128
|
+
saveConfig.apiKey = apiKey;
|
|
129
|
+
}
|
|
130
|
+
await onSave(provider, saveConfig);
|
|
71
131
|
} finally {
|
|
72
132
|
setSaving(false);
|
|
73
133
|
}
|
|
@@ -84,10 +144,6 @@ function ProviderSetupForm({ provider, config, isActive, onSave, onSetActive, on
|
|
|
84
144
|
setTesting(false);
|
|
85
145
|
}
|
|
86
146
|
}
|
|
87
|
-
async function handleSetActive() {
|
|
88
|
-
await onSetActive(provider, model);
|
|
89
|
-
}
|
|
90
|
-
const maskedKey = apiKey ? apiKey.slice(0, 8) + "\u2022".repeat(Math.max(0, apiKey.length - 12)) + apiKey.slice(-4) : "";
|
|
91
147
|
return /* @__PURE__ */ jsx(
|
|
92
148
|
motion.div,
|
|
93
149
|
{
|
|
@@ -96,19 +152,9 @@ function ProviderSetupForm({ provider, config, isActive, onSave, onSetActive, on
|
|
|
96
152
|
exit: { opacity: 0, height: 0 },
|
|
97
153
|
transition: { duration: 0.2 },
|
|
98
154
|
className: "overflow-hidden",
|
|
99
|
-
children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-[--cyan]/20 bg-[--card] p-5 mt-
|
|
155
|
+
children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-[--cyan]/20 bg-[--card] p-5 mt-2", children: [
|
|
100
156
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
|
|
101
|
-
/* @__PURE__ */
|
|
102
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
103
|
-
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
104
|
-
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
105
|
-
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
106
|
-
] }),
|
|
107
|
-
/* @__PURE__ */ jsxs("span", { className: "font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider", children: [
|
|
108
|
-
meta.name,
|
|
109
|
-
" Setup"
|
|
110
|
-
] })
|
|
111
|
-
] }),
|
|
157
|
+
/* @__PURE__ */ jsx(TrafficLights, { label: `${meta.name} Setup` }),
|
|
112
158
|
/* @__PURE__ */ jsx("button", { onClick: onClose, className: "text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(XIcon, { size: 14 }) })
|
|
113
159
|
] }),
|
|
114
160
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
@@ -122,7 +168,7 @@ function ProviderSetupForm({ provider, config, isActive, onSave, onSetActive, on
|
|
|
122
168
|
type: showKey ? "text" : "password",
|
|
123
169
|
value: apiKey,
|
|
124
170
|
onChange: (e) => setApiKey(e.target.value),
|
|
125
|
-
placeholder:
|
|
171
|
+
placeholder: config?.hasKey ? "(saved \u2014 enter new key to change)" : `${meta.keyPrefix}...`,
|
|
126
172
|
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors"
|
|
127
173
|
}
|
|
128
174
|
),
|
|
@@ -136,38 +182,230 @@ function ProviderSetupForm({ provider, config, isActive, onSave, onSetActive, on
|
|
|
136
182
|
}
|
|
137
183
|
)
|
|
138
184
|
] }),
|
|
139
|
-
/* @__PURE__ */
|
|
185
|
+
/* @__PURE__ */ jsxs(
|
|
140
186
|
"button",
|
|
141
187
|
{
|
|
142
188
|
onClick: handleTest,
|
|
143
|
-
disabled: testing || !apiKey,
|
|
189
|
+
disabled: testing || !apiKey && !config?.hasKey,
|
|
144
190
|
className: "inline-flex items-center gap-1.5 rounded-md px-3 py-2 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors disabled:opacity-50",
|
|
145
|
-
children:
|
|
191
|
+
children: [
|
|
192
|
+
testing ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(ZapIcon, { size: 12 }),
|
|
193
|
+
"Test"
|
|
194
|
+
]
|
|
146
195
|
}
|
|
147
196
|
)
|
|
197
|
+
] }),
|
|
198
|
+
config?.hasKey && /* @__PURE__ */ jsxs("p", { className: "text-[10px] font-mono text-muted-foreground/60 mt-1", children: [
|
|
199
|
+
"Key saved: ",
|
|
200
|
+
config.apiKey
|
|
148
201
|
] })
|
|
149
202
|
] }),
|
|
150
203
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
151
204
|
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Model" }),
|
|
152
|
-
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
153
206
|
"select",
|
|
154
207
|
{
|
|
155
208
|
value: model,
|
|
156
209
|
onChange: (e) => setModel(e.target.value),
|
|
157
210
|
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors appearance-none",
|
|
158
|
-
children: models.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: m }, m))
|
|
211
|
+
children: meta.models.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: m }, m))
|
|
159
212
|
}
|
|
160
|
-
)
|
|
213
|
+
)
|
|
214
|
+
] }),
|
|
215
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
216
|
+
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Max Tokens" }),
|
|
217
|
+
/* @__PURE__ */ jsx(
|
|
218
|
+
"input",
|
|
219
|
+
{
|
|
220
|
+
type: "number",
|
|
221
|
+
value: maxTokens,
|
|
222
|
+
onChange: (e) => setMaxTokens(e.target.value),
|
|
223
|
+
className: "w-32 text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors"
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
] }),
|
|
227
|
+
/* @__PURE__ */ jsx(TestResultBox, { result: testResult }),
|
|
228
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2", children: [
|
|
229
|
+
/* @__PURE__ */ jsxs(
|
|
230
|
+
"button",
|
|
231
|
+
{
|
|
232
|
+
onClick: handleSave,
|
|
233
|
+
disabled: saving,
|
|
234
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 hover:bg-[--cyan] hover:text-[--primary-foreground] transition-colors disabled:opacity-50",
|
|
235
|
+
children: [
|
|
236
|
+
saving ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(CheckIcon, { size: 12 }),
|
|
237
|
+
"Save"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
),
|
|
241
|
+
!isActive && (config?.hasKey || apiKey) && /* @__PURE__ */ jsx(
|
|
242
|
+
"button",
|
|
243
|
+
{
|
|
244
|
+
onClick: () => onSetActive(provider, model),
|
|
245
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors",
|
|
246
|
+
children: "Set as Active"
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
/* @__PURE__ */ jsx(
|
|
250
|
+
"button",
|
|
251
|
+
{
|
|
252
|
+
onClick: onClose,
|
|
253
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] transition-colors text-muted-foreground",
|
|
254
|
+
children: "Cancel"
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
] })
|
|
258
|
+
] })
|
|
259
|
+
] })
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
function LocalSetupForm({ provider, meta, config, detected, isActive, onSave, onSetActive, onClose }) {
|
|
264
|
+
const detectedModels = detected?.models || [];
|
|
265
|
+
const [model, setModel] = useState(config?.model || detectedModels[0]?.id || "");
|
|
266
|
+
const [customModel, setCustomModel] = useState("");
|
|
267
|
+
const [baseUrl, setBaseUrl] = useState(config?.baseUrl || detected?.baseUrl || meta.defaultUrl || "");
|
|
268
|
+
const [maxTokens, setMaxTokens] = useState(config?.maxTokens || 4096);
|
|
269
|
+
const [saving, setSaving] = useState(false);
|
|
270
|
+
const [testing, setTesting] = useState(false);
|
|
271
|
+
const [testResult, setTestResult] = useState(null);
|
|
272
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
273
|
+
const [models, setModels] = useState(detectedModels);
|
|
274
|
+
async function handleRefreshModels() {
|
|
275
|
+
setRefreshing(true);
|
|
276
|
+
try {
|
|
277
|
+
const fetched = await getLocalModels(baseUrl);
|
|
278
|
+
setModels(fetched);
|
|
279
|
+
if (fetched.length > 0 && !model) setModel(fetched[0].id);
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
setRefreshing(false);
|
|
283
|
+
}
|
|
284
|
+
async function handleSave() {
|
|
285
|
+
setSaving(true);
|
|
286
|
+
try {
|
|
287
|
+
const selectedModel = customModel || model;
|
|
288
|
+
await onSave(provider, {
|
|
289
|
+
model: selectedModel,
|
|
290
|
+
maxTokens: parseInt(maxTokens),
|
|
291
|
+
baseUrl,
|
|
292
|
+
apiKey: "not-needed"
|
|
293
|
+
});
|
|
294
|
+
} finally {
|
|
295
|
+
setSaving(false);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async function handleTest() {
|
|
299
|
+
setTesting(true);
|
|
300
|
+
setTestResult(null);
|
|
301
|
+
try {
|
|
302
|
+
const result = await testLlmConnection(provider);
|
|
303
|
+
setTestResult(result);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
setTestResult({ error: err.message });
|
|
306
|
+
} finally {
|
|
307
|
+
setTesting(false);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return /* @__PURE__ */ jsx(
|
|
311
|
+
motion.div,
|
|
312
|
+
{
|
|
313
|
+
initial: { opacity: 0, height: 0 },
|
|
314
|
+
animate: { opacity: 1, height: "auto" },
|
|
315
|
+
exit: { opacity: 0, height: 0 },
|
|
316
|
+
transition: { duration: 0.2 },
|
|
317
|
+
className: "overflow-hidden",
|
|
318
|
+
children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-emerald-500/20 bg-[--card] p-5 mt-2", children: [
|
|
319
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
|
|
320
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
321
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
322
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
323
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
324
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
325
|
+
] }),
|
|
326
|
+
/* @__PURE__ */ jsxs("span", { className: "font-mono text-[10px] font-medium text-emerald-400 uppercase tracking-wider ml-1", children: [
|
|
327
|
+
meta.name,
|
|
328
|
+
" Setup"
|
|
329
|
+
] }),
|
|
330
|
+
detected && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 px-1.5 py-0 text-[9px] font-mono", children: "detected" })
|
|
331
|
+
] }),
|
|
332
|
+
/* @__PURE__ */ jsx("button", { onClick: onClose, className: "text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(XIcon, { size: 14 }) })
|
|
333
|
+
] }),
|
|
334
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
335
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
336
|
+
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Base URL" }),
|
|
337
|
+
/* @__PURE__ */ jsx(
|
|
161
338
|
"input",
|
|
162
339
|
{
|
|
163
340
|
type: "text",
|
|
164
|
-
value:
|
|
165
|
-
onChange: (e) =>
|
|
166
|
-
placeholder:
|
|
167
|
-
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-
|
|
341
|
+
value: baseUrl,
|
|
342
|
+
onChange: (e) => setBaseUrl(e.target.value),
|
|
343
|
+
placeholder: meta.defaultUrl,
|
|
344
|
+
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-emerald-500/40 focus:ring-1 focus:ring-emerald-500/20 transition-colors"
|
|
168
345
|
}
|
|
169
346
|
)
|
|
170
347
|
] }),
|
|
348
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
349
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
350
|
+
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider", children: "Model" }),
|
|
351
|
+
/* @__PURE__ */ jsxs(
|
|
352
|
+
"button",
|
|
353
|
+
{
|
|
354
|
+
onClick: handleRefreshModels,
|
|
355
|
+
disabled: refreshing,
|
|
356
|
+
className: "inline-flex items-center gap-1 text-[10px] font-mono text-muted-foreground hover:text-emerald-400 transition-colors disabled:opacity-50",
|
|
357
|
+
children: [
|
|
358
|
+
refreshing ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 10 }) : /* @__PURE__ */ jsx(RefreshIcon, { size: 10 }),
|
|
359
|
+
"Scan Models"
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
] }),
|
|
364
|
+
models.length > 0 ? /* @__PURE__ */ jsx(
|
|
365
|
+
"select",
|
|
366
|
+
{
|
|
367
|
+
value: model,
|
|
368
|
+
onChange: (e) => {
|
|
369
|
+
setModel(e.target.value);
|
|
370
|
+
setCustomModel("");
|
|
371
|
+
},
|
|
372
|
+
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 focus:outline-none focus:border-emerald-500/40 focus:ring-1 focus:ring-emerald-500/20 transition-colors appearance-none",
|
|
373
|
+
children: models.map((m) => /* @__PURE__ */ jsxs("option", { value: m.id, children: [
|
|
374
|
+
m.name,
|
|
375
|
+
m.paramSize ? ` (${m.paramSize})` : "",
|
|
376
|
+
m.family ? ` \u2014 ${m.family}` : ""
|
|
377
|
+
] }, m.id))
|
|
378
|
+
}
|
|
379
|
+
) : /* @__PURE__ */ jsx(
|
|
380
|
+
"input",
|
|
381
|
+
{
|
|
382
|
+
type: "text",
|
|
383
|
+
value: customModel,
|
|
384
|
+
onChange: (e) => setCustomModel(e.target.value),
|
|
385
|
+
placeholder: "model-name (or click Scan Models)",
|
|
386
|
+
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-emerald-500/40 focus:ring-1 focus:ring-emerald-500/20 transition-colors"
|
|
387
|
+
}
|
|
388
|
+
),
|
|
389
|
+
models.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1.5 mt-2", children: [
|
|
390
|
+
models.slice(0, 8).map((m) => /* @__PURE__ */ jsx(
|
|
391
|
+
"button",
|
|
392
|
+
{
|
|
393
|
+
onClick: () => {
|
|
394
|
+
setModel(m.id);
|
|
395
|
+
setCustomModel("");
|
|
396
|
+
},
|
|
397
|
+
className: `inline-flex items-center rounded-full px-2 py-0.5 text-[9px] font-mono border transition-colors ${model === m.id ? "bg-emerald-500/15 text-emerald-400 border-emerald-500/30" : "bg-white/[0.02] text-muted-foreground border-white/[0.06] hover:border-emerald-500/20"}`,
|
|
398
|
+
children: m.name
|
|
399
|
+
},
|
|
400
|
+
m.id
|
|
401
|
+
)),
|
|
402
|
+
models.length > 8 && /* @__PURE__ */ jsxs("span", { className: "text-[9px] font-mono text-muted-foreground self-center", children: [
|
|
403
|
+
"+",
|
|
404
|
+
models.length - 8,
|
|
405
|
+
" more"
|
|
406
|
+
] })
|
|
407
|
+
] })
|
|
408
|
+
] }),
|
|
171
409
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
172
410
|
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Max Tokens" }),
|
|
173
411
|
/* @__PURE__ */ jsx(
|
|
@@ -176,11 +414,111 @@ function ProviderSetupForm({ provider, config, isActive, onSave, onSetActive, on
|
|
|
176
414
|
type: "number",
|
|
177
415
|
value: maxTokens,
|
|
178
416
|
onChange: (e) => setMaxTokens(e.target.value),
|
|
179
|
-
className: "w-32 text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 focus:outline-none focus:border-
|
|
417
|
+
className: "w-32 text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 focus:outline-none focus:border-emerald-500/40 focus:ring-1 focus:ring-emerald-500/20 transition-colors"
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
] }),
|
|
421
|
+
/* @__PURE__ */ jsx(TestResultBox, { result: testResult }),
|
|
422
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2", children: [
|
|
423
|
+
/* @__PURE__ */ jsxs(
|
|
424
|
+
"button",
|
|
425
|
+
{
|
|
426
|
+
onClick: handleSave,
|
|
427
|
+
disabled: saving || !model && !customModel,
|
|
428
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 hover:bg-emerald-500 hover:text-white transition-colors disabled:opacity-50",
|
|
429
|
+
children: [
|
|
430
|
+
saving ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(CheckIcon, { size: 12 }),
|
|
431
|
+
"Save"
|
|
432
|
+
]
|
|
433
|
+
}
|
|
434
|
+
),
|
|
435
|
+
/* @__PURE__ */ jsxs(
|
|
436
|
+
"button",
|
|
437
|
+
{
|
|
438
|
+
onClick: handleTest,
|
|
439
|
+
disabled: testing,
|
|
440
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-3 py-2 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-emerald-500/30 hover:text-emerald-400 transition-colors disabled:opacity-50",
|
|
441
|
+
children: [
|
|
442
|
+
testing ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(ZapIcon, { size: 12 }),
|
|
443
|
+
"Test"
|
|
444
|
+
]
|
|
445
|
+
}
|
|
446
|
+
),
|
|
447
|
+
!isActive && (model || customModel) && /* @__PURE__ */ jsx(
|
|
448
|
+
"button",
|
|
449
|
+
{
|
|
450
|
+
onClick: () => onSetActive(provider, customModel || model),
|
|
451
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors",
|
|
452
|
+
children: "Set as Active"
|
|
453
|
+
}
|
|
454
|
+
),
|
|
455
|
+
/* @__PURE__ */ jsx(
|
|
456
|
+
"button",
|
|
457
|
+
{
|
|
458
|
+
onClick: onClose,
|
|
459
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] transition-colors text-muted-foreground",
|
|
460
|
+
children: "Cancel"
|
|
180
461
|
}
|
|
181
462
|
)
|
|
463
|
+
] })
|
|
464
|
+
] })
|
|
465
|
+
] })
|
|
466
|
+
}
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
function CustomSetupForm({ config, isActive, onSave, onSetActive, onClose }) {
|
|
470
|
+
const [apiKey, setApiKey] = useState("");
|
|
471
|
+
const [model, setModel] = useState(config?.model || "");
|
|
472
|
+
const [baseUrl, setBaseUrl] = useState(config?.baseUrl || "");
|
|
473
|
+
const [maxTokens, setMaxTokens] = useState(config?.maxTokens || 4096);
|
|
474
|
+
const [showKey, setShowKey] = useState(false);
|
|
475
|
+
const [saving, setSaving] = useState(false);
|
|
476
|
+
const [testing, setTesting] = useState(false);
|
|
477
|
+
const [testResult, setTestResult] = useState(null);
|
|
478
|
+
async function handleSave() {
|
|
479
|
+
setSaving(true);
|
|
480
|
+
try {
|
|
481
|
+
const saveConfig = { model, maxTokens: parseInt(maxTokens), baseUrl };
|
|
482
|
+
if (apiKey && !apiKey.includes("***")) saveConfig.apiKey = apiKey;
|
|
483
|
+
await onSave("custom", saveConfig);
|
|
484
|
+
} finally {
|
|
485
|
+
setSaving(false);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async function handleTest() {
|
|
489
|
+
setTesting(true);
|
|
490
|
+
setTestResult(null);
|
|
491
|
+
try {
|
|
492
|
+
const result = await testLlmConnection("custom");
|
|
493
|
+
setTestResult(result);
|
|
494
|
+
} catch (err) {
|
|
495
|
+
setTestResult({ error: err.message });
|
|
496
|
+
} finally {
|
|
497
|
+
setTesting(false);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return /* @__PURE__ */ jsx(
|
|
501
|
+
motion.div,
|
|
502
|
+
{
|
|
503
|
+
initial: { opacity: 0, height: 0 },
|
|
504
|
+
animate: { opacity: 1, height: "auto" },
|
|
505
|
+
exit: { opacity: 0, height: 0 },
|
|
506
|
+
transition: { duration: 0.2 },
|
|
507
|
+
className: "overflow-hidden",
|
|
508
|
+
children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-purple-500/20 bg-[--card] p-5 mt-2", children: [
|
|
509
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
|
|
510
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
511
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
512
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
513
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
514
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
515
|
+
] }),
|
|
516
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-purple-400 uppercase tracking-wider ml-1", children: "Custom Provider (OpenAI-Compatible)" })
|
|
182
517
|
] }),
|
|
183
|
-
|
|
518
|
+
/* @__PURE__ */ jsx("button", { onClick: onClose, className: "text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(XIcon, { size: 14 }) })
|
|
519
|
+
] }),
|
|
520
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
521
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
184
522
|
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Base URL" }),
|
|
185
523
|
/* @__PURE__ */ jsx(
|
|
186
524
|
"input",
|
|
@@ -189,39 +527,89 @@ function ProviderSetupForm({ provider, config, isActive, onSave, onSetActive, on
|
|
|
189
527
|
value: baseUrl,
|
|
190
528
|
onChange: (e) => setBaseUrl(e.target.value),
|
|
191
529
|
placeholder: "https://api.example.com/v1",
|
|
192
|
-
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-
|
|
530
|
+
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-purple-500/40 focus:ring-1 focus:ring-purple-500/20 transition-colors"
|
|
193
531
|
}
|
|
194
532
|
)
|
|
195
533
|
] }),
|
|
196
|
-
|
|
197
|
-
/* @__PURE__ */
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
534
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
535
|
+
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "API Key" }),
|
|
536
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 relative", children: [
|
|
537
|
+
/* @__PURE__ */ jsx(
|
|
538
|
+
"input",
|
|
539
|
+
{
|
|
540
|
+
type: showKey ? "text" : "password",
|
|
541
|
+
value: apiKey,
|
|
542
|
+
onChange: (e) => setApiKey(e.target.value),
|
|
543
|
+
placeholder: config?.hasKey ? "(saved)" : "sk-... (or leave empty if not needed)",
|
|
544
|
+
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-purple-500/40 focus:ring-1 focus:ring-purple-500/20 transition-colors"
|
|
545
|
+
}
|
|
546
|
+
),
|
|
547
|
+
/* @__PURE__ */ jsx(
|
|
548
|
+
"button",
|
|
549
|
+
{
|
|
550
|
+
type: "button",
|
|
551
|
+
onClick: () => setShowKey(!showKey),
|
|
552
|
+
className: "absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors",
|
|
553
|
+
children: showKey ? /* @__PURE__ */ jsx(EyeOffIcon, { size: 14 }) : /* @__PURE__ */ jsx(EyeIcon, { size: 14 })
|
|
554
|
+
}
|
|
555
|
+
)
|
|
556
|
+
] })
|
|
207
557
|
] }),
|
|
558
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
559
|
+
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Model" }),
|
|
560
|
+
/* @__PURE__ */ jsx(
|
|
561
|
+
"input",
|
|
562
|
+
{
|
|
563
|
+
type: "text",
|
|
564
|
+
value: model,
|
|
565
|
+
onChange: (e) => setModel(e.target.value),
|
|
566
|
+
placeholder: "model-name",
|
|
567
|
+
className: "w-full text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-purple-500/40 focus:ring-1 focus:ring-purple-500/20 transition-colors"
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
] }),
|
|
571
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
572
|
+
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Max Tokens" }),
|
|
573
|
+
/* @__PURE__ */ jsx(
|
|
574
|
+
"input",
|
|
575
|
+
{
|
|
576
|
+
type: "number",
|
|
577
|
+
value: maxTokens,
|
|
578
|
+
onChange: (e) => setMaxTokens(e.target.value),
|
|
579
|
+
className: "w-32 text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 focus:outline-none focus:border-purple-500/40 focus:ring-1 focus:ring-purple-500/20 transition-colors"
|
|
580
|
+
}
|
|
581
|
+
)
|
|
582
|
+
] }),
|
|
583
|
+
/* @__PURE__ */ jsx(TestResultBox, { result: testResult }),
|
|
208
584
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2", children: [
|
|
209
585
|
/* @__PURE__ */ jsxs(
|
|
210
586
|
"button",
|
|
211
587
|
{
|
|
212
588
|
onClick: handleSave,
|
|
213
|
-
disabled: saving
|
|
214
|
-
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium bg-
|
|
589
|
+
disabled: saving,
|
|
590
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20 hover:bg-purple-500 hover:text-white transition-colors disabled:opacity-50",
|
|
215
591
|
children: [
|
|
216
592
|
saving ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(CheckIcon, { size: 12 }),
|
|
217
|
-
"Save"
|
|
593
|
+
" Save"
|
|
594
|
+
]
|
|
595
|
+
}
|
|
596
|
+
),
|
|
597
|
+
/* @__PURE__ */ jsxs(
|
|
598
|
+
"button",
|
|
599
|
+
{
|
|
600
|
+
onClick: handleTest,
|
|
601
|
+
disabled: testing,
|
|
602
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-3 py-2 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-purple-500/30 hover:text-purple-400 transition-colors disabled:opacity-50",
|
|
603
|
+
children: [
|
|
604
|
+
testing ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(ZapIcon, { size: 12 }),
|
|
605
|
+
" Test"
|
|
218
606
|
]
|
|
219
607
|
}
|
|
220
608
|
),
|
|
221
|
-
!isActive &&
|
|
609
|
+
!isActive && model && /* @__PURE__ */ jsx(
|
|
222
610
|
"button",
|
|
223
611
|
{
|
|
224
|
-
onClick:
|
|
612
|
+
onClick: () => onSetActive("custom", model),
|
|
225
613
|
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors",
|
|
226
614
|
children: "Set as Active"
|
|
227
615
|
}
|
|
@@ -240,10 +628,28 @@ function ProviderSetupForm({ provider, config, isActive, onSave, onSetActive, on
|
|
|
240
628
|
}
|
|
241
629
|
);
|
|
242
630
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
/* @__PURE__ */
|
|
631
|
+
function TestResultBox({ result }) {
|
|
632
|
+
if (!result) return null;
|
|
633
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-white/[0.04] bg-black/30 overflow-hidden", children: [
|
|
634
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-2.5 py-1.5 border-b border-white/[0.04]", children: [
|
|
635
|
+
/* @__PURE__ */ jsx(TrafficLights, {}),
|
|
636
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] text-muted-foreground ml-1", children: "connection test" }),
|
|
637
|
+
result.error ? /* @__PURE__ */ jsx(XIcon, { size: 10, className: "text-[--destructive] ml-auto" }) : /* @__PURE__ */ jsx(CheckIcon, { size: 10, className: "text-[--success] ml-auto" })
|
|
638
|
+
] }),
|
|
639
|
+
/* @__PURE__ */ jsx("pre", { className: "text-[11px] p-2.5 font-mono overflow-auto max-h-24 whitespace-pre-wrap break-words text-foreground/80", children: result.error ? `Error: ${result.error}` : `Connected. ${result.response || "OK"}` })
|
|
640
|
+
] });
|
|
641
|
+
}
|
|
642
|
+
function StatsCard({ label, value, color = "cyan" }) {
|
|
643
|
+
const colors = {
|
|
644
|
+
cyan: { border: "border-[--cyan]/20", text: "text-[--cyan]" },
|
|
645
|
+
emerald: { border: "border-emerald-500/20", text: "text-emerald-400" },
|
|
646
|
+
purple: { border: "border-purple-500/20", text: "text-purple-400" },
|
|
647
|
+
default: { border: "border-white/[0.06]", text: "text-foreground" }
|
|
648
|
+
};
|
|
649
|
+
const c = colors[color] || colors.default;
|
|
650
|
+
return /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center justify-center p-3 rounded-lg border bg-[--card] ${c.border}`, children: [
|
|
651
|
+
/* @__PURE__ */ jsx("span", { className: `text-xl font-semibold font-mono ${c.text}`, children: value }),
|
|
652
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] text-muted-foreground uppercase tracking-wider mt-0.5", children: label })
|
|
247
653
|
] });
|
|
248
654
|
}
|
|
249
655
|
function SettingsProvidersPage() {
|
|
@@ -251,7 +657,9 @@ function SettingsProvidersPage() {
|
|
|
251
657
|
const [providers, setProviders] = useState({});
|
|
252
658
|
const [activeProviderInfo, setActiveProviderInfo] = useState(null);
|
|
253
659
|
const [editingProvider, setEditingProvider] = useState(null);
|
|
254
|
-
|
|
660
|
+
const [scanning, setScanning] = useState(false);
|
|
661
|
+
const [localDetected, setLocalDetected] = useState({});
|
|
662
|
+
const load = useCallback(async () => {
|
|
255
663
|
try {
|
|
256
664
|
const [provs, active] = await Promise.all([
|
|
257
665
|
getLlmProviders(),
|
|
@@ -262,9 +670,23 @@ function SettingsProvidersPage() {
|
|
|
262
670
|
} catch {
|
|
263
671
|
}
|
|
264
672
|
setLoading(false);
|
|
673
|
+
}, []);
|
|
674
|
+
async function handleScan() {
|
|
675
|
+
setScanning(true);
|
|
676
|
+
try {
|
|
677
|
+
const results = await scanLocalProviders();
|
|
678
|
+
const detected = {};
|
|
679
|
+
for (const r of results) {
|
|
680
|
+
detected[r.id] = r;
|
|
681
|
+
}
|
|
682
|
+
setLocalDetected(detected);
|
|
683
|
+
} catch {
|
|
684
|
+
}
|
|
685
|
+
setScanning(false);
|
|
265
686
|
}
|
|
266
687
|
useEffect(() => {
|
|
267
688
|
load();
|
|
689
|
+
handleScan();
|
|
268
690
|
}, []);
|
|
269
691
|
async function handleSave(provider, config) {
|
|
270
692
|
await saveLlmProvider(provider, config);
|
|
@@ -279,57 +701,170 @@ function SettingsProvidersPage() {
|
|
|
279
701
|
if (loading) {
|
|
280
702
|
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-16 animate-shimmer rounded-lg border border-white/[0.06] bg-[--card]" }, i)) });
|
|
281
703
|
}
|
|
282
|
-
const
|
|
283
|
-
const
|
|
704
|
+
const cloudCount = Object.keys(CLOUD_PROVIDERS).filter((p) => providers[p]?.hasKey).length;
|
|
705
|
+
const localCount = Object.keys(localDetected).length;
|
|
706
|
+
const cloudList = Object.keys(CLOUD_PROVIDERS);
|
|
707
|
+
const localList = Object.keys(LOCAL_PROVIDERS);
|
|
284
708
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
285
|
-
activeProviderInfo?.provider && /* @__PURE__ */ jsx(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
709
|
+
activeProviderInfo?.provider && /* @__PURE__ */ jsx(
|
|
710
|
+
motion.div,
|
|
711
|
+
{
|
|
712
|
+
initial: { opacity: 0, y: -8 },
|
|
713
|
+
animate: { opacity: 1, y: 0 },
|
|
714
|
+
className: "rounded-lg border border-[--cyan]/20 bg-[--cyan]/5 p-4 mb-6",
|
|
715
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
716
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-[--cyan]/10 p-2", children: /* @__PURE__ */ jsx(CpuIcon, { size: 16, className: "text-[--cyan]" }) }),
|
|
717
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
718
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
719
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider", children: "Active Provider" }),
|
|
720
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[--success] animate-pulse" })
|
|
721
|
+
] }),
|
|
722
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-mono font-medium mt-0.5", children: [
|
|
723
|
+
CLOUD_PROVIDERS[activeProviderInfo.provider]?.name || LOCAL_PROVIDERS[activeProviderInfo.provider]?.name || activeProviderInfo.provider,
|
|
724
|
+
activeProviderInfo.model && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground ml-2", children: [
|
|
725
|
+
"/ ",
|
|
726
|
+
activeProviderInfo.model
|
|
727
|
+
] })
|
|
728
|
+
] }),
|
|
729
|
+
activeProviderInfo.keyLoaded === false && /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono text-amber-400 mt-1", children: "Warning: API key not loaded in process. Save the provider config or restart the server." })
|
|
730
|
+
] }),
|
|
731
|
+
/* @__PURE__ */ jsxs("div", { className: "text-right shrink-0", children: [
|
|
732
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[9px] font-mono text-muted-foreground/50", children: [
|
|
733
|
+
"env: ",
|
|
734
|
+
activeProviderInfo.envProvider || "not set"
|
|
735
|
+
] }),
|
|
736
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[9px] font-mono text-muted-foreground/50", children: [
|
|
737
|
+
"model: ",
|
|
738
|
+
activeProviderInfo.envModel || "default"
|
|
739
|
+
] })
|
|
297
740
|
] })
|
|
298
741
|
] })
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-
|
|
302
|
-
/* @__PURE__ */ jsx(StatsCard, { label: "
|
|
303
|
-
/* @__PURE__ */ jsx(StatsCard, { label: "Configured", value:
|
|
304
|
-
/* @__PURE__ */ jsx(StatsCard, { label: "
|
|
742
|
+
}
|
|
743
|
+
),
|
|
744
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-4 gap-3 mb-6", children: [
|
|
745
|
+
/* @__PURE__ */ jsx(StatsCard, { label: "Cloud", value: cloudList.length }),
|
|
746
|
+
/* @__PURE__ */ jsx(StatsCard, { label: "Configured", value: cloudCount, color: "cyan" }),
|
|
747
|
+
/* @__PURE__ */ jsx(StatsCard, { label: "Local", value: localCount, color: "emerald" }),
|
|
748
|
+
/* @__PURE__ */ jsx(StatsCard, { label: "Active", value: activeProviderInfo?.provider ? 1 : 0, color: "purple" })
|
|
305
749
|
] }),
|
|
306
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pb-2 mb-
|
|
307
|
-
/* @__PURE__ */ jsx(
|
|
308
|
-
/* @__PURE__ */ jsx("span", { className: "
|
|
750
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pb-2 mb-3", children: [
|
|
751
|
+
/* @__PURE__ */ jsx(GlobeIcon, { size: 12, className: "text-[--cyan]" }),
|
|
752
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider", children: "Cloud Providers" }),
|
|
753
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-full bg-[--cyan]/10 px-2 py-0.5 text-[10px] font-mono font-medium text-[--cyan]", children: [
|
|
754
|
+
cloudCount,
|
|
755
|
+
"/",
|
|
756
|
+
cloudList.length
|
|
757
|
+
] })
|
|
309
758
|
] }),
|
|
310
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-
|
|
759
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 mb-8", children: cloudList.map((p, i) => /* @__PURE__ */ jsxs("div", { children: [
|
|
311
760
|
/* @__PURE__ */ jsx(
|
|
312
761
|
ProviderCard,
|
|
762
|
+
{
|
|
763
|
+
id: p,
|
|
764
|
+
meta: CLOUD_PROVIDERS[p],
|
|
765
|
+
config: providers[p],
|
|
766
|
+
isActive: activeProviderInfo?.provider === p,
|
|
767
|
+
isLocal: false,
|
|
768
|
+
onEdit: () => setEditingProvider(editingProvider === p ? null : p),
|
|
769
|
+
index: i
|
|
770
|
+
}
|
|
771
|
+
),
|
|
772
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: editingProvider === p && /* @__PURE__ */ jsx(
|
|
773
|
+
CloudSetupForm,
|
|
313
774
|
{
|
|
314
775
|
provider: p,
|
|
776
|
+
meta: CLOUD_PROVIDERS[p],
|
|
315
777
|
config: providers[p],
|
|
316
778
|
isActive: activeProviderInfo?.provider === p,
|
|
779
|
+
onSave: handleSave,
|
|
780
|
+
onSetActive: handleSetActive,
|
|
781
|
+
onClose: () => setEditingProvider(null)
|
|
782
|
+
}
|
|
783
|
+
) })
|
|
784
|
+
] }, p)) }),
|
|
785
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pb-2 mb-3", children: [
|
|
786
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
787
|
+
/* @__PURE__ */ jsx(ServerIcon, { size: 12, className: "text-emerald-400" }),
|
|
788
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-emerald-400 uppercase tracking-wider", children: "Local Providers" }),
|
|
789
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-mono font-medium text-emerald-400", children: [
|
|
790
|
+
localCount,
|
|
791
|
+
" detected"
|
|
792
|
+
] })
|
|
793
|
+
] }),
|
|
794
|
+
/* @__PURE__ */ jsxs(
|
|
795
|
+
"button",
|
|
796
|
+
{
|
|
797
|
+
onClick: handleScan,
|
|
798
|
+
disabled: scanning,
|
|
799
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-[10px] font-mono font-medium border border-emerald-500/20 text-emerald-400 hover:bg-emerald-500/10 transition-colors disabled:opacity-50",
|
|
800
|
+
children: [
|
|
801
|
+
scanning ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 10 }) : /* @__PURE__ */ jsx(ScanIcon, { size: 10 }),
|
|
802
|
+
scanning ? "Scanning..." : "Scan Network"
|
|
803
|
+
]
|
|
804
|
+
}
|
|
805
|
+
)
|
|
806
|
+
] }),
|
|
807
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 mb-8", children: localList.sort((a, b) => {
|
|
808
|
+
const aD = localDetected[a] ? 1 : 0;
|
|
809
|
+
const bD = localDetected[b] ? 1 : 0;
|
|
810
|
+
return bD - aD;
|
|
811
|
+
}).map((p, i) => /* @__PURE__ */ jsxs("div", { children: [
|
|
812
|
+
/* @__PURE__ */ jsx(
|
|
813
|
+
ProviderCard,
|
|
814
|
+
{
|
|
815
|
+
id: p,
|
|
816
|
+
meta: LOCAL_PROVIDERS[p],
|
|
817
|
+
config: providers[p],
|
|
818
|
+
isActive: activeProviderInfo?.provider === p,
|
|
819
|
+
isLocal: true,
|
|
820
|
+
detected: localDetected[p],
|
|
317
821
|
onEdit: () => setEditingProvider(editingProvider === p ? null : p),
|
|
318
822
|
index: i
|
|
319
823
|
}
|
|
320
824
|
),
|
|
321
825
|
/* @__PURE__ */ jsx(AnimatePresence, { children: editingProvider === p && /* @__PURE__ */ jsx(
|
|
322
|
-
|
|
826
|
+
LocalSetupForm,
|
|
323
827
|
{
|
|
324
828
|
provider: p,
|
|
829
|
+
meta: LOCAL_PROVIDERS[p],
|
|
325
830
|
config: providers[p],
|
|
831
|
+
detected: localDetected[p],
|
|
326
832
|
isActive: activeProviderInfo?.provider === p,
|
|
327
833
|
onSave: handleSave,
|
|
328
834
|
onSetActive: handleSetActive,
|
|
329
835
|
onClose: () => setEditingProvider(null)
|
|
330
836
|
}
|
|
331
837
|
) })
|
|
332
|
-
] }, p)) })
|
|
838
|
+
] }, p)) }),
|
|
839
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pb-2 mb-3", children: [
|
|
840
|
+
/* @__PURE__ */ jsx(WifiIcon, { size: 12, className: "text-purple-400" }),
|
|
841
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-purple-400 uppercase tracking-wider", children: "Custom Provider" }),
|
|
842
|
+
/* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono text-muted-foreground", children: "(OpenAI-Compatible)" })
|
|
843
|
+
] }),
|
|
844
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
845
|
+
/* @__PURE__ */ jsx(
|
|
846
|
+
ProviderCard,
|
|
847
|
+
{
|
|
848
|
+
id: "custom",
|
|
849
|
+
meta: { name: "Custom", icon: "\u2699", color: "#a78bfa" },
|
|
850
|
+
config: providers.custom,
|
|
851
|
+
isActive: activeProviderInfo?.provider === "custom",
|
|
852
|
+
isLocal: false,
|
|
853
|
+
onEdit: () => setEditingProvider(editingProvider === "custom" ? null : "custom"),
|
|
854
|
+
index: 0
|
|
855
|
+
}
|
|
856
|
+
),
|
|
857
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: editingProvider === "custom" && /* @__PURE__ */ jsx(
|
|
858
|
+
CustomSetupForm,
|
|
859
|
+
{
|
|
860
|
+
config: providers.custom,
|
|
861
|
+
isActive: activeProviderInfo?.provider === "custom",
|
|
862
|
+
onSave: handleSave,
|
|
863
|
+
onSetActive: handleSetActive,
|
|
864
|
+
onClose: () => setEditingProvider(null)
|
|
865
|
+
}
|
|
866
|
+
) })
|
|
867
|
+
] })
|
|
333
868
|
] });
|
|
334
869
|
}
|
|
335
870
|
export {
|