thepopebot 1.2.74-beta.50 → 1.2.74-beta.52
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/ai/model.js +1 -1
- package/lib/chat/components/message.js +11 -18
- package/lib/chat/components/message.jsx +4 -2
- package/lib/chat/components/settings-chat-page.js +154 -77
- package/lib/chat/components/settings-chat-page.jsx +102 -37
- package/lib/chat/components/settings-coding-agents-page.js +160 -68
- package/lib/chat/components/settings-coding-agents-page.jsx +160 -70
- package/lib/chat/components/settings-general-page.js +54 -1
- package/lib/chat/components/settings-general-page.jsx +43 -0
- package/lib/chat/components/upgrade-dialog.js +1 -1
- package/lib/chat/components/upgrade-dialog.jsx +1 -1
- package/lib/db/config.js +8 -6
- package/lib/db/index.js +8 -6
- package/lib/llm-providers.js +1 -0
- package/lib/paths.js +1 -4
- package/lib/tools/docker.js +1 -1
- package/package.json +1 -1
- package/setup/setup.mjs +5 -33
- package/templates/docker-compose.litellm.yml +73 -2
package/lib/ai/model.js
CHANGED
|
@@ -25,7 +25,7 @@ export async function createModel(options = {}) {
|
|
|
25
25
|
const { getCustomProvider } = await import('../db/config.js');
|
|
26
26
|
const custom = getCustomProvider(provider);
|
|
27
27
|
if (!custom) throw new Error(`Unknown LLM provider: ${provider}`);
|
|
28
|
-
const config = { modelName: custom.
|
|
28
|
+
const config = { modelName: custom.models?.[0] || modelName, maxTokens };
|
|
29
29
|
config.apiKey = custom.apiKey || 'not-needed';
|
|
30
30
|
if (custom.baseUrl) {
|
|
31
31
|
config.configuration = { baseURL: custom.baseUrl };
|
|
@@ -148,26 +148,19 @@ function ToolCall({ part, className }) {
|
|
|
148
148
|
className: "flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-muted/50 rounded-lg",
|
|
149
149
|
children: [
|
|
150
150
|
/* @__PURE__ */ jsx(WrenchIcon, { size: 14, className: "text-muted-foreground shrink-0 mt-0.5" }),
|
|
151
|
-
/* @__PURE__ */
|
|
152
|
-
/* @__PURE__ */
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: o.backendApi || o.codingAgent });
|
|
159
|
-
}
|
|
160
|
-
} catch {
|
|
151
|
+
/* @__PURE__ */ jsx("span", { className: "flex flex-col min-w-0 flex-1", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
152
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: displayName }),
|
|
153
|
+
isDone && (() => {
|
|
154
|
+
try {
|
|
155
|
+
const o = typeof part.output === "string" ? JSON.parse(part.output) : part.output;
|
|
156
|
+
if (o?.codingAgent || o?.backendApi) {
|
|
157
|
+
return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: [o.codingAgent, o.backendApi].filter(Boolean).join(" \xB7 ") });
|
|
161
158
|
}
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
(() => {
|
|
166
|
-
const prompt = part.input?.prompt;
|
|
167
|
-
if (!prompt) return null;
|
|
168
|
-
return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground whitespace-pre-wrap", children: prompt });
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
169
162
|
})()
|
|
170
|
-
] }),
|
|
163
|
+
] }) }),
|
|
171
164
|
/* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-xs text-muted-foreground shrink-0", children: [
|
|
172
165
|
isRunning && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
173
166
|
/* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }),
|
|
@@ -160,7 +160,7 @@ function ToolCall({ part, className }) {
|
|
|
160
160
|
if (o?.codingAgent || o?.backendApi) {
|
|
161
161
|
return (
|
|
162
162
|
<span className="text-xs text-muted-foreground">
|
|
163
|
-
{o.backendApi
|
|
163
|
+
{[o.codingAgent, o.backendApi].filter(Boolean).join(' · ')}
|
|
164
164
|
</span>
|
|
165
165
|
);
|
|
166
166
|
}
|
|
@@ -168,13 +168,15 @@ function ToolCall({ part, className }) {
|
|
|
168
168
|
return null;
|
|
169
169
|
})()}
|
|
170
170
|
</span>
|
|
171
|
+
{/* DISABLED — may be re-enabled later. Do NOT remove from codebase.
|
|
172
|
+
Shows the tool's prompt text as a subtitle below the tool name.
|
|
171
173
|
{(() => {
|
|
172
174
|
const prompt = part.input?.prompt;
|
|
173
175
|
if (!prompt) return null;
|
|
174
176
|
return (
|
|
175
177
|
<span className="text-xs text-muted-foreground whitespace-pre-wrap">{prompt}</span>
|
|
176
178
|
);
|
|
177
|
-
})()}
|
|
179
|
+
})()} */}
|
|
178
180
|
</span>
|
|
179
181
|
<span className="ml-auto flex items-center gap-1.5 text-xs text-muted-foreground shrink-0">
|
|
180
182
|
{isRunning && (
|
|
@@ -51,6 +51,7 @@ function ChatConfigPage() {
|
|
|
51
51
|
function ActiveConfig({ settings, onSave }) {
|
|
52
52
|
const [provider, setProvider] = useState("");
|
|
53
53
|
const [model, setModel] = useState("");
|
|
54
|
+
const [modelText, setModelText] = useState("");
|
|
54
55
|
const [maxTokens, setMaxTokens] = useState("4096");
|
|
55
56
|
const [saving, setSaving] = useState(false);
|
|
56
57
|
const [saved, setSaved] = useState(false);
|
|
@@ -60,6 +61,7 @@ function ActiveConfig({ settings, onSave }) {
|
|
|
60
61
|
if (settings?.active) {
|
|
61
62
|
setProvider(settings.active.provider || "anthropic");
|
|
62
63
|
setModel(settings.active.model || "");
|
|
64
|
+
setModelText(settings.active.model || "");
|
|
63
65
|
setMaxTokens(settings.active.maxTokens || "4096");
|
|
64
66
|
setTimeout(() => {
|
|
65
67
|
initialized.current = true;
|
|
@@ -92,77 +94,88 @@ function ActiveConfig({ settings, onSave }) {
|
|
|
92
94
|
}
|
|
93
95
|
if (settings?.customProviders) {
|
|
94
96
|
for (const cp of settings.customProviders) {
|
|
95
|
-
availableProviders.push({ slug: cp.key, name: cp.name, models:
|
|
97
|
+
availableProviders.push({ slug: cp.key, name: cp.name, models: cp.models.map((m) => ({ id: m, name: m })) });
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
|
-
const
|
|
100
|
+
const selectedProvider = availableProviders.find((p) => p.slug === provider);
|
|
99
101
|
const handleProviderChange = (slug) => {
|
|
100
102
|
setProvider(slug);
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
newModel = def?.id || bp.models[0]?.id || "";
|
|
106
|
-
} else {
|
|
107
|
-
const cp = settings?.customProviders?.find((c) => c.key === slug);
|
|
108
|
-
newModel = cp?.model || "";
|
|
109
|
-
}
|
|
103
|
+
const prov = availableProviders.find((p) => p.slug === slug);
|
|
104
|
+
const models = prov?.models || [];
|
|
105
|
+
const def = models.find((m) => m.default);
|
|
106
|
+
const newModel = def?.id || models[0]?.id || "";
|
|
110
107
|
setModel(newModel);
|
|
111
|
-
|
|
108
|
+
setModelText(newModel);
|
|
109
|
+
if (models.length > 0) {
|
|
110
|
+
scheduleAutoSave(slug, newModel, maxTokens);
|
|
111
|
+
}
|
|
112
112
|
};
|
|
113
113
|
const handleModelChange = (m) => {
|
|
114
114
|
setModel(m);
|
|
115
115
|
scheduleAutoSave(provider, m, maxTokens);
|
|
116
116
|
};
|
|
117
|
+
const handleModelTextSave = () => {
|
|
118
|
+
setModel(modelText);
|
|
119
|
+
doSave(provider, modelText, maxTokens);
|
|
120
|
+
};
|
|
121
|
+
const savedModel = settings?.active?.model || "";
|
|
122
|
+
const savedMaxTokens = settings?.active?.maxTokens || "4096";
|
|
123
|
+
const hasUnsavedChanges = modelText !== savedModel || maxTokens !== savedMaxTokens;
|
|
124
|
+
const hasModels = (selectedProvider?.models || []).length > 0;
|
|
117
125
|
const handleMaxTokensChange = (mt) => {
|
|
118
126
|
setMaxTokens(mt);
|
|
119
|
-
|
|
127
|
+
if (hasModels) {
|
|
128
|
+
scheduleAutoSave(provider, model, mt);
|
|
129
|
+
}
|
|
120
130
|
};
|
|
121
|
-
return /* @__PURE__ */
|
|
122
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
123
|
-
/* @__PURE__ */ jsx("
|
|
124
|
-
/* @__PURE__ */ jsxs(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
onChange: (e) => handleProviderChange(e.target.value),
|
|
129
|
-
className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground",
|
|
130
|
-
children: [
|
|
131
|
-
availableProviders.map((p) => /* @__PURE__ */ jsx("option", { value: p.slug, children: p.name }, p.slug)),
|
|
132
|
-
availableProviders.length === 0 && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "No providers configured" })
|
|
133
|
-
]
|
|
134
|
-
}
|
|
135
|
-
)
|
|
136
|
-
] }),
|
|
137
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-3", children: [
|
|
138
|
-
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium shrink-0", children: "Model" }),
|
|
139
|
-
selectedBuiltin ? /* @__PURE__ */ jsx(
|
|
140
|
-
"select",
|
|
141
|
-
{
|
|
142
|
-
value: model,
|
|
143
|
-
onChange: (e) => handleModelChange(e.target.value),
|
|
144
|
-
className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground",
|
|
145
|
-
children: selectedBuiltin.models.filter((m) => m.chat !== false).map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: m.name }, m.id))
|
|
146
|
-
}
|
|
147
|
-
) : /* @__PURE__ */ jsx(
|
|
148
|
-
"input",
|
|
149
|
-
{
|
|
150
|
-
type: "text",
|
|
151
|
-
value: model,
|
|
152
|
-
onChange: (e) => handleModelChange(e.target.value),
|
|
153
|
-
placeholder: "Model name",
|
|
154
|
-
className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
|
|
155
|
-
}
|
|
156
|
-
)
|
|
131
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
|
|
132
|
+
(saving || saved) && /* @__PURE__ */ jsxs("div", { className: "flex justify-end mb-2", children: [
|
|
133
|
+
saving && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Saving..." }),
|
|
134
|
+
saved && /* @__PURE__ */ jsxs("span", { className: "text-xs text-green-500 inline-flex items-center gap-1", children: [
|
|
135
|
+
/* @__PURE__ */ jsx(CheckIcon, { size: 12 }),
|
|
136
|
+
" Saved"
|
|
137
|
+
] })
|
|
157
138
|
] }),
|
|
158
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
159
|
-
/* @__PURE__ */
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
139
|
+
/* @__PURE__ */ jsxs("div", { className: "divide-y divide-border", children: [
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-3 first:pt-0", children: [
|
|
141
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium shrink-0", children: "Provider" }),
|
|
142
|
+
/* @__PURE__ */ jsxs(
|
|
143
|
+
"select",
|
|
144
|
+
{
|
|
145
|
+
value: provider,
|
|
146
|
+
onChange: (e) => handleProviderChange(e.target.value),
|
|
147
|
+
className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground",
|
|
148
|
+
children: [
|
|
149
|
+
availableProviders.map((p) => /* @__PURE__ */ jsx("option", { value: p.slug, children: p.name }, p.slug)),
|
|
150
|
+
availableProviders.length === 0 && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "No providers configured" })
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-3", children: [
|
|
156
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium shrink-0", children: "Model" }),
|
|
157
|
+
(selectedProvider?.models || []).length > 0 ? /* @__PURE__ */ jsx(
|
|
158
|
+
"select",
|
|
159
|
+
{
|
|
160
|
+
value: model,
|
|
161
|
+
onChange: (e) => handleModelChange(e.target.value),
|
|
162
|
+
className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground",
|
|
163
|
+
children: (selectedProvider?.models || []).filter((m) => m.chat !== false).map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: m.name }, m.id))
|
|
164
|
+
}
|
|
165
|
+
) : /* @__PURE__ */ jsx(
|
|
166
|
+
"input",
|
|
167
|
+
{
|
|
168
|
+
type: "text",
|
|
169
|
+
value: modelText,
|
|
170
|
+
onChange: (e) => setModelText(e.target.value),
|
|
171
|
+
onKeyDown: (e) => e.key === "Enter" && handleModelTextSave(),
|
|
172
|
+
placeholder: "Model name",
|
|
173
|
+
className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
] }),
|
|
177
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-3 last:pb-0", children: [
|
|
178
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium shrink-0", children: "Max Tokens" }),
|
|
166
179
|
/* @__PURE__ */ jsx(
|
|
167
180
|
"input",
|
|
168
181
|
{
|
|
@@ -173,8 +186,17 @@ function ActiveConfig({ settings, onSave }) {
|
|
|
173
186
|
}
|
|
174
187
|
)
|
|
175
188
|
] })
|
|
176
|
-
] })
|
|
177
|
-
|
|
189
|
+
] }),
|
|
190
|
+
!hasModels && /* @__PURE__ */ jsx("div", { className: "flex justify-end mt-4", children: /* @__PURE__ */ jsx(
|
|
191
|
+
"button",
|
|
192
|
+
{
|
|
193
|
+
onClick: handleModelTextSave,
|
|
194
|
+
disabled: !hasUnsavedChanges || saving,
|
|
195
|
+
className: "rounded-md px-3 py-1.5 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 transition-colors",
|
|
196
|
+
children: "Save"
|
|
197
|
+
}
|
|
198
|
+
) })
|
|
199
|
+
] });
|
|
178
200
|
}
|
|
179
201
|
function ChatProvidersPage() {
|
|
180
202
|
const [settings, setSettings] = useState(null);
|
|
@@ -537,8 +559,8 @@ function CustomProviderCard({ provider, onEdit, onRemove }) {
|
|
|
537
559
|
/* @__PURE__ */ jsx(StatusBadge, { isSet: provider.hasApiKey })
|
|
538
560
|
] }),
|
|
539
561
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between py-3", children: [
|
|
540
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "
|
|
541
|
-
/* @__PURE__ */ jsx("code", { className: "text-xs font-mono text-muted-foreground", children: provider.
|
|
562
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Models" }) }),
|
|
563
|
+
/* @__PURE__ */ jsx("code", { className: "text-xs font-mono text-muted-foreground", children: provider.models.join(", ") })
|
|
542
564
|
] }),
|
|
543
565
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between py-3", children: [
|
|
544
566
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Base URL" }) }),
|
|
@@ -551,7 +573,7 @@ function CustomProviderDialog({ open, initial, onSave, onCancel }) {
|
|
|
551
573
|
const [name, setName] = useState(initial?.name || "");
|
|
552
574
|
const [baseUrl, setBaseUrl] = useState(initial?.baseUrl || "");
|
|
553
575
|
const [apiKey, setApiKey] = useState("");
|
|
554
|
-
const [
|
|
576
|
+
const [models, setModels] = useState(initial?.models?.length ? initial.models : [""]);
|
|
555
577
|
const [saving, setSaving] = useState(false);
|
|
556
578
|
const nameRef = useRef(null);
|
|
557
579
|
useEffect(() => {
|
|
@@ -559,19 +581,32 @@ function CustomProviderDialog({ open, initial, onSave, onCancel }) {
|
|
|
559
581
|
setName(initial?.name || "");
|
|
560
582
|
setBaseUrl(initial?.baseUrl || "");
|
|
561
583
|
setApiKey("");
|
|
562
|
-
|
|
584
|
+
setModels(initial?.models?.length ? [...initial.models] : [""]);
|
|
563
585
|
setSaving(false);
|
|
564
586
|
setTimeout(() => nameRef.current?.focus(), 50);
|
|
565
587
|
}
|
|
566
588
|
}, [open, initial]);
|
|
567
589
|
const handleSubmit = async () => {
|
|
568
590
|
setSaving(true);
|
|
569
|
-
const
|
|
591
|
+
const filteredModels = models.filter((m) => m.trim());
|
|
592
|
+
const config = { name, baseUrl, models: filteredModels };
|
|
570
593
|
if (apiKey) config.apiKey = apiKey;
|
|
571
594
|
else if (initial?.hasApiKey) config.apiKey = "__keep__";
|
|
572
595
|
await onSave(config);
|
|
573
596
|
setSaving(false);
|
|
574
597
|
};
|
|
598
|
+
const updateModel = (index, value) => {
|
|
599
|
+
const next = [...models];
|
|
600
|
+
next[index] = value;
|
|
601
|
+
setModels(next);
|
|
602
|
+
};
|
|
603
|
+
const removeModel = (index) => {
|
|
604
|
+
setModels(models.filter((_, i) => i !== index));
|
|
605
|
+
};
|
|
606
|
+
const addModel = () => {
|
|
607
|
+
setModels([...models, ""]);
|
|
608
|
+
};
|
|
609
|
+
const hasValidModels = models.some((m) => m.trim());
|
|
575
610
|
return /* @__PURE__ */ jsxs(Dialog, { open, onClose: onCancel, title: initial ? "Edit Provider" : "Add OpenAI Compatible API", children: [
|
|
576
611
|
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
577
612
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -583,7 +618,7 @@ function CustomProviderDialog({ open, initial, onSave, onCancel }) {
|
|
|
583
618
|
type: "text",
|
|
584
619
|
value: name,
|
|
585
620
|
onChange: (e) => setName(e.target.value),
|
|
586
|
-
placeholder: "
|
|
621
|
+
placeholder: "Ollama (model)",
|
|
587
622
|
className: "w-full rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
|
|
588
623
|
}
|
|
589
624
|
)
|
|
@@ -622,17 +657,59 @@ function CustomProviderDialog({ open, initial, onSave, onCancel }) {
|
|
|
622
657
|
)
|
|
623
658
|
] }),
|
|
624
659
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
625
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium mb-1 block", children: "
|
|
626
|
-
/* @__PURE__ */
|
|
627
|
-
"
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
660
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium mb-1 block", children: "Models" }),
|
|
661
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
662
|
+
models.map((m, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
663
|
+
/* @__PURE__ */ jsx(
|
|
664
|
+
"input",
|
|
665
|
+
{
|
|
666
|
+
type: "text",
|
|
667
|
+
value: m,
|
|
668
|
+
onChange: (e) => updateModel(i, e.target.value),
|
|
669
|
+
placeholder: "qwen2.5-coder:3b",
|
|
670
|
+
className: "w-full rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
|
|
671
|
+
}
|
|
672
|
+
),
|
|
673
|
+
models.length > 1 && /* @__PURE__ */ jsx(
|
|
674
|
+
"button",
|
|
675
|
+
{
|
|
676
|
+
type: "button",
|
|
677
|
+
onClick: () => removeModel(i),
|
|
678
|
+
className: "shrink-0 rounded-md px-2 py-1.5 text-xs font-medium border border-border text-muted-foreground hover:text-destructive hover:border-destructive/50 transition-colors",
|
|
679
|
+
children: "\xD7"
|
|
680
|
+
}
|
|
681
|
+
)
|
|
682
|
+
] }, i)),
|
|
683
|
+
/* @__PURE__ */ jsx(
|
|
684
|
+
"button",
|
|
685
|
+
{
|
|
686
|
+
type: "button",
|
|
687
|
+
onClick: addModel,
|
|
688
|
+
className: "text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
|
|
689
|
+
children: "+ Add model"
|
|
690
|
+
}
|
|
691
|
+
)
|
|
692
|
+
] })
|
|
693
|
+
] }),
|
|
694
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border bg-muted/50 px-3 py-2.5 text-xs text-muted-foreground space-y-1", children: [
|
|
695
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Ollama" }),
|
|
696
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
697
|
+
"Name: ",
|
|
698
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "Ollama (qwen2.5-coder:3b)" })
|
|
699
|
+
] }),
|
|
700
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
701
|
+
"URL: ",
|
|
702
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "http://host.docker.internal:11434/v1" })
|
|
703
|
+
] }),
|
|
704
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
705
|
+
"API Key: any value (e.g. ",
|
|
706
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "ollama" }),
|
|
707
|
+
")"
|
|
708
|
+
] }),
|
|
709
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
710
|
+
"Models: exact names from ",
|
|
711
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "ollama list" })
|
|
712
|
+
] })
|
|
636
713
|
] })
|
|
637
714
|
] }),
|
|
638
715
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 mt-5", children: [
|
|
@@ -641,7 +718,7 @@ function CustomProviderDialog({ open, initial, onSave, onCancel }) {
|
|
|
641
718
|
"button",
|
|
642
719
|
{
|
|
643
720
|
onClick: handleSubmit,
|
|
644
|
-
disabled: !name || !baseUrl || !
|
|
721
|
+
disabled: !name || !baseUrl || !hasValidModels || saving,
|
|
645
722
|
className: "rounded-md px-3 py-1.5 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 transition-colors",
|
|
646
723
|
children: saving ? "Saving..." : initial ? "Save" : "Add"
|
|
647
724
|
}
|