@truefoundry/trueforge-ui 0.0.0 → 0.1.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +340 -0
- package/LICENSE +21 -0
- package/NOTICE +17 -0
- package/README.md +640 -3
- package/dist/ConnectorSettings-CLBEWGQJ.js +898 -0
- package/dist/ConnectorSettings-CLBEWGQJ.js.map +1 -0
- package/dist/DockLayout-WRF7EQLD.js +35 -0
- package/dist/DockLayout-WRF7EQLD.js.map +1 -0
- package/dist/DrawerLayout-X2UXYTOS.js +156 -0
- package/dist/DrawerLayout-X2UXYTOS.js.map +1 -0
- package/dist/ModelSettings-IJ55QJQO.js +1082 -0
- package/dist/ModelSettings-IJ55QJQO.js.map +1 -0
- package/dist/OpenUiFenceBlock-SLLZSTMJ.js +9 -0
- package/dist/OpenUiFenceBlock-SLLZSTMJ.js.map +1 -0
- package/dist/SandboxSettings-S5XGZWOL.js +505 -0
- package/dist/SandboxSettings-S5XGZWOL.js.map +1 -0
- package/dist/SettingsBuilder-64Y33NP3.js +128 -0
- package/dist/SettingsBuilder-64Y33NP3.js.map +1 -0
- package/dist/SidebarLayout-M7HEOZVK.js +236 -0
- package/dist/SidebarLayout-M7HEOZVK.js.map +1 -0
- package/dist/SkillSettings-MXK4ZEI7.js +389 -0
- package/dist/SkillSettings-MXK4ZEI7.js.map +1 -0
- package/dist/WidgetLayout-7MDOKLHU.js +101 -0
- package/dist/WidgetLayout-7MDOKLHU.js.map +1 -0
- package/dist/assistant-ui.d.ts +1 -0
- package/dist/assistant-ui.js +9 -0
- package/dist/assistant-ui.js.map +1 -0
- package/dist/chunk-42GF723P.js +24 -0
- package/dist/chunk-42GF723P.js.map +1 -0
- package/dist/chunk-CFLVPV5M.js +29 -0
- package/dist/chunk-CFLVPV5M.js.map +1 -0
- package/dist/chunk-M6MHLVSO.js +8 -0
- package/dist/chunk-M6MHLVSO.js.map +1 -0
- package/dist/chunk-NBMP2XNL.js +89 -0
- package/dist/chunk-NBMP2XNL.js.map +1 -0
- package/dist/chunk-UBOFEI77.js +567 -0
- package/dist/chunk-UBOFEI77.js.map +1 -0
- package/dist/chunk-UHGSHDBM.js +633 -0
- package/dist/chunk-UHGSHDBM.js.map +1 -0
- package/dist/chunk-UXZOL5GO.js +2088 -0
- package/dist/chunk-UXZOL5GO.js.map +1 -0
- package/dist/chunk-WFC3W4C3.js +73 -0
- package/dist/chunk-WFC3W4C3.js.map +1 -0
- package/dist/chunk-XSHXH6EI.js +6800 -0
- package/dist/chunk-XSHXH6EI.js.map +1 -0
- package/dist/createTrueFoundryServer-B72bU-J7.d.ts +28 -0
- package/dist/index.d.ts +1569 -0
- package/dist/index.js +781 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/trueforge-agent-server-adapter/index.d.ts +479 -0
- package/dist/plugins/trueforge-agent-server-adapter/index.js +830 -0
- package/dist/plugins/trueforge-agent-server-adapter/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +129 -6
- package/index.js +0 -1
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
Accordion,
|
|
4
|
+
AccordionDetails,
|
|
5
|
+
AccordionSummary,
|
|
6
|
+
Button,
|
|
7
|
+
CatalogLogo,
|
|
8
|
+
CenteredModal,
|
|
9
|
+
SearchInput_default,
|
|
10
|
+
auiInputClass,
|
|
11
|
+
getErrorMessage,
|
|
12
|
+
useToasterOptional
|
|
13
|
+
} from "./chunk-XSHXH6EI.js";
|
|
14
|
+
import {
|
|
15
|
+
Icon,
|
|
16
|
+
useCatalogServer
|
|
17
|
+
} from "./chunk-UHGSHDBM.js";
|
|
18
|
+
import "./chunk-42GF723P.js";
|
|
19
|
+
import {
|
|
20
|
+
cn
|
|
21
|
+
} from "./chunk-UBOFEI77.js";
|
|
22
|
+
|
|
23
|
+
// src/containers/SettingsBuilder/ModelSettings.tsx
|
|
24
|
+
import { useCallback, useEffect as useEffect2, useMemo, useState as useState3 } from "react";
|
|
25
|
+
|
|
26
|
+
// src/containers/SettingsBuilder/ConfigureModelProviderForm.tsx
|
|
27
|
+
import { useEffect, useState } from "react";
|
|
28
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
29
|
+
var inputClassName = auiInputClass("h-11 shadow-sm");
|
|
30
|
+
var ConfigureModelProviderForm = ({
|
|
31
|
+
open,
|
|
32
|
+
onOpenChange,
|
|
33
|
+
onSave,
|
|
34
|
+
title,
|
|
35
|
+
description,
|
|
36
|
+
initialBaseUrl = "",
|
|
37
|
+
baseUrlPlaceholder = "https://api.openai.com/v1",
|
|
38
|
+
requireApiKey = true,
|
|
39
|
+
submitLabel = "Save",
|
|
40
|
+
busy = false,
|
|
41
|
+
error
|
|
42
|
+
}) => {
|
|
43
|
+
const [apiKey, setApiKey] = useState("");
|
|
44
|
+
const [baseUrl, setBaseUrl] = useState("");
|
|
45
|
+
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
46
|
+
const resetForm = () => {
|
|
47
|
+
setApiKey("");
|
|
48
|
+
setBaseUrl("");
|
|
49
|
+
setAdvancedOpen(false);
|
|
50
|
+
};
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!open) return;
|
|
53
|
+
setApiKey("");
|
|
54
|
+
setBaseUrl(initialBaseUrl);
|
|
55
|
+
setAdvancedOpen(false);
|
|
56
|
+
}, [open, initialBaseUrl]);
|
|
57
|
+
const handleOpenChange = (nextOpen) => {
|
|
58
|
+
if (!nextOpen) resetForm();
|
|
59
|
+
onOpenChange(nextOpen);
|
|
60
|
+
};
|
|
61
|
+
const trimmedKey = apiKey.trim();
|
|
62
|
+
const isValid = !requireApiKey || !!trimmedKey;
|
|
63
|
+
const handleSubmit = async (event) => {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
if (!isValid || busy) return;
|
|
66
|
+
try {
|
|
67
|
+
await onSave({ apiKey: trimmedKey, baseUrl: baseUrl.trim() });
|
|
68
|
+
resetForm();
|
|
69
|
+
onOpenChange(false);
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return /* @__PURE__ */ jsx(CenteredModal, { open, onOpenChange: handleOpenChange, title, description, contentSized: true, children: /* @__PURE__ */ jsxs("form", { className: "flex flex-col overflow-y-auto p-5 md:p-6", onSubmit: handleSubmit, children: [
|
|
74
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
75
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
76
|
+
/* @__PURE__ */ jsxs("label", { htmlFor: "model-provider-api-key", className: "mb-1.5 block text-sm font-medium text-text-primary", children: [
|
|
77
|
+
"API key",
|
|
78
|
+
!requireApiKey ? /* @__PURE__ */ jsx("span", { className: "font-normal text-text-secondary", children: " (optional)" }) : null
|
|
79
|
+
] }),
|
|
80
|
+
/* @__PURE__ */ jsx(
|
|
81
|
+
"input",
|
|
82
|
+
{
|
|
83
|
+
id: "model-provider-api-key",
|
|
84
|
+
type: "password",
|
|
85
|
+
required: requireApiKey,
|
|
86
|
+
value: apiKey,
|
|
87
|
+
onChange: (event) => {
|
|
88
|
+
setApiKey(event.target.value);
|
|
89
|
+
},
|
|
90
|
+
placeholder: requireApiKey ? "Enter API key" : "Enter a new key",
|
|
91
|
+
autoFocus: true,
|
|
92
|
+
className: inputClassName
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
!requireApiKey ? /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-text-secondary", children: "Leave blank to keep the current key; enter a new one to replace it." }) : null
|
|
96
|
+
] }),
|
|
97
|
+
/* @__PURE__ */ jsxs(
|
|
98
|
+
Accordion,
|
|
99
|
+
{
|
|
100
|
+
expanded: advancedOpen,
|
|
101
|
+
onChange: (_event, next) => setAdvancedOpen(next),
|
|
102
|
+
className: "rounded-md border border-border",
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ jsx(AccordionSummary, { className: "px-3 py-2.5", children: /* @__PURE__ */ jsxs("span", { className: "flex w-full items-center justify-between text-sm font-medium text-text-primary", children: [
|
|
105
|
+
"Advanced \xB7 custom endpoint",
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
Icon,
|
|
108
|
+
{
|
|
109
|
+
name: "chevron-down",
|
|
110
|
+
className: cn(
|
|
111
|
+
"size-4 shrink-0 text-text-secondary transition-transform duration-200",
|
|
112
|
+
advancedOpen ? "rotate-0" : "-rotate-90"
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
] }) }),
|
|
117
|
+
/* @__PURE__ */ jsxs(AccordionDetails, { className: "space-y-1.5 border-t border-border px-3 pb-3 pt-4", children: [
|
|
118
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "model-provider-base-url", className: "block text-sm font-medium text-text-primary", children: "Base URL" }),
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
"input",
|
|
121
|
+
{
|
|
122
|
+
id: "model-provider-base-url",
|
|
123
|
+
type: "url",
|
|
124
|
+
value: baseUrl,
|
|
125
|
+
onChange: (event) => {
|
|
126
|
+
setBaseUrl(event.target.value);
|
|
127
|
+
},
|
|
128
|
+
placeholder: baseUrlPlaceholder,
|
|
129
|
+
className: inputClassName
|
|
130
|
+
}
|
|
131
|
+
),
|
|
132
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: "Leave blank unless you use a regional or proxy endpoint." })
|
|
133
|
+
] })
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
] }),
|
|
138
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-3", children: [
|
|
139
|
+
error ? /* @__PURE__ */ jsx("p", { className: "text-failure-bg text-sm", children: error }) : null,
|
|
140
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", size: "lg", disabled: !isValid || busy, className: "w-full shrink-0", children: submitLabel })
|
|
141
|
+
] })
|
|
142
|
+
] }) });
|
|
143
|
+
};
|
|
144
|
+
var ConfigureModelProviderForm_default = ConfigureModelProviderForm;
|
|
145
|
+
|
|
146
|
+
// src/containers/SettingsBuilder/CustomModelProviderForm.tsx
|
|
147
|
+
import { useState as useState2 } from "react";
|
|
148
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
149
|
+
var PLACEHOLDER_CONTEXT_LENGTH = "128000";
|
|
150
|
+
var PLACEHOLDER_MAX_OUTPUT_TOKENS = "4096";
|
|
151
|
+
var createEmptyModelRow = () => ({
|
|
152
|
+
id: "",
|
|
153
|
+
name: "",
|
|
154
|
+
// Expanded by default: Context length and Max output tokens are required, so they
|
|
155
|
+
// shouldn't be hidden behind a collapsed section.
|
|
156
|
+
advancedExpanded: true,
|
|
157
|
+
contextLength: "",
|
|
158
|
+
maxOutputTokens: ""
|
|
159
|
+
});
|
|
160
|
+
var createModelRows = (initialValues) => initialValues?.models.map((model) => ({
|
|
161
|
+
id: model.id,
|
|
162
|
+
name: model.name,
|
|
163
|
+
advancedExpanded: true,
|
|
164
|
+
reasoningEfforts: model.properties?.reasoningEfforts,
|
|
165
|
+
contextLength: model.properties?.contextLength?.toString() ?? "",
|
|
166
|
+
maxOutputTokens: model.properties?.maxOutputTokens?.toString() ?? "",
|
|
167
|
+
nameDirty: true
|
|
168
|
+
})) ?? [createEmptyModelRow()];
|
|
169
|
+
var inputClassName2 = "h-11 w-full rounded-md border border-border/70 bg-secondary-bg px-3 text-sm text-text-primary outline-none transition-colors placeholder:text-text-secondary/70 focus-visible:border-focus-ring focus-visible:ring-2 focus-visible:ring-focus-ring/50";
|
|
170
|
+
var inputErrorClassName = "border-failure-bg focus-visible:border-failure-bg focus-visible:ring-failure-bg";
|
|
171
|
+
function parsePositiveInt(raw) {
|
|
172
|
+
const trimmed = raw.trim();
|
|
173
|
+
if (trimmed === "") return null;
|
|
174
|
+
const n = Number(trimmed);
|
|
175
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
176
|
+
}
|
|
177
|
+
function slugifyModelId(raw) {
|
|
178
|
+
return raw.replace(/([a-zA-Z])(\d)/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 64).replace(/-+$/g, "");
|
|
179
|
+
}
|
|
180
|
+
var RequiredMark = () => /* @__PURE__ */ jsx2("span", { className: "ml-0.5 text-failure-bg", "aria-hidden": true, children: "*" });
|
|
181
|
+
var FieldError = ({ children }) => /* @__PURE__ */ jsx2("p", { className: "mt-1 text-xs text-failure-bg", children });
|
|
182
|
+
var FieldHelp = ({ children }) => /* @__PURE__ */ jsx2("p", { className: "mt-1 text-xs text-text-secondary", children });
|
|
183
|
+
var CustomModelProviderForm = ({
|
|
184
|
+
open,
|
|
185
|
+
onOpenChange,
|
|
186
|
+
onSubmit,
|
|
187
|
+
reasoningEffortOptions,
|
|
188
|
+
busy = false,
|
|
189
|
+
error,
|
|
190
|
+
isEditMode = false,
|
|
191
|
+
initialValues
|
|
192
|
+
}) => {
|
|
193
|
+
const [name, setName] = useState2(initialValues?.name ?? "");
|
|
194
|
+
const [baseUrl, setBaseUrl] = useState2(initialValues?.baseUrl ?? "");
|
|
195
|
+
const [apiKey, setApiKey] = useState2("");
|
|
196
|
+
const [models, setModels] = useState2(() => createModelRows(initialValues));
|
|
197
|
+
const [nameTouched, setNameTouched] = useState2(false);
|
|
198
|
+
const [baseUrlTouched, setBaseUrlTouched] = useState2(false);
|
|
199
|
+
const resetForm = () => {
|
|
200
|
+
setName(initialValues?.name ?? "");
|
|
201
|
+
setBaseUrl(initialValues?.baseUrl ?? "");
|
|
202
|
+
setApiKey("");
|
|
203
|
+
setModels(createModelRows(initialValues));
|
|
204
|
+
setNameTouched(false);
|
|
205
|
+
setBaseUrlTouched(false);
|
|
206
|
+
};
|
|
207
|
+
const handleOpenChange = (nextOpen) => {
|
|
208
|
+
if (!nextOpen) resetForm();
|
|
209
|
+
onOpenChange(nextOpen);
|
|
210
|
+
};
|
|
211
|
+
const updateModel = (index, patch) => {
|
|
212
|
+
setModels((current) => current.map((model, i) => i === index ? { ...model, ...patch } : model));
|
|
213
|
+
};
|
|
214
|
+
const markAllTouched = () => {
|
|
215
|
+
setNameTouched(true);
|
|
216
|
+
setBaseUrlTouched(true);
|
|
217
|
+
setModels(
|
|
218
|
+
(current) => current.map((model) => ({
|
|
219
|
+
...model,
|
|
220
|
+
idTouched: true,
|
|
221
|
+
contextTouched: true,
|
|
222
|
+
maxTouched: true
|
|
223
|
+
}))
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
const trimmedName = name.trim();
|
|
227
|
+
const nameError = trimmedName ? null : "Name is required.";
|
|
228
|
+
const trimmedBaseUrl = baseUrl.trim();
|
|
229
|
+
let baseUrlError = null;
|
|
230
|
+
if (!trimmedBaseUrl) {
|
|
231
|
+
baseUrlError = "Base URL is required.";
|
|
232
|
+
} else {
|
|
233
|
+
try {
|
|
234
|
+
new URL(trimmedBaseUrl);
|
|
235
|
+
} catch {
|
|
236
|
+
baseUrlError = "Enter a valid URL.";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const modelIdError = (model) => model.id.trim() ? null : "Model ID is required.";
|
|
240
|
+
const modelNameError = (model) => model.name.trim() ? null : "Model name is required.";
|
|
241
|
+
const modelContextError = (model) => parsePositiveInt(model.contextLength) == null ? "Set the model's context window." : null;
|
|
242
|
+
const modelMaxOutputError = (model) => parsePositiveInt(model.maxOutputTokens) == null ? "Set the max output tokens." : null;
|
|
243
|
+
const visibleValid = !nameError && !baseUrlError && models.every((model) => !modelIdError(model) && !modelNameError(model));
|
|
244
|
+
const showNameError = nameTouched && nameError;
|
|
245
|
+
const showBaseUrlError = baseUrlTouched && baseUrlError;
|
|
246
|
+
const handleSubmit = async () => {
|
|
247
|
+
markAllTouched();
|
|
248
|
+
if (!visibleValid || busy) return;
|
|
249
|
+
const incompleteIndex = models.findIndex((model) => modelContextError(model) || modelMaxOutputError(model));
|
|
250
|
+
if (incompleteIndex !== -1) {
|
|
251
|
+
updateModel(incompleteIndex, { advancedExpanded: true });
|
|
252
|
+
const target = models[incompleteIndex];
|
|
253
|
+
const field = target && modelContextError(target) ? "context-length" : "max-output-tokens";
|
|
254
|
+
setTimeout(() => {
|
|
255
|
+
const el = document.getElementById(`custom-provider-model-${incompleteIndex}-${field}`);
|
|
256
|
+
el?.scrollIntoView?.({ block: "center", behavior: "smooth" });
|
|
257
|
+
el?.focus();
|
|
258
|
+
}, 0);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
await onSubmit({
|
|
263
|
+
name: trimmedName,
|
|
264
|
+
baseUrl: trimmedBaseUrl,
|
|
265
|
+
apiKey: apiKey.trim(),
|
|
266
|
+
models: models.map((model) => {
|
|
267
|
+
const properties = {};
|
|
268
|
+
if (model.reasoningEfforts?.length) properties.reasoningEfforts = model.reasoningEfforts;
|
|
269
|
+
const contextLength = parsePositiveInt(model.contextLength);
|
|
270
|
+
if (contextLength != null) properties.contextLength = contextLength;
|
|
271
|
+
const maxOutputTokens = parsePositiveInt(model.maxOutputTokens);
|
|
272
|
+
if (maxOutputTokens != null) properties.maxOutputTokens = maxOutputTokens;
|
|
273
|
+
return {
|
|
274
|
+
id: model.id.trim(),
|
|
275
|
+
name: model.name.trim(),
|
|
276
|
+
...Object.keys(properties).length > 0 ? { properties } : {}
|
|
277
|
+
};
|
|
278
|
+
})
|
|
279
|
+
});
|
|
280
|
+
handleOpenChange(false);
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
return /* @__PURE__ */ jsx2(
|
|
285
|
+
CenteredModal,
|
|
286
|
+
{
|
|
287
|
+
open,
|
|
288
|
+
onOpenChange: handleOpenChange,
|
|
289
|
+
title: isEditMode ? `Edit ${initialValues?.name ?? name}` : "Add custom provider",
|
|
290
|
+
description: isEditMode ? "Update the endpoint, credentials, and models for this provider." : "Connect any OpenAI-compatible endpoint, whether it's a local model, a proxy, or your own hosted service.",
|
|
291
|
+
contentSized: true,
|
|
292
|
+
children: /* @__PURE__ */ jsxs2(
|
|
293
|
+
"form",
|
|
294
|
+
{
|
|
295
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
296
|
+
onSubmit: (event) => {
|
|
297
|
+
event.preventDefault();
|
|
298
|
+
void handleSubmit();
|
|
299
|
+
},
|
|
300
|
+
children: [
|
|
301
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-h-0 flex-1 space-y-4 overflow-y-auto px-5 pt-4 pb-8", children: [
|
|
302
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
303
|
+
/* @__PURE__ */ jsxs2("label", { htmlFor: "custom-provider-name", className: "mb-1.5 block text-sm font-medium text-text-primary", children: [
|
|
304
|
+
"Name",
|
|
305
|
+
/* @__PURE__ */ jsx2(RequiredMark, {})
|
|
306
|
+
] }),
|
|
307
|
+
/* @__PURE__ */ jsx2(
|
|
308
|
+
"input",
|
|
309
|
+
{
|
|
310
|
+
id: "custom-provider-name",
|
|
311
|
+
type: "text",
|
|
312
|
+
value: name,
|
|
313
|
+
onChange: (event) => setName(event.target.value),
|
|
314
|
+
onBlur: () => setNameTouched(true),
|
|
315
|
+
placeholder: "local-llama",
|
|
316
|
+
autoFocus: !isEditMode,
|
|
317
|
+
readOnly: isEditMode,
|
|
318
|
+
"aria-readonly": isEditMode,
|
|
319
|
+
"aria-invalid": showNameError ? true : void 0,
|
|
320
|
+
className: cn(
|
|
321
|
+
inputClassName2,
|
|
322
|
+
isEditMode && "cursor-not-allowed bg-secondary-bg/60 text-text-secondary",
|
|
323
|
+
showNameError && inputErrorClassName
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
),
|
|
327
|
+
isEditMode ? /* @__PURE__ */ jsx2(FieldHelp, { children: "Provider names cannot be changed after creation." }) : null,
|
|
328
|
+
showNameError ? /* @__PURE__ */ jsx2(FieldError, { children: nameError }) : null
|
|
329
|
+
] }),
|
|
330
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
331
|
+
/* @__PURE__ */ jsxs2("label", { htmlFor: "custom-provider-base-url", className: "mb-1.5 block text-sm font-medium text-text-primary", children: [
|
|
332
|
+
"Base URL",
|
|
333
|
+
/* @__PURE__ */ jsx2(RequiredMark, {})
|
|
334
|
+
] }),
|
|
335
|
+
/* @__PURE__ */ jsx2(
|
|
336
|
+
"input",
|
|
337
|
+
{
|
|
338
|
+
id: "custom-provider-base-url",
|
|
339
|
+
type: "text",
|
|
340
|
+
value: baseUrl,
|
|
341
|
+
onChange: (event) => setBaseUrl(event.target.value),
|
|
342
|
+
onBlur: () => setBaseUrlTouched(true),
|
|
343
|
+
placeholder: "http://localhost:11434/v1",
|
|
344
|
+
autoFocus: isEditMode,
|
|
345
|
+
"aria-invalid": showBaseUrlError ? true : void 0,
|
|
346
|
+
className: cn(inputClassName2, showBaseUrlError && inputErrorClassName)
|
|
347
|
+
}
|
|
348
|
+
),
|
|
349
|
+
showBaseUrlError ? /* @__PURE__ */ jsx2(FieldError, { children: baseUrlError }) : !trimmedBaseUrl ? /* @__PURE__ */ jsx2(FieldHelp, { children: "OpenAI-compatible endpoint, usually ending in /v1." }) : null
|
|
350
|
+
] }),
|
|
351
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
352
|
+
/* @__PURE__ */ jsxs2("label", { htmlFor: "custom-provider-api-key", className: "mb-1.5 block text-sm font-medium text-text-primary", children: [
|
|
353
|
+
"API key",
|
|
354
|
+
/* @__PURE__ */ jsx2("span", { className: "font-normal text-text-secondary", children: " (optional)" })
|
|
355
|
+
] }),
|
|
356
|
+
/* @__PURE__ */ jsx2(
|
|
357
|
+
"input",
|
|
358
|
+
{
|
|
359
|
+
id: "custom-provider-api-key",
|
|
360
|
+
type: "password",
|
|
361
|
+
value: apiKey,
|
|
362
|
+
onChange: (event) => setApiKey(event.target.value),
|
|
363
|
+
placeholder: "sk-\u2026",
|
|
364
|
+
className: inputClassName2
|
|
365
|
+
}
|
|
366
|
+
),
|
|
367
|
+
/* @__PURE__ */ jsx2(FieldHelp, { children: isEditMode ? "Leave blank to keep the saved key, or enter a new key to replace it." : "Leave blank if your endpoint needs no key (e.g. a local model)." })
|
|
368
|
+
] }),
|
|
369
|
+
/* @__PURE__ */ jsxs2("fieldset", { className: "m-0 min-w-0 border-0 p-0", children: [
|
|
370
|
+
/* @__PURE__ */ jsxs2("legend", { className: "float-left mb-2 w-full p-0 text-sm font-medium text-text-primary", children: [
|
|
371
|
+
"Models",
|
|
372
|
+
/* @__PURE__ */ jsx2(RequiredMark, {})
|
|
373
|
+
] }),
|
|
374
|
+
/* @__PURE__ */ jsx2("div", { className: "clear-both", children: models.map((model, index) => {
|
|
375
|
+
const idError = modelIdError(model);
|
|
376
|
+
const showIdError = model.idTouched && idError;
|
|
377
|
+
const nameFieldError = modelNameError(model);
|
|
378
|
+
const showNameFieldError = !!nameFieldError && model.nameDirty;
|
|
379
|
+
const contextError = modelContextError(model);
|
|
380
|
+
const maxError = modelMaxOutputError(model);
|
|
381
|
+
const showContextError = model.contextTouched && contextError;
|
|
382
|
+
const showMaxError = model.maxTouched && maxError;
|
|
383
|
+
return /* @__PURE__ */ jsxs2(
|
|
384
|
+
"div",
|
|
385
|
+
{
|
|
386
|
+
className: cn("pb-6", index > 0 ? "border-t border-border pt-6" : "pt-1"),
|
|
387
|
+
style: index > 0 ? { borderTopWidth: "0.5px" } : void 0,
|
|
388
|
+
children: [
|
|
389
|
+
/* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-[1fr_1fr_2rem] items-end gap-2", children: [
|
|
390
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
|
|
391
|
+
/* @__PURE__ */ jsx2(
|
|
392
|
+
"label",
|
|
393
|
+
{
|
|
394
|
+
htmlFor: `custom-provider-model-${index}-id`,
|
|
395
|
+
className: "mb-1 block text-xs font-normal text-text-secondary",
|
|
396
|
+
children: "Model ID"
|
|
397
|
+
}
|
|
398
|
+
),
|
|
399
|
+
/* @__PURE__ */ jsx2(
|
|
400
|
+
"input",
|
|
401
|
+
{
|
|
402
|
+
id: `custom-provider-model-${index}-id`,
|
|
403
|
+
type: "text",
|
|
404
|
+
value: model.id,
|
|
405
|
+
onChange: (event) => {
|
|
406
|
+
const id = event.target.value;
|
|
407
|
+
updateModel(index, {
|
|
408
|
+
id,
|
|
409
|
+
...model.nameDirty ? {} : { name: slugifyModelId(id) }
|
|
410
|
+
});
|
|
411
|
+
},
|
|
412
|
+
onBlur: () => updateModel(index, { idTouched: true }),
|
|
413
|
+
placeholder: "llama3.1:70b",
|
|
414
|
+
"aria-invalid": showIdError ? true : void 0,
|
|
415
|
+
className: cn(inputClassName2, "font-mono", showIdError && inputErrorClassName)
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
] }),
|
|
419
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
|
|
420
|
+
/* @__PURE__ */ jsx2(
|
|
421
|
+
"label",
|
|
422
|
+
{
|
|
423
|
+
htmlFor: `custom-provider-model-${index}-name`,
|
|
424
|
+
className: "mb-1 block text-xs font-normal text-text-secondary",
|
|
425
|
+
children: "Model name"
|
|
426
|
+
}
|
|
427
|
+
),
|
|
428
|
+
/* @__PURE__ */ jsx2(
|
|
429
|
+
"input",
|
|
430
|
+
{
|
|
431
|
+
id: `custom-provider-model-${index}-name`,
|
|
432
|
+
type: "text",
|
|
433
|
+
value: model.name,
|
|
434
|
+
onChange: (event) => updateModel(index, { name: event.target.value, nameDirty: true }),
|
|
435
|
+
placeholder: "llama-3-1-70b",
|
|
436
|
+
"aria-invalid": showNameFieldError ? true : void 0,
|
|
437
|
+
className: cn(inputClassName2, showNameFieldError && inputErrorClassName)
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
] }),
|
|
441
|
+
/* @__PURE__ */ jsx2(
|
|
442
|
+
"button",
|
|
443
|
+
{
|
|
444
|
+
type: "button",
|
|
445
|
+
"aria-label": `Remove model ${index + 1}`,
|
|
446
|
+
disabled: models.length === 1 || busy,
|
|
447
|
+
className: "mb-0.5 flex size-9 items-center justify-center rounded-md text-text-secondary transition-colors hover:text-text-primary disabled:opacity-40",
|
|
448
|
+
onClick: () => setModels((current) => current.filter((_, i) => i !== index)),
|
|
449
|
+
children: /* @__PURE__ */ jsx2(Icon, { name: "trash", className: "size-4" })
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
] }),
|
|
453
|
+
showIdError ? /* @__PURE__ */ jsx2(FieldError, { children: idError }) : null,
|
|
454
|
+
showNameFieldError ? /* @__PURE__ */ jsx2(FieldError, { children: nameFieldError }) : null,
|
|
455
|
+
/* @__PURE__ */ jsxs2("div", { className: "mt-2", children: [
|
|
456
|
+
/* @__PURE__ */ jsxs2(
|
|
457
|
+
"button",
|
|
458
|
+
{
|
|
459
|
+
type: "button",
|
|
460
|
+
"aria-expanded": model.advancedExpanded,
|
|
461
|
+
"aria-controls": `custom-provider-model-${index}-advanced`,
|
|
462
|
+
className: "flex cursor-pointer items-center gap-1.5 text-sm font-medium text-text-primary",
|
|
463
|
+
onClick: () => updateModel(index, { advancedExpanded: !model.advancedExpanded }),
|
|
464
|
+
children: [
|
|
465
|
+
/* @__PURE__ */ jsx2(
|
|
466
|
+
Icon,
|
|
467
|
+
{
|
|
468
|
+
name: "chevron-down",
|
|
469
|
+
className: cn(
|
|
470
|
+
"size-4 text-text-secondary transition-transform",
|
|
471
|
+
model.advancedExpanded ? "" : "-rotate-90"
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
),
|
|
475
|
+
"Advanced"
|
|
476
|
+
]
|
|
477
|
+
}
|
|
478
|
+
),
|
|
479
|
+
model.advancedExpanded ? /* @__PURE__ */ jsxs2("div", { id: `custom-provider-model-${index}-advanced`, className: "mt-3 space-y-4 pl-6", children: [
|
|
480
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
481
|
+
/* @__PURE__ */ jsxs2(
|
|
482
|
+
"label",
|
|
483
|
+
{
|
|
484
|
+
htmlFor: `custom-provider-model-${index}-context-length`,
|
|
485
|
+
className: "mb-1.5 block text-xs font-medium text-text-secondary",
|
|
486
|
+
children: [
|
|
487
|
+
"Context length",
|
|
488
|
+
/* @__PURE__ */ jsx2(RequiredMark, {})
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
),
|
|
492
|
+
/* @__PURE__ */ jsx2(
|
|
493
|
+
"input",
|
|
494
|
+
{
|
|
495
|
+
id: `custom-provider-model-${index}-context-length`,
|
|
496
|
+
type: "number",
|
|
497
|
+
min: 1,
|
|
498
|
+
inputMode: "numeric",
|
|
499
|
+
value: model.contextLength,
|
|
500
|
+
onChange: (event) => updateModel(index, { contextLength: event.target.value }),
|
|
501
|
+
onBlur: () => updateModel(index, { contextTouched: true }),
|
|
502
|
+
placeholder: PLACEHOLDER_CONTEXT_LENGTH,
|
|
503
|
+
"aria-invalid": showContextError ? true : void 0,
|
|
504
|
+
className: cn(inputClassName2, showContextError && inputErrorClassName)
|
|
505
|
+
}
|
|
506
|
+
),
|
|
507
|
+
showContextError ? /* @__PURE__ */ jsx2(FieldError, { children: contextError }) : model.contextLength.trim() === "" ? /* @__PURE__ */ jsx2(FieldHelp, { children: "Prevents the context window from overflowing during a run." }) : null
|
|
508
|
+
] }),
|
|
509
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
510
|
+
/* @__PURE__ */ jsxs2(
|
|
511
|
+
"label",
|
|
512
|
+
{
|
|
513
|
+
htmlFor: `custom-provider-model-${index}-max-output-tokens`,
|
|
514
|
+
className: "mb-1.5 block text-xs font-medium text-text-secondary",
|
|
515
|
+
children: [
|
|
516
|
+
"Max output tokens",
|
|
517
|
+
/* @__PURE__ */ jsx2(RequiredMark, {})
|
|
518
|
+
]
|
|
519
|
+
}
|
|
520
|
+
),
|
|
521
|
+
/* @__PURE__ */ jsx2(
|
|
522
|
+
"input",
|
|
523
|
+
{
|
|
524
|
+
id: `custom-provider-model-${index}-max-output-tokens`,
|
|
525
|
+
type: "number",
|
|
526
|
+
min: 1,
|
|
527
|
+
inputMode: "numeric",
|
|
528
|
+
value: model.maxOutputTokens,
|
|
529
|
+
onChange: (event) => updateModel(index, { maxOutputTokens: event.target.value }),
|
|
530
|
+
onBlur: () => updateModel(index, { maxTouched: true }),
|
|
531
|
+
placeholder: PLACEHOLDER_MAX_OUTPUT_TOKENS,
|
|
532
|
+
"aria-invalid": showMaxError ? true : void 0,
|
|
533
|
+
className: cn(inputClassName2, showMaxError && inputErrorClassName)
|
|
534
|
+
}
|
|
535
|
+
),
|
|
536
|
+
showMaxError ? /* @__PURE__ */ jsx2(FieldError, { children: maxError }) : model.maxOutputTokens.trim() === "" ? /* @__PURE__ */ jsx2(FieldHelp, { children: "Caps tokens per model call." }) : null
|
|
537
|
+
] }),
|
|
538
|
+
reasoningEffortOptions && reasoningEffortOptions.length > 0 ? /* @__PURE__ */ jsxs2("div", { children: [
|
|
539
|
+
/* @__PURE__ */ jsxs2("label", { className: "flex w-fit cursor-pointer items-center gap-2 select-none", children: [
|
|
540
|
+
/* @__PURE__ */ jsx2(
|
|
541
|
+
"input",
|
|
542
|
+
{
|
|
543
|
+
type: "checkbox",
|
|
544
|
+
className: "size-4 shrink-0 cursor-pointer accent-primary-button-bg disabled:cursor-not-allowed disabled:opacity-50",
|
|
545
|
+
checked: model.reasoningEfforts !== void 0,
|
|
546
|
+
"aria-label": `Enable reasoning effort for model ${index + 1}`,
|
|
547
|
+
disabled: busy,
|
|
548
|
+
onChange: (event) => updateModel(index, {
|
|
549
|
+
reasoningEfforts: event.target.checked ? [] : void 0
|
|
550
|
+
})
|
|
551
|
+
}
|
|
552
|
+
),
|
|
553
|
+
/* @__PURE__ */ jsx2("span", { className: "text-text-secondary text-xs font-medium", children: "Reasoning effort" })
|
|
554
|
+
] }),
|
|
555
|
+
model.reasoningEfforts !== void 0 ? /* @__PURE__ */ jsx2(
|
|
556
|
+
"div",
|
|
557
|
+
{
|
|
558
|
+
role: "group",
|
|
559
|
+
"aria-label": `Supported reasoning efforts for model ${index + 1}`,
|
|
560
|
+
className: "mt-2 flex flex-wrap gap-2",
|
|
561
|
+
children: reasoningEffortOptions.map((effort) => {
|
|
562
|
+
const selected = model.reasoningEfforts?.includes(effort) ?? false;
|
|
563
|
+
return /* @__PURE__ */ jsx2(
|
|
564
|
+
"button",
|
|
565
|
+
{
|
|
566
|
+
type: "button",
|
|
567
|
+
"aria-pressed": selected,
|
|
568
|
+
disabled: busy,
|
|
569
|
+
className: cn(
|
|
570
|
+
"rounded-full border px-3 py-1 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring/40 disabled:opacity-50",
|
|
571
|
+
selected ? "border-primary-button-bg bg-primary-button-bg text-primary-button-text" : "border-border text-text-secondary hover:bg-ghost-button-hover"
|
|
572
|
+
),
|
|
573
|
+
onClick: () => updateModel(index, {
|
|
574
|
+
reasoningEfforts: selected ? model.reasoningEfforts?.filter((item) => item !== effort) : [...model.reasoningEfforts ?? [], effort]
|
|
575
|
+
}),
|
|
576
|
+
children: effort
|
|
577
|
+
},
|
|
578
|
+
effort
|
|
579
|
+
);
|
|
580
|
+
})
|
|
581
|
+
}
|
|
582
|
+
) : null
|
|
583
|
+
] }) : null
|
|
584
|
+
] }) : null
|
|
585
|
+
] })
|
|
586
|
+
]
|
|
587
|
+
},
|
|
588
|
+
index
|
|
589
|
+
);
|
|
590
|
+
}) }),
|
|
591
|
+
/* @__PURE__ */ jsxs2(
|
|
592
|
+
Button,
|
|
593
|
+
{
|
|
594
|
+
variant: "secondary",
|
|
595
|
+
size: "sm",
|
|
596
|
+
type: "button",
|
|
597
|
+
className: "mt-4 w-fit",
|
|
598
|
+
onClick: () => setModels((current) => [...current, createEmptyModelRow()]),
|
|
599
|
+
children: [
|
|
600
|
+
/* @__PURE__ */ jsx2(Icon, { name: "plus", className: "size-3.5" }),
|
|
601
|
+
"Add model"
|
|
602
|
+
]
|
|
603
|
+
}
|
|
604
|
+
)
|
|
605
|
+
] })
|
|
606
|
+
] }),
|
|
607
|
+
/* @__PURE__ */ jsxs2("div", { className: "shrink-0 space-y-3 border-t border-border px-5 py-4", children: [
|
|
608
|
+
error ? /* @__PURE__ */ jsx2("p", { className: "text-failure-bg text-sm", children: error }) : null,
|
|
609
|
+
/* @__PURE__ */ jsx2(Button, { type: "submit", size: "lg", disabled: !visibleValid || busy, className: "w-full", children: isEditMode ? "Save changes" : "Add provider" })
|
|
610
|
+
] })
|
|
611
|
+
]
|
|
612
|
+
}
|
|
613
|
+
)
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
};
|
|
617
|
+
var CustomModelProviderForm_default = CustomModelProviderForm;
|
|
618
|
+
|
|
619
|
+
// src/containers/SettingsBuilder/ModelSettings.tsx
|
|
620
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
621
|
+
function catalogBaseUrl(provider) {
|
|
622
|
+
if ("baseUrl" in provider && typeof provider.baseUrl === "string") {
|
|
623
|
+
return provider.baseUrl;
|
|
624
|
+
}
|
|
625
|
+
return "";
|
|
626
|
+
}
|
|
627
|
+
var ModelSettings = () => {
|
|
628
|
+
const { modelCatalog } = useCatalogServer();
|
|
629
|
+
const toaster = useToasterOptional();
|
|
630
|
+
const [query, setQuery] = useState3("");
|
|
631
|
+
const [configured, setConfigured] = useState3([]);
|
|
632
|
+
const [catalog, setCatalog] = useState3([]);
|
|
633
|
+
const [loading, setLoading] = useState3(true);
|
|
634
|
+
const [error, setError] = useState3(null);
|
|
635
|
+
const [formError, setFormError] = useState3(null);
|
|
636
|
+
const [busy, setBusy] = useState3(false);
|
|
637
|
+
const [editingProviderId, setEditingProviderId] = useState3(null);
|
|
638
|
+
const [editingCatalogType, setEditingCatalogType] = useState3(null);
|
|
639
|
+
const [keyError, setKeyError] = useState3(null);
|
|
640
|
+
const [customProviderOpen, setCustomProviderOpen] = useState3(false);
|
|
641
|
+
const [customProviderToEdit, setCustomProviderToEdit] = useState3(null);
|
|
642
|
+
const modelProviderIconMap = useMemo(() => {
|
|
643
|
+
return (catalog ?? []).reduce(
|
|
644
|
+
(acc, entry) => {
|
|
645
|
+
acc[entry.name] = entry.logo ?? "";
|
|
646
|
+
return acc;
|
|
647
|
+
},
|
|
648
|
+
{}
|
|
649
|
+
);
|
|
650
|
+
}, [catalog]);
|
|
651
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
652
|
+
const refresh = useCallback(async () => {
|
|
653
|
+
setLoading(true);
|
|
654
|
+
setError(null);
|
|
655
|
+
try {
|
|
656
|
+
const [listed, available] = await Promise.all([
|
|
657
|
+
modelCatalog.listModelProviders(),
|
|
658
|
+
modelCatalog.getModelProviderCatalog()
|
|
659
|
+
]);
|
|
660
|
+
setConfigured(listed);
|
|
661
|
+
setCatalog(available);
|
|
662
|
+
} catch (err) {
|
|
663
|
+
setError(getErrorMessage(err, "Failed to load model providers"));
|
|
664
|
+
} finally {
|
|
665
|
+
setLoading(false);
|
|
666
|
+
}
|
|
667
|
+
}, [modelCatalog]);
|
|
668
|
+
useEffect2(() => {
|
|
669
|
+
void refresh();
|
|
670
|
+
}, [refresh]);
|
|
671
|
+
const configuredTypes = useMemo(() => new Set(configured.map((provider) => provider.type)), [configured]);
|
|
672
|
+
const matchingConfigured = useMemo(
|
|
673
|
+
() => configured.filter(
|
|
674
|
+
(provider) => provider.name?.toLowerCase().includes(normalizedQuery) || provider.models.some(
|
|
675
|
+
(model) => model.name.toLowerCase().includes(normalizedQuery) || model.id.toLowerCase().includes(normalizedQuery)
|
|
676
|
+
)
|
|
677
|
+
),
|
|
678
|
+
[configured, normalizedQuery]
|
|
679
|
+
);
|
|
680
|
+
const matchingAvailable = useMemo(
|
|
681
|
+
() => catalog.filter(
|
|
682
|
+
(provider) => provider.type !== "custom" && !configuredTypes.has(provider.type) && (provider.name.toLowerCase().includes(normalizedQuery) || provider.models.some(
|
|
683
|
+
(model) => model.name.toLowerCase().includes(normalizedQuery) || model.id.toLowerCase().includes(normalizedQuery)
|
|
684
|
+
))
|
|
685
|
+
),
|
|
686
|
+
[catalog, configuredTypes, normalizedQuery]
|
|
687
|
+
);
|
|
688
|
+
const supportedReasoningEfforts = useMemo(() => {
|
|
689
|
+
return catalog.find((provider) => provider.type === "custom")?.supportedReasoningEfforts;
|
|
690
|
+
}, [catalog]);
|
|
691
|
+
const catalogModelsByType = useMemo(() => {
|
|
692
|
+
const map = /* @__PURE__ */ new Map();
|
|
693
|
+
for (const entry of catalog) {
|
|
694
|
+
map.set(entry.type, entry.models);
|
|
695
|
+
}
|
|
696
|
+
return map;
|
|
697
|
+
}, [catalog]);
|
|
698
|
+
const closeKeyEditor = () => {
|
|
699
|
+
setEditingProviderId(null);
|
|
700
|
+
setEditingCatalogType(null);
|
|
701
|
+
setKeyError(null);
|
|
702
|
+
};
|
|
703
|
+
const runMutation = async (fn, onError) => {
|
|
704
|
+
setBusy(true);
|
|
705
|
+
setError(null);
|
|
706
|
+
try {
|
|
707
|
+
await fn();
|
|
708
|
+
await refresh();
|
|
709
|
+
closeKeyEditor();
|
|
710
|
+
} catch (err) {
|
|
711
|
+
if (onError) {
|
|
712
|
+
onError(err);
|
|
713
|
+
} else if (toaster == null) {
|
|
714
|
+
setError(getErrorMessage(err, "Request failed"));
|
|
715
|
+
} else {
|
|
716
|
+
toaster.showError(err);
|
|
717
|
+
}
|
|
718
|
+
throw err;
|
|
719
|
+
} finally {
|
|
720
|
+
setBusy(false);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
const handleCreateFromCatalog = async (entry, draft) => {
|
|
724
|
+
setKeyError(null);
|
|
725
|
+
await runMutation(
|
|
726
|
+
async () => {
|
|
727
|
+
await modelCatalog.createModelProvider({
|
|
728
|
+
type: entry.type,
|
|
729
|
+
name: entry.name,
|
|
730
|
+
...draft.baseUrl ? { baseUrl: draft.baseUrl } : {},
|
|
731
|
+
apiKey: draft.apiKey,
|
|
732
|
+
models: entry.models
|
|
733
|
+
});
|
|
734
|
+
},
|
|
735
|
+
(err) => setKeyError(getErrorMessage(err, "Request failed"))
|
|
736
|
+
);
|
|
737
|
+
};
|
|
738
|
+
const handleReplaceKey = async (provider, draft) => {
|
|
739
|
+
setKeyError(null);
|
|
740
|
+
await runMutation(
|
|
741
|
+
async () => {
|
|
742
|
+
await modelCatalog.updateModelProvider({
|
|
743
|
+
id: provider.id,
|
|
744
|
+
type: provider.type,
|
|
745
|
+
name: provider.name,
|
|
746
|
+
...draft.baseUrl ? { baseUrl: draft.baseUrl } : {},
|
|
747
|
+
// Empty key means "keep the existing one" for hosts that support it.
|
|
748
|
+
apiKey: draft.apiKey,
|
|
749
|
+
models: provider.models
|
|
750
|
+
});
|
|
751
|
+
},
|
|
752
|
+
(err) => setKeyError(getErrorMessage(err, "Request failed"))
|
|
753
|
+
);
|
|
754
|
+
toaster?.showSuccess({ title: `${provider.name} updated` });
|
|
755
|
+
};
|
|
756
|
+
const handleRemoveProvider = (provider) => {
|
|
757
|
+
if (!modelCatalog.deleteModelProvider) return;
|
|
758
|
+
void runMutation(async () => {
|
|
759
|
+
await modelCatalog.deleteModelProvider({ id: provider.id });
|
|
760
|
+
}).catch(() => {
|
|
761
|
+
});
|
|
762
|
+
};
|
|
763
|
+
const handleUpdateModels = (provider, models) => {
|
|
764
|
+
void runMutation(async () => {
|
|
765
|
+
await modelCatalog.updateModelProvider({
|
|
766
|
+
id: provider.id,
|
|
767
|
+
type: provider.type,
|
|
768
|
+
name: provider.name,
|
|
769
|
+
...provider.baseUrl ? { baseUrl: provider.baseUrl } : {},
|
|
770
|
+
apiKey: "",
|
|
771
|
+
models
|
|
772
|
+
});
|
|
773
|
+
}).catch(() => {
|
|
774
|
+
});
|
|
775
|
+
};
|
|
776
|
+
const handleAddCustomProvider = async (draft) => {
|
|
777
|
+
setFormError(null);
|
|
778
|
+
await runMutation(
|
|
779
|
+
async () => {
|
|
780
|
+
await modelCatalog.createModelProvider({
|
|
781
|
+
type: "custom",
|
|
782
|
+
name: draft.name,
|
|
783
|
+
baseUrl: draft.baseUrl,
|
|
784
|
+
apiKey: draft.apiKey,
|
|
785
|
+
models: draft.models
|
|
786
|
+
});
|
|
787
|
+
},
|
|
788
|
+
(err) => setFormError(getErrorMessage(err, "Request failed"))
|
|
789
|
+
);
|
|
790
|
+
setTimeout(() => {
|
|
791
|
+
toaster?.showSuccess({ title: `${draft.name} added` });
|
|
792
|
+
}, 0);
|
|
793
|
+
};
|
|
794
|
+
const handleUpdateCustomProvider = async (draft) => {
|
|
795
|
+
if (!customProviderToEdit) return;
|
|
796
|
+
const provider = customProviderToEdit;
|
|
797
|
+
setFormError(null);
|
|
798
|
+
await runMutation(
|
|
799
|
+
async () => {
|
|
800
|
+
await modelCatalog.updateModelProvider({
|
|
801
|
+
id: provider.id,
|
|
802
|
+
type: provider.type,
|
|
803
|
+
name: provider.name,
|
|
804
|
+
baseUrl: draft.baseUrl,
|
|
805
|
+
apiKey: draft.apiKey,
|
|
806
|
+
models: draft.models
|
|
807
|
+
});
|
|
808
|
+
},
|
|
809
|
+
(err) => setFormError(getErrorMessage(err, "Request failed"))
|
|
810
|
+
);
|
|
811
|
+
setTimeout(() => {
|
|
812
|
+
toaster?.showSuccess({
|
|
813
|
+
title: "Model provider updated",
|
|
814
|
+
description: `${provider.name} was updated successfully.`
|
|
815
|
+
});
|
|
816
|
+
}, 0);
|
|
817
|
+
};
|
|
818
|
+
const customProviderInitialValues = customProviderToEdit ? {
|
|
819
|
+
name: customProviderToEdit.name,
|
|
820
|
+
baseUrl: customProviderToEdit.baseUrl ?? "",
|
|
821
|
+
models: customProviderToEdit.models
|
|
822
|
+
} : void 0;
|
|
823
|
+
const editingProvider = editingProviderId != null ? configured.find((item) => item.id === editingProviderId) ?? null : null;
|
|
824
|
+
const editingCatalogEntry = editingCatalogType != null ? catalog.find((item) => item.type === editingCatalogType) ?? null : null;
|
|
825
|
+
const keyModalOpen = editingProvider != null || editingCatalogEntry != null;
|
|
826
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
827
|
+
/* @__PURE__ */ jsx3("h3", { className: "text-xl font-semibold tracking-tight text-text-primary", children: "Models" }),
|
|
828
|
+
/* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-text-secondary", children: "Connect providers and choose which models are available." }),
|
|
829
|
+
error ? /* @__PURE__ */ jsx3("p", { className: "mt-3 rounded-md border border-failure-bg/30 bg-failure-bg/10 px-3 py-2 text-sm text-failure-bg", children: error }) : null,
|
|
830
|
+
/* @__PURE__ */ jsx3("div", { className: "mt-4 flex-1 overflow-y-hidden", children: /* @__PURE__ */ jsxs3("div", { className: "flex h-full flex-col gap-3", children: [
|
|
831
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
|
|
832
|
+
/* @__PURE__ */ jsx3("div", { className: "flex-1", children: /* @__PURE__ */ jsx3(SearchInput_default, { query, setQuery, placeholder: "Search providers and models" }) }),
|
|
833
|
+
/* @__PURE__ */ jsxs3(
|
|
834
|
+
Button,
|
|
835
|
+
{
|
|
836
|
+
variant: "secondary",
|
|
837
|
+
type: "button",
|
|
838
|
+
onClick: () => {
|
|
839
|
+
setFormError(null);
|
|
840
|
+
setCustomProviderToEdit(null);
|
|
841
|
+
setCustomProviderOpen(true);
|
|
842
|
+
},
|
|
843
|
+
children: [
|
|
844
|
+
/* @__PURE__ */ jsx3(Icon, { name: "plus", size: "1rem", className: "mr-1" }),
|
|
845
|
+
"Add Custom Provider"
|
|
846
|
+
]
|
|
847
|
+
}
|
|
848
|
+
)
|
|
849
|
+
] }),
|
|
850
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-1 flex-col gap-2 overflow-y-auto", children: [
|
|
851
|
+
loading ? /* @__PURE__ */ jsx3("p", { className: "py-8 text-center text-sm text-text-secondary", children: "Loading\u2026" }) : null,
|
|
852
|
+
!loading && matchingConfigured.length > 0 ? /* @__PURE__ */ jsxs3("section", { "aria-labelledby": "configured-providers-heading", children: [
|
|
853
|
+
/* @__PURE__ */ jsx3(
|
|
854
|
+
"div",
|
|
855
|
+
{
|
|
856
|
+
id: "configured-providers-heading",
|
|
857
|
+
className: "mb-2 text-[0.8125rem] font-semibold uppercase text-text-secondary",
|
|
858
|
+
children: "Configured"
|
|
859
|
+
}
|
|
860
|
+
),
|
|
861
|
+
/* @__PURE__ */ jsx3("div", { className: "overflow-hidden rounded-xl border border-border bg-card-bg", children: matchingConfigured.map((provider) => {
|
|
862
|
+
const catalogModels = catalogModelsByType.get(provider.type) ?? [];
|
|
863
|
+
const configuredIds = new Set(provider.models.map((model) => model.id));
|
|
864
|
+
const availableModels = catalogModels.filter((model) => !configuredIds.has(model.id));
|
|
865
|
+
const logoSrc = modelProviderIconMap[provider.name];
|
|
866
|
+
return /* @__PURE__ */ jsxs3("article", { className: "border-b border-border p-3 last:border-b-0", children: [
|
|
867
|
+
/* @__PURE__ */ jsxs3("header", { className: "flex flex-col gap-3 sm:flex-row sm:items-center", children: [
|
|
868
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
|
|
869
|
+
/* @__PURE__ */ jsx3(
|
|
870
|
+
"div",
|
|
871
|
+
{
|
|
872
|
+
className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border bg-secondary-bg text-text-primary",
|
|
873
|
+
"aria-hidden": true,
|
|
874
|
+
children: logoSrc ? /* @__PURE__ */ jsx3(CatalogLogo, { src: logoSrc, alt: provider.name, className: "size-4.5" }) : /* @__PURE__ */ jsx3(Icon, { name: "cpu", className: "size-4.5" })
|
|
875
|
+
}
|
|
876
|
+
),
|
|
877
|
+
/* @__PURE__ */ jsx3("h5", { className: "truncate text-base font-medium text-text-primary", children: provider.name })
|
|
878
|
+
] }),
|
|
879
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2.5 sm:justify-end", children: [
|
|
880
|
+
/* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1.5 rounded-full border border-border bg-secondary-bg/40 px-2 py-0.5 text-xs font-medium text-success-bg", children: [
|
|
881
|
+
/* @__PURE__ */ jsx3("span", { className: "h-1.5 w-1.5 rounded-full bg-success-bg" }),
|
|
882
|
+
"Connected"
|
|
883
|
+
] }),
|
|
884
|
+
/* @__PURE__ */ jsxs3(
|
|
885
|
+
Button,
|
|
886
|
+
{
|
|
887
|
+
variant: "secondary",
|
|
888
|
+
size: "sm",
|
|
889
|
+
className: "text-[0.8125rem]",
|
|
890
|
+
type: "button",
|
|
891
|
+
"aria-label": `Edit ${provider.name}`,
|
|
892
|
+
title: `Edit ${provider.name}`,
|
|
893
|
+
disabled: busy,
|
|
894
|
+
onClick: () => {
|
|
895
|
+
if (provider.type === "custom") {
|
|
896
|
+
closeKeyEditor();
|
|
897
|
+
setFormError(null);
|
|
898
|
+
setCustomProviderToEdit(provider);
|
|
899
|
+
setCustomProviderOpen(true);
|
|
900
|
+
} else {
|
|
901
|
+
setKeyError(null);
|
|
902
|
+
setEditingCatalogType(null);
|
|
903
|
+
setEditingProviderId(provider.id);
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
children: [
|
|
907
|
+
/* @__PURE__ */ jsx3(Icon, { name: "wrench", className: "size-3.5" }),
|
|
908
|
+
"Edit"
|
|
909
|
+
]
|
|
910
|
+
}
|
|
911
|
+
),
|
|
912
|
+
modelCatalog.deleteModelProvider ? /* @__PURE__ */ jsx3(
|
|
913
|
+
Button,
|
|
914
|
+
{
|
|
915
|
+
variant: "outline",
|
|
916
|
+
size: "sm",
|
|
917
|
+
className: "transition-colors hover:bg-failure-bg/10 hover:text-failure-bg",
|
|
918
|
+
type: "button",
|
|
919
|
+
disabled: busy,
|
|
920
|
+
onClick: () => {
|
|
921
|
+
handleRemoveProvider(provider);
|
|
922
|
+
},
|
|
923
|
+
children: "Remove"
|
|
924
|
+
}
|
|
925
|
+
) : null
|
|
926
|
+
] })
|
|
927
|
+
] }),
|
|
928
|
+
/* @__PURE__ */ jsxs3("div", { className: "mt-3 overflow-hidden rounded-lg border border-border bg-secondary-bg/30", children: [
|
|
929
|
+
provider.models.map((model) => /* @__PURE__ */ jsxs3(
|
|
930
|
+
"div",
|
|
931
|
+
{
|
|
932
|
+
className: "flex min-h-10 items-center gap-3 border-b border-border px-3 py-2 text-sm text-text-primary last:border-b-0",
|
|
933
|
+
children: [
|
|
934
|
+
/* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 truncate", children: model.name }),
|
|
935
|
+
/* @__PURE__ */ jsx3(
|
|
936
|
+
"button",
|
|
937
|
+
{
|
|
938
|
+
type: "button",
|
|
939
|
+
"aria-label": `Remove ${model.name}`,
|
|
940
|
+
disabled: busy,
|
|
941
|
+
className: "inline-flex size-7 shrink-0 items-center justify-center rounded-md text-text-secondary hover:bg-ghost-button-hover hover:text-ghost-button-text focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-focus-ring disabled:opacity-50",
|
|
942
|
+
onClick: () => {
|
|
943
|
+
handleUpdateModels(
|
|
944
|
+
provider,
|
|
945
|
+
provider.models.filter((item) => item.id !== model.id)
|
|
946
|
+
);
|
|
947
|
+
},
|
|
948
|
+
children: /* @__PURE__ */ jsx3(Icon, { name: "trash", className: "size-3.5" })
|
|
949
|
+
}
|
|
950
|
+
)
|
|
951
|
+
]
|
|
952
|
+
},
|
|
953
|
+
model.id
|
|
954
|
+
)),
|
|
955
|
+
availableModels.length > 0 ? /* @__PURE__ */ jsxs3("section", { children: [
|
|
956
|
+
/* @__PURE__ */ jsxs3("div", { className: "border-b border-border bg-secondary-bg/40 px-3 py-2 text-xs font-semibold uppercase tracking-wide text-text-secondary", children: [
|
|
957
|
+
"Available to add \xB7 ",
|
|
958
|
+
availableModels.length
|
|
959
|
+
] }),
|
|
960
|
+
availableModels.map((model) => /* @__PURE__ */ jsxs3(
|
|
961
|
+
"div",
|
|
962
|
+
{
|
|
963
|
+
className: "flex min-h-12 items-center gap-3 border-b border-border px-3 py-2 text-sm text-text-primary last:border-b-0",
|
|
964
|
+
children: [
|
|
965
|
+
/* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 truncate", children: model.name }),
|
|
966
|
+
/* @__PURE__ */ jsx3(
|
|
967
|
+
Button,
|
|
968
|
+
{
|
|
969
|
+
variant: "outline",
|
|
970
|
+
size: "sm",
|
|
971
|
+
type: "button",
|
|
972
|
+
disabled: busy,
|
|
973
|
+
onClick: () => {
|
|
974
|
+
handleUpdateModels(provider, [...provider.models, model]);
|
|
975
|
+
},
|
|
976
|
+
children: "Add"
|
|
977
|
+
}
|
|
978
|
+
)
|
|
979
|
+
]
|
|
980
|
+
},
|
|
981
|
+
model.id
|
|
982
|
+
))
|
|
983
|
+
] }) : null
|
|
984
|
+
] })
|
|
985
|
+
] }, provider.id);
|
|
986
|
+
}) })
|
|
987
|
+
] }) : null,
|
|
988
|
+
!loading && matchingAvailable.length > 0 ? /* @__PURE__ */ jsxs3("section", { "aria-labelledby": "available-providers-heading", className: "mt-2", children: [
|
|
989
|
+
/* @__PURE__ */ jsx3(
|
|
990
|
+
"div",
|
|
991
|
+
{
|
|
992
|
+
id: "available-providers-heading",
|
|
993
|
+
className: "mb-2 text-[0.8125rem] font-semibold uppercase text-text-secondary",
|
|
994
|
+
children: "Available"
|
|
995
|
+
}
|
|
996
|
+
),
|
|
997
|
+
/* @__PURE__ */ jsx3("div", { className: "overflow-hidden rounded-xl border border-border bg-card-bg", children: matchingAvailable.map((provider) => {
|
|
998
|
+
const logoSrc = modelProviderIconMap[provider.name];
|
|
999
|
+
return /* @__PURE__ */ jsx3("article", { className: "border-b border-border p-3 last:border-b-0", children: /* @__PURE__ */ jsxs3("header", { className: "flex flex-col gap-3 sm:flex-row sm:items-center", children: [
|
|
1000
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
|
|
1001
|
+
/* @__PURE__ */ jsx3(
|
|
1002
|
+
"span",
|
|
1003
|
+
{
|
|
1004
|
+
className: "flex size-9 shrink-0 items-center justify-center rounded-lg border border-border bg-secondary-bg text-text-primary",
|
|
1005
|
+
"aria-hidden": true,
|
|
1006
|
+
children: logoSrc ? /* @__PURE__ */ jsx3(CatalogLogo, { src: logoSrc, alt: provider.name, className: "size-4.5" }) : /* @__PURE__ */ jsx3(Icon, { name: "cpu", className: "size-4.5" })
|
|
1007
|
+
}
|
|
1008
|
+
),
|
|
1009
|
+
/* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
|
|
1010
|
+
/* @__PURE__ */ jsx3("h5", { className: "truncate text-base font-medium text-text-primary", children: provider.name }),
|
|
1011
|
+
/* @__PURE__ */ jsx3("p", { className: "truncate text-sm text-text-secondary", children: provider.models.map((model) => model.name).join(" \xB7 ") })
|
|
1012
|
+
] })
|
|
1013
|
+
] }),
|
|
1014
|
+
/* @__PURE__ */ jsxs3(
|
|
1015
|
+
Button,
|
|
1016
|
+
{
|
|
1017
|
+
variant: "secondary",
|
|
1018
|
+
type: "button",
|
|
1019
|
+
disabled: busy,
|
|
1020
|
+
onClick: () => {
|
|
1021
|
+
setKeyError(null);
|
|
1022
|
+
setEditingProviderId(null);
|
|
1023
|
+
setEditingCatalogType(provider.type);
|
|
1024
|
+
},
|
|
1025
|
+
children: [
|
|
1026
|
+
/* @__PURE__ */ jsx3(Icon, { name: "wrench", className: "size-4" }),
|
|
1027
|
+
"Configure"
|
|
1028
|
+
]
|
|
1029
|
+
}
|
|
1030
|
+
)
|
|
1031
|
+
] }) }, provider.type);
|
|
1032
|
+
}) })
|
|
1033
|
+
] }) : null,
|
|
1034
|
+
!loading && matchingConfigured.length === 0 && matchingAvailable.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "flex flex-1 items-center justify-center rounded-xl border border-dashed border-border p-8 text-center text-sm text-text-secondary", children: normalizedQuery ? `No providers match \u201C${query.trim()}\u201D.` : "No model providers yet." }) : null
|
|
1035
|
+
] }),
|
|
1036
|
+
/* @__PURE__ */ jsx3(
|
|
1037
|
+
ConfigureModelProviderForm_default,
|
|
1038
|
+
{
|
|
1039
|
+
open: keyModalOpen,
|
|
1040
|
+
onOpenChange: (open) => {
|
|
1041
|
+
if (!open) closeKeyEditor();
|
|
1042
|
+
},
|
|
1043
|
+
onSave: (draft) => {
|
|
1044
|
+
if (editingProvider) return handleReplaceKey(editingProvider, draft);
|
|
1045
|
+
if (editingCatalogEntry) return handleCreateFromCatalog(editingCatalogEntry, draft);
|
|
1046
|
+
},
|
|
1047
|
+
title: editingProvider ? "Edit Provider Details" : "Configure Provider Details",
|
|
1048
|
+
initialBaseUrl: editingProvider ? editingProvider.baseUrl ?? "" : editingCatalogEntry ? catalogBaseUrl(editingCatalogEntry) : "",
|
|
1049
|
+
requireApiKey: editingProvider == null,
|
|
1050
|
+
submitLabel: editingProvider ? "Save" : "Create",
|
|
1051
|
+
busy,
|
|
1052
|
+
error: keyError
|
|
1053
|
+
}
|
|
1054
|
+
),
|
|
1055
|
+
/* @__PURE__ */ jsx3(
|
|
1056
|
+
CustomModelProviderForm_default,
|
|
1057
|
+
{
|
|
1058
|
+
open: customProviderOpen,
|
|
1059
|
+
onOpenChange: (open) => {
|
|
1060
|
+
setCustomProviderOpen(open);
|
|
1061
|
+
if (!open) {
|
|
1062
|
+
setFormError(null);
|
|
1063
|
+
setCustomProviderToEdit(null);
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
isEditMode: customProviderToEdit !== null,
|
|
1067
|
+
initialValues: customProviderInitialValues,
|
|
1068
|
+
onSubmit: customProviderToEdit ? handleUpdateCustomProvider : handleAddCustomProvider,
|
|
1069
|
+
reasoningEffortOptions: supportedReasoningEfforts,
|
|
1070
|
+
busy,
|
|
1071
|
+
error: formError
|
|
1072
|
+
},
|
|
1073
|
+
customProviderToEdit?.id ?? "add-custom-provider"
|
|
1074
|
+
)
|
|
1075
|
+
] }) })
|
|
1076
|
+
] });
|
|
1077
|
+
};
|
|
1078
|
+
var ModelSettings_default = ModelSettings;
|
|
1079
|
+
export {
|
|
1080
|
+
ModelSettings_default as default
|
|
1081
|
+
};
|
|
1082
|
+
//# sourceMappingURL=ModelSettings-IJ55QJQO.js.map
|