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 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.model || modelName, maxTokens };
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__ */ jsxs("span", { className: "flex flex-col min-w-0 flex-1", children: [
152
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
153
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: displayName }),
154
- isDone && (() => {
155
- try {
156
- const o = typeof part.output === "string" ? JSON.parse(part.output) : part.output;
157
- if (o?.codingAgent || o?.backendApi) {
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
- return null;
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 || o.codingAgent}
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: null, customModel: cp.model });
97
+ availableProviders.push({ slug: cp.key, name: cp.name, models: cp.models.map((m) => ({ id: m, name: m })) });
96
98
  }
97
99
  }
98
- const selectedBuiltin = settings?.builtinProviders?.[provider];
100
+ const selectedProvider = availableProviders.find((p) => p.slug === provider);
99
101
  const handleProviderChange = (slug) => {
100
102
  setProvider(slug);
101
- const bp = settings?.builtinProviders?.[slug];
102
- let newModel;
103
- if (bp) {
104
- const def = bp.models.find((m) => m.default);
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
- scheduleAutoSave(slug, newModel, maxTokens);
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
- scheduleAutoSave(provider, model, mt);
127
+ if (hasModels) {
128
+ scheduleAutoSave(provider, model, mt);
129
+ }
120
130
  };
121
- return /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-4", children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border", children: [
122
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-3 first:pt-0", children: [
123
- /* @__PURE__ */ jsx("label", { className: "text-sm font-medium shrink-0", children: "Provider" }),
124
- /* @__PURE__ */ jsxs(
125
- "select",
126
- {
127
- value: provider,
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: "flex items-center justify-between py-3 last:pb-0", children: [
159
- /* @__PURE__ */ jsx("label", { className: "text-sm font-medium shrink-0", children: "Max Tokens" }),
160
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
161
- saving && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Saving..." }),
162
- saved && /* @__PURE__ */ jsxs("span", { className: "text-xs text-green-500 inline-flex items-center gap-1", children: [
163
- /* @__PURE__ */ jsx(CheckIcon, { size: 12 }),
164
- " Saved"
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: "Model" }) }),
541
- /* @__PURE__ */ jsx("code", { className: "text-xs font-mono text-muted-foreground", children: provider.model })
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 [model, setModel] = useState(initial?.model || "");
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
- setModel(initial?.model || "");
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 config = { name, baseUrl, model };
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: "Together AI",
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: "Model" }),
626
- /* @__PURE__ */ jsx(
627
- "input",
628
- {
629
- type: "text",
630
- value: model,
631
- onChange: (e) => setModel(e.target.value),
632
- placeholder: "meta-llama/Llama-3-70b-chat",
633
- 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"
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 || !model || saving,
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
  }