sa2kit 3.7.0 → 3.9.1

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.
Files changed (51) hide show
  1. package/dist/{chunk-ZJLS5JU5.mjs → chunk-6KD4CD7O.mjs} +7 -16
  2. package/dist/chunk-6KD4CD7O.mjs.map +1 -0
  3. package/dist/chunk-FV7IHC23.js +751 -0
  4. package/dist/chunk-FV7IHC23.js.map +1 -0
  5. package/dist/chunk-GJVEYCS4.js +12 -0
  6. package/dist/chunk-GJVEYCS4.js.map +1 -0
  7. package/dist/chunk-OOVINE7M.mjs +715 -0
  8. package/dist/chunk-OOVINE7M.mjs.map +1 -0
  9. package/dist/chunk-PPV4IEWR.mjs +8 -0
  10. package/dist/chunk-PPV4IEWR.mjs.map +1 -0
  11. package/dist/{chunk-XPY45Y75.js → chunk-VVHFMAE7.js} +9 -21
  12. package/dist/chunk-VVHFMAE7.js.map +1 -0
  13. package/dist/common/aiApi/client/index.d.mts +66 -4
  14. package/dist/common/aiApi/client/index.d.ts +66 -4
  15. package/dist/common/aiApi/client/index.js +360 -15
  16. package/dist/common/aiApi/client/index.js.map +1 -1
  17. package/dist/common/aiApi/client/index.mjs +345 -11
  18. package/dist/common/aiApi/client/index.mjs.map +1 -1
  19. package/dist/common/aiApi/index.d.mts +2 -2
  20. package/dist/common/aiApi/index.d.ts +2 -2
  21. package/dist/common/aiApi/index.js +63 -62
  22. package/dist/common/aiApi/index.mjs +2 -1
  23. package/dist/common/aiApi/server/index.d.mts +1 -1
  24. package/dist/common/aiApi/server/index.d.ts +1 -1
  25. package/dist/common/aiApi/server/index.js +63 -62
  26. package/dist/common/aiApi/server/index.mjs +2 -1
  27. package/dist/common/appLauncher/index.d.mts +32 -0
  28. package/dist/common/appLauncher/index.d.ts +32 -0
  29. package/dist/common/appLauncher/index.js +149 -0
  30. package/dist/common/appLauncher/index.js.map +1 -0
  31. package/dist/common/appLauncher/index.mjs +4 -0
  32. package/dist/common/appLauncher/index.mjs.map +1 -0
  33. package/dist/common/appLauncher/rn/index.d.mts +35 -0
  34. package/dist/common/appLauncher/rn/index.d.ts +35 -0
  35. package/dist/common/appLauncher/rn/index.js +100 -0
  36. package/dist/common/appLauncher/rn/index.js.map +1 -0
  37. package/dist/common/appLauncher/rn/index.mjs +31 -0
  38. package/dist/common/appLauncher/rn/index.mjs.map +1 -0
  39. package/dist/index-BHgDdlJW.d.mts +177 -0
  40. package/dist/index-BHgDdlJW.d.ts +177 -0
  41. package/dist/index.d.mts +9 -1
  42. package/dist/index.d.ts +9 -1
  43. package/dist/index.js +3 -9
  44. package/dist/index.js.map +1 -1
  45. package/dist/index.mjs +3 -9
  46. package/dist/index.mjs.map +1 -1
  47. package/dist/{types-CiqMQ-uu.d.mts → types-DgbG0Of-.d.mts} +9 -1
  48. package/dist/{types-CiqMQ-uu.d.ts → types-DgbG0Of-.d.ts} +9 -1
  49. package/package.json +11 -1
  50. package/dist/chunk-XPY45Y75.js.map +0 -1
  51. package/dist/chunk-ZJLS5JU5.mjs.map +0 -1
@@ -1,7 +1,13 @@
1
1
  'use strict';
2
2
 
3
+ var chunkGJVEYCS4_js = require('../../../chunk-GJVEYCS4.js');
3
4
  require('../../../chunk-NSBPE2FW.js');
4
- var react = require('react');
5
+ var React3 = require('react');
6
+ var lucideReact = require('lucide-react');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var React3__default = /*#__PURE__*/_interopDefault(React3);
5
11
 
6
12
  // src/common/aiApi/client/settingsCore.ts
7
13
  var DEFAULT_AI_API_SETTINGS = {
@@ -13,14 +19,14 @@ var DEFAULT_AI_API_SETTINGS = {
13
19
  audioStrategy: "auto"
14
20
  };
15
21
  var AI_API_SETTINGS_STORAGE_KEY = "ai-api-settings";
16
- function loadAiApiSettings(storageKey = AI_API_SETTINGS_STORAGE_KEY) {
17
- if (typeof window === "undefined") return DEFAULT_AI_API_SETTINGS;
22
+ function loadAiApiSettings(storageKey = AI_API_SETTINGS_STORAGE_KEY, defaults = DEFAULT_AI_API_SETTINGS) {
23
+ if (typeof window === "undefined") return defaults;
18
24
  try {
19
25
  const raw = localStorage.getItem(storageKey);
20
- if (!raw) return DEFAULT_AI_API_SETTINGS;
21
- return { ...DEFAULT_AI_API_SETTINGS, ...JSON.parse(raw) };
26
+ if (!raw) return defaults;
27
+ return { ...defaults, ...JSON.parse(raw) };
22
28
  } catch {
23
- return DEFAULT_AI_API_SETTINGS;
29
+ return defaults;
24
30
  }
25
31
  }
26
32
  function saveAiApiSettings(settings, storageKey = AI_API_SETTINGS_STORAGE_KEY) {
@@ -40,8 +46,14 @@ function toClientSettings(settings) {
40
46
  if (settings.audioStrategy) client.audioStrategy = settings.audioStrategy;
41
47
  return Object.keys(client).length > 0 ? client : void 0;
42
48
  }
43
- function pickClientSettingsFromStorage(storageKey = AI_API_SETTINGS_STORAGE_KEY) {
44
- return toClientSettings(loadAiApiSettings(storageKey));
49
+ function toServerClientSettings(settings) {
50
+ if (!settings.apiKey.trim()) {
51
+ return void 0;
52
+ }
53
+ return toClientSettings(settings);
54
+ }
55
+ function pickClientSettingsFromStorage(storageKey = AI_API_SETTINGS_STORAGE_KEY, defaults = DEFAULT_AI_API_SETTINGS) {
56
+ return toServerClientSettings(loadAiApiSettings(storageKey, defaults));
45
57
  }
46
58
 
47
59
  // src/common/aiApi/client/aiApiClient.ts
@@ -56,7 +68,7 @@ var AiApiClientError = class extends Error {
56
68
  async function runAiTask(taskId, input, options) {
57
69
  const fetchFn = options?.fetchImpl ?? fetch;
58
70
  const endpoint = options?.runEndpoint ?? "/api/ai/run";
59
- const clientSettings = options?.clientSettings ?? pickClientSettingsFromStorage();
71
+ const clientSettings = options !== void 0 && "clientSettings" in options ? options.clientSettings : pickClientSettingsFromStorage();
60
72
  const payload = {
61
73
  taskId,
62
74
  input,
@@ -100,20 +112,24 @@ var aiApiClient = {
100
112
  async function fetchAiModels(clientSettings, options) {
101
113
  const fetchFn = options?.fetchImpl ?? fetch;
102
114
  const endpoint = options?.modelsEndpoint ?? "/api/ai/models";
115
+ const body = {};
116
+ if (clientSettings) {
117
+ body.clientSettings = clientSettings;
118
+ }
103
119
  const response = await fetchFn(endpoint, {
104
120
  method: "POST",
105
121
  credentials: "include",
106
122
  headers: { "Content-Type": "application/json" },
107
- body: JSON.stringify({ clientSettings }),
123
+ body: JSON.stringify(body),
108
124
  signal: options?.signal
109
125
  });
110
126
  return await response.json();
111
127
  }
112
128
  function useAiTask(taskId) {
113
- const [loading, setLoading] = react.useState(false);
114
- const [error, setError] = react.useState(null);
115
- const [result, setResult] = react.useState(null);
116
- const execute = react.useCallback(
129
+ const [loading, setLoading] = React3.useState(false);
130
+ const [error, setError] = React3.useState(null);
131
+ const [result, setResult] = React3.useState(null);
132
+ const execute = React3.useCallback(
117
133
  async (input, options) => {
118
134
  setLoading(true);
119
135
  setError(null);
@@ -140,16 +156,341 @@ function useAiTask(taskId) {
140
156
  },
141
157
  [taskId]
142
158
  );
143
- const reset = react.useCallback(() => {
159
+ const reset = React3.useCallback(() => {
144
160
  setLoading(false);
145
161
  setError(null);
146
162
  setResult(null);
147
163
  }, []);
148
164
  return { execute, loading, error, result, reset };
149
165
  }
166
+ function useAiModels(settings, onSuggestedModel, options) {
167
+ const [visionModels, setVisionModels] = React3.useState([]);
168
+ const [allModels, setAllModels] = React3.useState([]);
169
+ const [loading, setLoading] = React3.useState(false);
170
+ const [error, setError] = React3.useState(null);
171
+ const abortRef = React3.useRef(null);
172
+ const onSuggestedRef = React3.useRef(onSuggestedModel);
173
+ const settingsRef = React3.useRef(settings);
174
+ onSuggestedRef.current = onSuggestedModel;
175
+ settingsRef.current = settings;
176
+ const load = React3.useCallback(async () => {
177
+ abortRef.current?.abort();
178
+ const controller = new AbortController();
179
+ abortRef.current = controller;
180
+ setLoading(true);
181
+ setError(null);
182
+ const currentSettings = settingsRef.current;
183
+ try {
184
+ const clientSettings = toServerClientSettings(currentSettings);
185
+ const result = await fetchAiModels(clientSettings, {
186
+ signal: controller.signal,
187
+ modelsEndpoint: options?.modelsEndpoint
188
+ });
189
+ if (controller.signal.aborted) return;
190
+ if (!result.success) {
191
+ setVisionModels([]);
192
+ setAllModels([]);
193
+ setError(result.error?.message ?? "\u83B7\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25");
194
+ return;
195
+ }
196
+ setVisionModels(result.visionModels);
197
+ setAllModels(result.models);
198
+ if (result.suggestedVisionModel) {
199
+ const current = currentSettings.visionModel.trim();
200
+ const shouldAutoSelect = !current || !result.visionModels.includes(current) && !result.models.includes(current);
201
+ if (shouldAutoSelect) {
202
+ onSuggestedRef.current?.(result.suggestedVisionModel);
203
+ }
204
+ }
205
+ } catch (err) {
206
+ if (controller.signal.aborted) return;
207
+ setVisionModels([]);
208
+ setAllModels([]);
209
+ setError(err instanceof Error ? err.message : "\u83B7\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25");
210
+ } finally {
211
+ if (!controller.signal.aborted) {
212
+ setLoading(false);
213
+ }
214
+ }
215
+ }, [options?.modelsEndpoint, settings.apiKey, settings.baseUrl]);
216
+ React3.useEffect(() => {
217
+ const debounceMs = options?.debounceMs ?? 600;
218
+ const timer = window.setTimeout(() => {
219
+ void load();
220
+ }, debounceMs);
221
+ return () => {
222
+ window.clearTimeout(timer);
223
+ abortRef.current?.abort();
224
+ };
225
+ }, [load, options?.debounceMs]);
226
+ return {
227
+ visionModels,
228
+ allModels,
229
+ loading,
230
+ error,
231
+ refresh: load
232
+ };
233
+ }
234
+ function useAiServerConfig(options) {
235
+ const [config, setConfig] = React3.useState(null);
236
+ const [loading, setLoading] = React3.useState(true);
237
+ const [error, setError] = React3.useState(null);
238
+ const configEndpoint = options?.configEndpoint ?? "/api/ai/config";
239
+ React3.useEffect(() => {
240
+ let cancelled = false;
241
+ async function load() {
242
+ try {
243
+ const response = await fetch(configEndpoint, { credentials: "include" });
244
+ if (!response.ok) {
245
+ if (!cancelled) {
246
+ setConfig({ serverConfigured: false });
247
+ setError(response.status === 401 ? "\u8BF7\u5148\u767B\u5F55" : "\u65E0\u6CD5\u8BFB\u53D6\u670D\u52A1\u7AEF\u914D\u7F6E");
248
+ }
249
+ return;
250
+ }
251
+ const data = await response.json();
252
+ if (!cancelled) {
253
+ setConfig(data);
254
+ setError(null);
255
+ }
256
+ } catch (err) {
257
+ if (!cancelled) {
258
+ setConfig({ serverConfigured: false });
259
+ setError(err instanceof Error ? err.message : "\u65E0\u6CD5\u8BFB\u53D6\u670D\u52A1\u7AEF\u914D\u7F6E");
260
+ }
261
+ } finally {
262
+ if (!cancelled) setLoading(false);
263
+ }
264
+ }
265
+ void load();
266
+ return () => {
267
+ cancelled = true;
268
+ };
269
+ }, [configEndpoint]);
270
+ return { config, loading, error };
271
+ }
272
+ var AiApiSettingsContext = React3.createContext(null);
273
+ function AiApiSettingsProvider({
274
+ children,
275
+ defaultSettings,
276
+ storageKey
277
+ }) {
278
+ const mergedDefaults = React3.useMemo(
279
+ () => ({ ...DEFAULT_AI_API_SETTINGS, ...defaultSettings }),
280
+ [defaultSettings]
281
+ );
282
+ const [settings, setSettings] = React3.useState(mergedDefaults);
283
+ React3.useEffect(() => {
284
+ setSettings(loadAiApiSettings(storageKey, mergedDefaults));
285
+ }, [storageKey, mergedDefaults]);
286
+ const persist = React3.useCallback(
287
+ (next) => {
288
+ setSettings(next);
289
+ saveAiApiSettings(next, storageKey);
290
+ },
291
+ [storageKey]
292
+ );
293
+ const updateSettings = React3.useCallback(
294
+ (updates) => {
295
+ setSettings((prev) => {
296
+ const next = { ...prev, ...updates };
297
+ saveAiApiSettings(next, storageKey);
298
+ return next;
299
+ });
300
+ },
301
+ [storageKey]
302
+ );
303
+ const resetSettings = React3.useCallback(() => {
304
+ persist(mergedDefaults);
305
+ }, [persist, mergedDefaults]);
306
+ const value = React3.useMemo(
307
+ () => ({ settings, updateSettings, resetSettings }),
308
+ [settings, updateSettings, resetSettings]
309
+ );
310
+ return /* @__PURE__ */ React3__default.default.createElement(AiApiSettingsContext.Provider, { value }, children);
311
+ }
312
+ function useAiApiSettings() {
313
+ const ctx = React3.useContext(AiApiSettingsContext);
314
+ if (!ctx) {
315
+ throw new Error("useAiApiSettings must be used within AiApiSettingsProvider");
316
+ }
317
+ return ctx;
318
+ }
319
+ function AiApiConnectivityTest({ runEndpoint }) {
320
+ const { settings } = useAiApiSettings();
321
+ const [status, setStatus] = React3.useState("idle");
322
+ const [message, setMessage] = React3.useState("");
323
+ const [meta, setMeta] = React3.useState(null);
324
+ const handleTest = React3.useCallback(async () => {
325
+ setStatus("loading");
326
+ setMessage("");
327
+ setMeta(null);
328
+ const started = Date.now();
329
+ const clientSettings = toServerClientSettings(settings);
330
+ try {
331
+ const result = await runAiTask(
332
+ chunkGJVEYCS4_js.CORE_CONNECTIVITY_TEST_TASK_ID,
333
+ {},
334
+ { clientSettings, runEndpoint }
335
+ );
336
+ const latencyMs = result.meta?.latencyMs ?? Date.now() - started;
337
+ if (!result.success) {
338
+ setStatus("error");
339
+ setMessage(result.error?.message ?? "\u8FDE\u901A\u6027\u6D4B\u8BD5\u5931\u8D25");
340
+ setMeta({ latencyMs });
341
+ return;
342
+ }
343
+ setStatus("success");
344
+ setMessage(result.data?.reply ? `\u54CD\u5E94\uFF1A${result.data.reply}` : "\u8FDE\u63A5\u6B63\u5E38");
345
+ setMeta({ model: result.meta?.model, latencyMs });
346
+ } catch (err) {
347
+ setStatus("error");
348
+ setMessage(err instanceof Error ? err.message : "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25");
349
+ setMeta({ latencyMs: Date.now() - started });
350
+ }
351
+ }, [settings, runEndpoint]);
352
+ return /* @__PURE__ */ React3__default.default.createElement("div", { className: "rounded-xl border border-slate-200 bg-slate-50/80 p-4" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React3__default.default.createElement("div", null, /* @__PURE__ */ React3__default.default.createElement("h3", { className: "text-sm font-medium text-slate-900" }, "\u8FDE\u901A\u6027\u6D4B\u8BD5"), /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1 text-xs text-slate-500" }, "\u4F7F\u7528\u5F53\u524D\u586B\u5199\u7684 Key\u3001Base URL \u4E0E\u6A21\u578B\u53D1\u8D77\u4E00\u6B21\u8F7B\u91CF\u8BF7\u6C42\uFF08\u9700\u5DF2\u767B\u5F55\uFF09\u3002\u672A\u586B\u5199 Key \u65F6\u5C06\u4F7F\u7528\u670D\u52A1\u7AEF\u914D\u7F6E\u3002")), /* @__PURE__ */ React3__default.default.createElement(
353
+ "button",
354
+ {
355
+ type: "button",
356
+ onClick: () => void handleTest(),
357
+ disabled: status === "loading",
358
+ className: "inline-flex h-10 items-center gap-2 rounded-lg bg-slate-900 px-4 text-sm font-medium text-white transition-transform hover:bg-slate-800 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-60"
359
+ },
360
+ status === "loading" ? /* @__PURE__ */ React3__default.default.createElement(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ React3__default.default.createElement(lucideReact.Wifi, { className: "h-4 w-4" }),
361
+ status === "loading" ? "\u6D4B\u8BD5\u4E2D\u2026" : "\u6D4B\u8BD5\u8FDE\u63A5"
362
+ )), status !== "idle" && /* @__PURE__ */ React3__default.default.createElement(
363
+ "div",
364
+ {
365
+ className: `mt-3 flex items-start gap-2 rounded-lg px-3 py-2.5 text-sm ${status === "success" ? "bg-emerald-50 text-emerald-800" : status === "error" ? "bg-red-50 text-red-800" : "bg-white text-slate-600"}`
366
+ },
367
+ status === "loading" && /* @__PURE__ */ React3__default.default.createElement(lucideReact.Loader2, { className: "mt-0.5 h-4 w-4 shrink-0 animate-spin" }),
368
+ status === "success" && /* @__PURE__ */ React3__default.default.createElement(lucideReact.CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0" }),
369
+ status === "error" && /* @__PURE__ */ React3__default.default.createElement(lucideReact.XCircle, { className: "mt-0.5 h-4 w-4 shrink-0" }),
370
+ /* @__PURE__ */ React3__default.default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React3__default.default.createElement("p", { className: "text-pretty" }, status === "loading" ? "\u6B63\u5728\u8FDE\u63A5 AI \u670D\u52A1\u2026" : message), meta && (meta.model || meta.latencyMs !== void 0) && status !== "loading" && /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1 tabular-nums text-xs opacity-80" }, meta.model && /* @__PURE__ */ React3__default.default.createElement("span", null, "\u6A21\u578B ", meta.model), meta.model && meta.latencyMs !== void 0 && /* @__PURE__ */ React3__default.default.createElement("span", null, " \xB7 "), meta.latencyMs !== void 0 && /* @__PURE__ */ React3__default.default.createElement("span", null, meta.latencyMs, " ms")))
371
+ ));
372
+ }
373
+
374
+ // src/common/aiApi/client/components/AiApiSettingsPanel.tsx
375
+ function AiApiSettingsPanel({
376
+ serverConfigEndpoint,
377
+ modelsEndpoint,
378
+ runEndpoint,
379
+ serverMissingHint,
380
+ apiKeyPlaceholder = "sk-\u2026",
381
+ baseUrlPlaceholder = "https://api.openai.com/v1",
382
+ visionModelPlaceholder = "gpt-4o-mini"
383
+ }) {
384
+ const { settings, updateSettings } = useAiApiSettings();
385
+ const { config: serverConfig, loading: serverLoading } = useAiServerConfig({
386
+ configEndpoint: serverConfigEndpoint
387
+ });
388
+ const [showOverride, setShowOverride] = React3.useState(false);
389
+ const hasBrowserKey = Boolean(settings.apiKey.trim());
390
+ const serverReady = Boolean(serverConfig?.serverConfigured);
391
+ const useServerOnly = serverReady && !hasBrowserKey && !showOverride;
392
+ const handleSuggestedModel = React3.useCallback(
393
+ (model) => {
394
+ updateSettings({ visionModel: model });
395
+ },
396
+ [updateSettings]
397
+ );
398
+ const { visionModels, allModels, loading, error, refresh } = useAiModels(
399
+ settings,
400
+ handleSuggestedModel,
401
+ { modelsEndpoint }
402
+ );
403
+ const selectableModels = visionModels;
404
+ const showDropdown = selectableModels.length > 0;
405
+ const showAllModelsFallback = visionModels.length === 0 && allModels.length > 0;
406
+ const defaultServerMissingHint = /* @__PURE__ */ React3__default.default.createElement(React3__default.default.Fragment, null, "\u8BF7\u5728\u5BBF\u4E3B\u914D\u7F6E\u6587\u4EF6\u4E2D\u586B\u5199 AI API Key\uFF08\u5982 YAML \u7684 ", /* @__PURE__ */ React3__default.default.createElement("code", { className: "rounded bg-amber-100 px-1" }, "ai.apiKey"), "\uFF09\uFF0C\u6216\u5728\u4E0B\u65B9\u7684\u6D4F\u89C8\u5668\u8BBE\u7F6E\u4E2D\u586B\u5199\u3002");
407
+ return /* @__PURE__ */ React3__default.default.createElement("div", { className: "space-y-6" }, serverLoading ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600" }, /* @__PURE__ */ React3__default.default.createElement(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }), "\u6B63\u5728\u68C0\u67E5\u670D\u52A1\u7AEF AI \u914D\u7F6E\u2026") : serverReady && !hasBrowserKey ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "rounded-xl border border-emerald-200 bg-emerald-50/80 p-4" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React3__default.default.createElement(lucideReact.CheckCircle2, { className: "mt-0.5 h-5 w-5 shrink-0 text-emerald-600" }), /* @__PURE__ */ React3__default.default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React3__default.default.createElement("h3", { className: "text-sm font-medium text-emerald-900" }, "\u5DF2\u7531\u670D\u52A1\u7AEF\u914D\u7F6E"), /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1 text-xs text-emerald-800/90" }, "AI \u529F\u80FD\u5C06\u4F7F\u7528\u670D\u52A1\u7AEF\u914D\u7F6E\u7684 API Key\uFF0C\u65E0\u9700\u5728\u6B64\u91CD\u590D\u586B\u5199\u3002"), /* @__PURE__ */ React3__default.default.createElement("dl", { className: "mt-3 grid gap-1 text-xs text-emerald-900/80" }, serverConfig?.baseUrl && /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React3__default.default.createElement("dt", { className: "shrink-0 font-medium" }, "Base URL"), /* @__PURE__ */ React3__default.default.createElement("dd", { className: "truncate font-mono" }, serverConfig.baseUrl)), serverConfig?.visionModel && /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React3__default.default.createElement("dt", { className: "shrink-0 font-medium" }, "\u89C6\u89C9\u6A21\u578B"), /* @__PURE__ */ React3__default.default.createElement("dd", { className: "font-mono" }, serverConfig.visionModel))), /* @__PURE__ */ React3__default.default.createElement(
408
+ "button",
409
+ {
410
+ type: "button",
411
+ onClick: () => setShowOverride((v) => !v),
412
+ className: "mt-3 text-xs font-medium text-emerald-700 underline-offset-2 hover:underline"
413
+ },
414
+ showOverride ? "\u6536\u8D77\u81EA\u5B9A\u4E49\u914D\u7F6E" : "\u9AD8\u7EA7\uFF1A\u4F7F\u7528\u6D4F\u89C8\u5668\u81EA\u5B9A\u4E49 Key \u8986\u76D6\u670D\u52A1\u7AEF"
415
+ )))) : !serverReady && !hasBrowserKey ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "rounded-xl border border-amber-200 bg-amber-50/80 p-4" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React3__default.default.createElement(lucideReact.Server, { className: "mt-0.5 h-5 w-5 shrink-0 text-amber-600" }), /* @__PURE__ */ React3__default.default.createElement("div", null, /* @__PURE__ */ React3__default.default.createElement("h3", { className: "text-sm font-medium text-amber-900" }, "\u670D\u52A1\u7AEF\u672A\u914D\u7F6E AI"), /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1 text-xs text-amber-800/90" }, serverMissingHint ?? defaultServerMissingHint)))) : null, !useServerOnly && /* @__PURE__ */ React3__default.default.createElement(React3__default.default.Fragment, null, /* @__PURE__ */ React3__default.default.createElement("div", null, /* @__PURE__ */ React3__default.default.createElement("label", { htmlFor: "ai-api-key", className: "mb-2 block text-sm font-medium text-gray-700" }, "API Key"), /* @__PURE__ */ React3__default.default.createElement(
416
+ "input",
417
+ {
418
+ id: "ai-api-key",
419
+ type: "password",
420
+ autoComplete: "off",
421
+ value: settings.apiKey,
422
+ onChange: (e) => updateSettings({ apiKey: e.target.value }),
423
+ placeholder: apiKeyPlaceholder,
424
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
425
+ }
426
+ ), /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 text-xs text-gray-500" }, "\u4FDD\u5B58\u5728\u672C\u673A\u6D4F\u89C8\u5668\u3002\u586B\u5199\u540E\u5C06\u8986\u76D6\u670D\u52A1\u7AEF\u914D\u7F6E\uFF1B\u7559\u7A7A\u5219\u4F7F\u7528\u670D\u52A1\u7AEF\u5BC6\u94A5\u3002")), /* @__PURE__ */ React3__default.default.createElement("div", null, /* @__PURE__ */ React3__default.default.createElement("label", { htmlFor: "ai-base-url", className: "mb-2 block text-sm font-medium text-gray-700" }, "API Base URL"), /* @__PURE__ */ React3__default.default.createElement(
427
+ "input",
428
+ {
429
+ id: "ai-base-url",
430
+ type: "url",
431
+ value: settings.baseUrl,
432
+ onChange: (e) => updateSettings({ baseUrl: e.target.value }),
433
+ placeholder: baseUrlPlaceholder,
434
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
435
+ }
436
+ ), /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 text-xs text-gray-500" }, "\u586B\u5199 Key \u4E0E\u5730\u5740\u540E\u5C06\u81EA\u52A8\u62C9\u53D6\u53EF\u7528\u6A21\u578B\u5E76\u9009\u62E9\u5408\u9002\u7684\u89C6\u89C9\u6A21\u578B\u3002"))), !useServerOnly && /* @__PURE__ */ React3__default.default.createElement(React3__default.default.Fragment, null, /* @__PURE__ */ React3__default.default.createElement("div", null, /* @__PURE__ */ React3__default.default.createElement("div", { className: "mb-2 flex items-center justify-between gap-2" }, /* @__PURE__ */ React3__default.default.createElement("label", { htmlFor: "ai-vision-model", className: "text-sm font-medium text-gray-700" }, "\u89C6\u89C9\u6A21\u578B"), /* @__PURE__ */ React3__default.default.createElement(
437
+ "button",
438
+ {
439
+ type: "button",
440
+ onClick: () => void refresh(),
441
+ disabled: loading,
442
+ className: "inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 disabled:opacity-50"
443
+ },
444
+ loading ? /* @__PURE__ */ React3__default.default.createElement(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ React3__default.default.createElement(lucideReact.RefreshCw, { className: "h-3.5 w-3.5" }),
445
+ "\u5237\u65B0\u6A21\u578B"
446
+ )), showDropdown ? /* @__PURE__ */ React3__default.default.createElement(
447
+ "select",
448
+ {
449
+ id: "ai-vision-model",
450
+ value: settings.visionModel,
451
+ onChange: (e) => updateSettings({ visionModel: e.target.value }),
452
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
453
+ },
454
+ selectableModels.map((model) => /* @__PURE__ */ React3__default.default.createElement("option", { key: model, value: model }, model)),
455
+ settings.visionModel && !selectableModels.includes(settings.visionModel) && /* @__PURE__ */ React3__default.default.createElement("option", { value: settings.visionModel }, settings.visionModel, "\uFF08\u5F53\u524D\uFF09")
456
+ ) : /* @__PURE__ */ React3__default.default.createElement(
457
+ "input",
458
+ {
459
+ id: "ai-vision-model",
460
+ type: "text",
461
+ value: settings.visionModel,
462
+ onChange: (e) => updateSettings({ visionModel: e.target.value }),
463
+ placeholder: visionModelPlaceholder,
464
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
465
+ }
466
+ ), loading && /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 flex items-center gap-1.5 text-xs text-gray-500" }, /* @__PURE__ */ React3__default.default.createElement(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }), "\u6B63\u5728\u8BFB\u53D6\u53EF\u7528\u6A21\u578B\u2026"), !loading && error && /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 text-xs text-amber-600" }, "\u65E0\u6CD5\u81EA\u52A8\u83B7\u53D6\u6A21\u578B\u5217\u8868\uFF1A", error, "\u3002\u53EF\u624B\u52A8\u586B\u5199\u6A21\u578B\u540D\u79F0\u3002"), !loading && !error && showDropdown && /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 text-xs text-gray-500" }, "\u5DF2\u52A0\u8F7D ", selectableModels.length, " \u4E2A", visionModels.length > 0 ? "\u89C6\u89C9" : "\u5BF9\u8BDD", "\u6A21\u578B\uFF0C\u53EF\u624B\u52A8\u5207\u6362\u3002"), !loading && !error && showAllModelsFallback && /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 text-xs text-amber-600" }, "\u672A\u8BC6\u522B\u5230\u89C6\u89C9\u6A21\u578B\uFF0C\u8BF7\u624B\u52A8\u586B\u5199\u652F\u6301\u8BC6\u56FE\u7684\u6A21\u578B\u540D\u3002"), !loading && !error && !showDropdown && !showAllModelsFallback && /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 text-xs text-gray-500" }, "\u9700\u652F\u6301\u56FE\u7247\u8F93\u5165\u7684\u591A\u6A21\u6001\u6A21\u578B\u3002")), /* @__PURE__ */ React3__default.default.createElement("div", null, /* @__PURE__ */ React3__default.default.createElement("label", { htmlFor: "ai-text-model", className: "mb-2 block text-sm font-medium text-gray-700" }, "\u6587\u672C\u6A21\u578B"), /* @__PURE__ */ React3__default.default.createElement(
467
+ "input",
468
+ {
469
+ id: "ai-text-model",
470
+ type: "text",
471
+ value: settings.textModel ?? settings.visionModel,
472
+ onChange: (e) => updateSettings({ textModel: e.target.value }),
473
+ placeholder: visionModelPlaceholder,
474
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
475
+ }
476
+ ), /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 text-xs text-gray-500" }, "\u7EAF\u6587\u672C\u4E0E STT \u8F6C\u5199\u540E\u7684\u5BF9\u8BDD\uFF1B\u9ED8\u8BA4\u540C\u89C6\u89C9\u6A21\u578B\u3002")), /* @__PURE__ */ React3__default.default.createElement("div", null, /* @__PURE__ */ React3__default.default.createElement("label", { htmlFor: "ai-audio-model", className: "mb-2 block text-sm font-medium text-gray-700" }, "\u8BED\u97F3\u8F6C\u5199\u6A21\u578B\uFF08STT\uFF09"), /* @__PURE__ */ React3__default.default.createElement(
477
+ "input",
478
+ {
479
+ id: "ai-audio-model",
480
+ type: "text",
481
+ value: settings.audioModel ?? "whisper-1",
482
+ onChange: (e) => updateSettings({ audioModel: e.target.value }),
483
+ placeholder: "whisper-1",
484
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
485
+ }
486
+ ), /* @__PURE__ */ React3__default.default.createElement("p", { className: "mt-1.5 text-xs text-gray-500" }, "\u591A\u6A21\u6001 auto \u6A21\u5F0F\u4E0B\uFF0C\u4E0D\u652F\u6301\u5185\u5D4C\u97F3\u9891\u65F6\u5C06\u7528\u6B64\u6A21\u578B\u8F6C\u5199\uFF08Whisper \u7B49\uFF09\u3002"))), /* @__PURE__ */ React3__default.default.createElement(AiApiConnectivityTest, { runEndpoint }));
487
+ }
150
488
 
151
489
  exports.AI_API_SETTINGS_STORAGE_KEY = AI_API_SETTINGS_STORAGE_KEY;
152
490
  exports.AiApiClientError = AiApiClientError;
491
+ exports.AiApiConnectivityTest = AiApiConnectivityTest;
492
+ exports.AiApiSettingsPanel = AiApiSettingsPanel;
493
+ exports.AiApiSettingsProvider = AiApiSettingsProvider;
153
494
  exports.DEFAULT_AI_API_SETTINGS = DEFAULT_AI_API_SETTINGS;
154
495
  exports.aiApiClient = aiApiClient;
155
496
  exports.createAiTaskRunner = createAiTaskRunner;
@@ -160,6 +501,10 @@ exports.runAiTask = runAiTask;
160
501
  exports.runAiTaskOrThrow = runAiTaskOrThrow;
161
502
  exports.saveAiApiSettings = saveAiApiSettings;
162
503
  exports.toClientSettings = toClientSettings;
504
+ exports.toServerClientSettings = toServerClientSettings;
505
+ exports.useAiApiSettings = useAiApiSettings;
506
+ exports.useAiModels = useAiModels;
507
+ exports.useAiServerConfig = useAiServerConfig;
163
508
  exports.useAiTask = useAiTask;
164
509
  //# sourceMappingURL=index.js.map
165
510
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/common/aiApi/client/settingsCore.ts","../../../../src/common/aiApi/client/aiApiClient.ts","../../../../src/common/aiApi/client/fetchModels.ts","../../../../src/common/aiApi/client/hooks/useAiTask.ts"],"names":["useState","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["import type { AiClientSettings, AudioStrategy } from '../types';\n\nexport interface AiApiSettings {\n apiKey: string;\n baseUrl: string;\n visionModel: string;\n textModel?: string;\n audioModel?: string;\n audioStrategy?: AudioStrategy;\n}\n\nexport const DEFAULT_AI_API_SETTINGS: AiApiSettings = {\n apiKey: '',\n baseUrl: 'https://api.openai.com/v1',\n visionModel: 'gpt-4o-mini',\n textModel: 'gpt-4o-mini',\n audioModel: 'whisper-1',\n audioStrategy: 'auto',\n};\n\nexport const AI_API_SETTINGS_STORAGE_KEY = 'ai-api-settings';\n\nexport function loadAiApiSettings(storageKey = AI_API_SETTINGS_STORAGE_KEY): AiApiSettings {\n if (typeof window === 'undefined') return DEFAULT_AI_API_SETTINGS;\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return DEFAULT_AI_API_SETTINGS;\n return { ...DEFAULT_AI_API_SETTINGS, ...JSON.parse(raw) };\n } catch {\n return DEFAULT_AI_API_SETTINGS;\n }\n}\n\nexport function saveAiApiSettings(\n settings: AiApiSettings,\n storageKey = AI_API_SETTINGS_STORAGE_KEY\n): void {\n if (typeof window === 'undefined') return;\n localStorage.setItem(storageKey, JSON.stringify(settings));\n}\n\nexport function toClientSettings(settings: AiApiSettings): AiClientSettings | undefined {\n const client: AiClientSettings = {};\n if (settings.apiKey.trim()) client.apiKey = settings.apiKey.trim();\n if (settings.baseUrl.trim()) client.baseUrl = settings.baseUrl.trim();\n const visionModel = settings.visionModel.trim();\n const textModel = settings.textModel?.trim() || visionModel;\n const audioModel = settings.audioModel?.trim();\n if (visionModel) client.visionModel = visionModel;\n if (textModel) client.textModel = textModel;\n if (audioModel) client.audioModel = audioModel;\n if (settings.audioStrategy) client.audioStrategy = settings.audioStrategy;\n return Object.keys(client).length > 0 ? client : undefined;\n}\n\nexport function pickClientSettingsFromStorage(\n storageKey = AI_API_SETTINGS_STORAGE_KEY\n): AiClientSettings | undefined {\n return toClientSettings(loadAiApiSettings(storageKey));\n}\n","import type { AiApiResponse, AiApiRunRequest, AiClientSettings } from '../types';\nimport { pickClientSettingsFromStorage } from './settingsCore';\n\nexport class AiApiClientError extends Error {\n constructor(\n message: string,\n public readonly code?: string,\n public readonly response?: AiApiResponse\n ) {\n super(message);\n this.name = 'AiApiClientError';\n }\n}\n\nexport interface AiApiClientOptions {\n /** 默认 POST /api/ai/run,宿主应用可覆盖 */\n runEndpoint?: string;\n fetchImpl?: typeof fetch;\n}\n\n/**\n * 通过 HTTP 调用宿主暴露的 AI 任务入口(如 Next.js route)。\n * 连接配置可通过请求体 clientSettings 传入,或从 localStorage 读取。\n */\nexport async function runAiTask<TInput, TOutput>(\n taskId: string,\n input: TInput,\n options?: {\n signal?: AbortSignal;\n clientSettings?: AiClientSettings;\n runEndpoint?: string;\n fetchImpl?: typeof fetch;\n }\n): Promise<AiApiResponse<TOutput>> {\n const fetchFn = options?.fetchImpl ?? fetch;\n const endpoint = options?.runEndpoint ?? '/api/ai/run';\n const clientSettings = options?.clientSettings ?? pickClientSettingsFromStorage();\n\n const payload: AiApiRunRequest<TInput> = {\n taskId,\n input,\n ...(clientSettings ? { clientSettings } : {}),\n };\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: options?.signal,\n });\n\n const data = (await response.json()) as AiApiResponse<TOutput>;\n return data;\n}\n\nexport async function runAiTaskOrThrow<TInput, TOutput>(\n taskId: string,\n input: TInput,\n options?: {\n signal?: AbortSignal;\n clientSettings?: AiClientSettings;\n runEndpoint?: string;\n fetchImpl?: typeof fetch;\n }\n): Promise<TOutput> {\n const result = await runAiTask<TInput, TOutput>(taskId, input, options);\n if (!result.success || result.data === undefined) {\n throw new AiApiClientError(\n result.error?.message ?? 'AI 任务失败',\n result.error?.code,\n result\n );\n }\n return result.data;\n}\n\nexport function createAiTaskRunner<TInput, TOutput>(\n taskId: string,\n options?: AiApiClientOptions\n) {\n return (\n input: TInput,\n runOptions?: { signal?: AbortSignal; clientSettings?: AiClientSettings }\n ) =>\n runAiTaskOrThrow<TInput, TOutput>(taskId, input, {\n ...runOptions,\n runEndpoint: options?.runEndpoint,\n fetchImpl: options?.fetchImpl,\n });\n}\n\nexport const aiApiClient = {\n run: runAiTask,\n runOrThrow: runAiTaskOrThrow,\n createRunner: createAiTaskRunner,\n};\n","import type { AiClientSettings, AiModelsListResponse } from '../types';\n\nexport async function fetchAiModels(\n clientSettings?: AiClientSettings,\n options?: { signal?: AbortSignal; modelsEndpoint?: string; fetchImpl?: typeof fetch }\n): Promise<AiModelsListResponse> {\n const fetchFn = options?.fetchImpl ?? fetch;\n const endpoint = options?.modelsEndpoint ?? '/api/ai/models';\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ clientSettings }),\n signal: options?.signal,\n });\n\n return (await response.json()) as AiModelsListResponse;\n}\n","'use client';\n\nimport { useCallback, useState } from 'react';\nimport type { AiClientSettings, AiApiResponse } from '../../types';\nimport { runAiTask } from '../aiApiClient';\n\nexport function useAiTask<TInput, TOutput>(taskId: string) {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [result, setResult] = useState<AiApiResponse<TOutput> | null>(null);\n\n const execute = useCallback(\n async (\n input: TInput,\n options?: { signal?: AbortSignal; clientSettings?: AiClientSettings }\n ) => {\n setLoading(true);\n setError(null);\n try {\n const response = await runAiTask<TInput, TOutput>(taskId, input, options);\n setResult(response);\n if (!response.success) {\n setError(response.error?.message ?? 'AI 任务失败');\n }\n return response;\n } catch (err) {\n const message = err instanceof Error ? err.message : 'AI 任务失败';\n setError(message);\n const failed: AiApiResponse<TOutput> = {\n success: false,\n taskId,\n error: { code: 'AI_REQUEST_FAILED', message },\n };\n setResult(failed);\n return failed;\n } finally {\n setLoading(false);\n }\n },\n [taskId]\n );\n\n const reset = useCallback(() => {\n setLoading(false);\n setError(null);\n setResult(null);\n }, []);\n\n return { execute, loading, error, result, reset };\n}\n"]}
1
+ {"version":3,"sources":["../../../../src/common/aiApi/client/settingsCore.ts","../../../../src/common/aiApi/client/aiApiClient.ts","../../../../src/common/aiApi/client/fetchModels.ts","../../../../src/common/aiApi/client/hooks/useAiTask.ts","../../../../src/common/aiApi/client/hooks/useAiModels.ts","../../../../src/common/aiApi/client/hooks/useAiServerConfig.ts","../../../../src/common/aiApi/client/context/AiApiSettingsContext.tsx","../../../../src/common/aiApi/client/components/AiApiConnectivityTest.tsx","../../../../src/common/aiApi/client/components/AiApiSettingsPanel.tsx"],"names":["useState","useCallback","useRef","useEffect","createContext","useMemo","React","useContext","CORE_CONNECTIVITY_TEST_TASK_ID","Loader2","Wifi","CheckCircle2","XCircle","Server","RefreshCw"],"mappings":"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","file":"index.js","sourcesContent":["import type { AiClientSettings, AudioStrategy } from '../types';\n\nexport interface AiApiSettings {\n apiKey: string;\n baseUrl: string;\n visionModel: string;\n textModel?: string;\n audioModel?: string;\n audioStrategy?: AudioStrategy;\n}\n\nexport const DEFAULT_AI_API_SETTINGS: AiApiSettings = {\n apiKey: '',\n baseUrl: 'https://api.openai.com/v1',\n visionModel: 'gpt-4o-mini',\n textModel: 'gpt-4o-mini',\n audioModel: 'whisper-1',\n audioStrategy: 'auto',\n};\n\nexport const AI_API_SETTINGS_STORAGE_KEY = 'ai-api-settings';\n\nexport function loadAiApiSettings(\n storageKey = AI_API_SETTINGS_STORAGE_KEY,\n defaults: AiApiSettings = DEFAULT_AI_API_SETTINGS\n): AiApiSettings {\n if (typeof window === 'undefined') return defaults;\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return defaults;\n return { ...defaults, ...JSON.parse(raw) };\n } catch {\n return defaults;\n }\n}\n\nexport function saveAiApiSettings(\n settings: AiApiSettings,\n storageKey = AI_API_SETTINGS_STORAGE_KEY\n): void {\n if (typeof window === 'undefined') return;\n localStorage.setItem(storageKey, JSON.stringify(settings));\n}\n\nexport function toClientSettings(settings: AiApiSettings): AiClientSettings | undefined {\n const client: AiClientSettings = {};\n if (settings.apiKey.trim()) client.apiKey = settings.apiKey.trim();\n if (settings.baseUrl.trim()) client.baseUrl = settings.baseUrl.trim();\n const visionModel = settings.visionModel.trim();\n const textModel = settings.textModel?.trim() || visionModel;\n const audioModel = settings.audioModel?.trim();\n if (visionModel) client.visionModel = visionModel;\n if (textModel) client.textModel = textModel;\n if (audioModel) client.audioModel = audioModel;\n if (settings.audioStrategy) client.audioStrategy = settings.audioStrategy;\n return Object.keys(client).length > 0 ? client : undefined;\n}\n\n/**\n * 浏览器未填写 API Key 时,完全依赖服务端环境变量(AI_API_KEY 等),\n * 避免 localStorage 中的 baseUrl/model 覆盖宿主 YAML 配置。\n */\nexport function toServerClientSettings(settings: AiApiSettings): AiClientSettings | undefined {\n if (!settings.apiKey.trim()) {\n return undefined;\n }\n return toClientSettings(settings);\n}\n\n/** 仅当浏览器填写了 API Key 时才向服务端传递 clientSettings */\nexport function pickClientSettingsFromStorage(\n storageKey = AI_API_SETTINGS_STORAGE_KEY,\n defaults: AiApiSettings = DEFAULT_AI_API_SETTINGS\n): AiClientSettings | undefined {\n return toServerClientSettings(loadAiApiSettings(storageKey, defaults));\n}\n","import type { AiApiResponse, AiApiRunRequest, AiClientSettings } from '../types';\nimport { pickClientSettingsFromStorage } from './settingsCore';\n\nexport class AiApiClientError extends Error {\n constructor(\n message: string,\n public readonly code?: string,\n public readonly response?: AiApiResponse\n ) {\n super(message);\n this.name = 'AiApiClientError';\n }\n}\n\nexport interface AiApiClientOptions {\n /** 默认 POST /api/ai/run,宿主应用可覆盖 */\n runEndpoint?: string;\n fetchImpl?: typeof fetch;\n}\n\n/**\n * 通过 HTTP 调用宿主暴露的 AI 任务入口(如 Next.js route)。\n * 连接配置可通过请求体 clientSettings 传入,或从 localStorage 读取。\n */\nexport async function runAiTask<TInput, TOutput>(\n taskId: string,\n input: TInput,\n options?: {\n signal?: AbortSignal;\n clientSettings?: AiClientSettings;\n runEndpoint?: string;\n fetchImpl?: typeof fetch;\n }\n): Promise<AiApiResponse<TOutput>> {\n const fetchFn = options?.fetchImpl ?? fetch;\n const endpoint = options?.runEndpoint ?? '/api/ai/run';\n // 显式传入 clientSettings: undefined 表示完全走服务端配置,不回退 localStorage\n const clientSettings =\n options !== undefined && 'clientSettings' in options\n ? options.clientSettings\n : pickClientSettingsFromStorage();\n\n const payload: AiApiRunRequest<TInput> = {\n taskId,\n input,\n ...(clientSettings ? { clientSettings } : {}),\n };\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: options?.signal,\n });\n\n const data = (await response.json()) as AiApiResponse<TOutput>;\n return data;\n}\n\nexport async function runAiTaskOrThrow<TInput, TOutput>(\n taskId: string,\n input: TInput,\n options?: {\n signal?: AbortSignal;\n clientSettings?: AiClientSettings;\n runEndpoint?: string;\n fetchImpl?: typeof fetch;\n }\n): Promise<TOutput> {\n const result = await runAiTask<TInput, TOutput>(taskId, input, options);\n if (!result.success || result.data === undefined) {\n throw new AiApiClientError(\n result.error?.message ?? 'AI 任务失败',\n result.error?.code,\n result\n );\n }\n return result.data;\n}\n\nexport function createAiTaskRunner<TInput, TOutput>(\n taskId: string,\n options?: AiApiClientOptions\n) {\n return (\n input: TInput,\n runOptions?: { signal?: AbortSignal; clientSettings?: AiClientSettings }\n ) =>\n runAiTaskOrThrow<TInput, TOutput>(taskId, input, {\n ...runOptions,\n runEndpoint: options?.runEndpoint,\n fetchImpl: options?.fetchImpl,\n });\n}\n\nexport const aiApiClient = {\n run: runAiTask,\n runOrThrow: runAiTaskOrThrow,\n createRunner: createAiTaskRunner,\n};\n","import type { AiClientSettings, AiModelsListResponse } from '../types';\n\nexport async function fetchAiModels(\n clientSettings?: AiClientSettings,\n options?: { signal?: AbortSignal; modelsEndpoint?: string; fetchImpl?: typeof fetch }\n): Promise<AiModelsListResponse> {\n const fetchFn = options?.fetchImpl ?? fetch;\n const endpoint = options?.modelsEndpoint ?? '/api/ai/models';\n\n const body: { clientSettings?: AiClientSettings } = {};\n if (clientSettings) {\n body.clientSettings = clientSettings;\n }\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: options?.signal,\n });\n\n return (await response.json()) as AiModelsListResponse;\n}\n","'use client';\n\nimport { useCallback, useState } from 'react';\nimport type { AiClientSettings, AiApiResponse } from '../../types';\nimport { runAiTask } from '../aiApiClient';\n\nexport function useAiTask<TInput, TOutput>(taskId: string) {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [result, setResult] = useState<AiApiResponse<TOutput> | null>(null);\n\n const execute = useCallback(\n async (\n input: TInput,\n options?: { signal?: AbortSignal; clientSettings?: AiClientSettings }\n ) => {\n setLoading(true);\n setError(null);\n try {\n const response = await runAiTask<TInput, TOutput>(taskId, input, options);\n setResult(response);\n if (!response.success) {\n setError(response.error?.message ?? 'AI 任务失败');\n }\n return response;\n } catch (err) {\n const message = err instanceof Error ? err.message : 'AI 任务失败';\n setError(message);\n const failed: AiApiResponse<TOutput> = {\n success: false,\n taskId,\n error: { code: 'AI_REQUEST_FAILED', message },\n };\n setResult(failed);\n return failed;\n } finally {\n setLoading(false);\n }\n },\n [taskId]\n );\n\n const reset = useCallback(() => {\n setLoading(false);\n setError(null);\n setResult(null);\n }, []);\n\n return { execute, loading, error, result, reset };\n}\n","'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { fetchAiModels } from '../fetchModels';\nimport type { AiApiSettings } from '../settingsCore';\nimport { toServerClientSettings } from '../settingsCore';\n\nexport interface UseAiModelsOptions {\n modelsEndpoint?: string;\n debounceMs?: number;\n}\n\nexport interface UseAiModelsResult {\n visionModels: string[];\n allModels: string[];\n loading: boolean;\n error: string | null;\n refresh: () => void;\n}\n\nexport function useAiModels(\n settings: AiApiSettings,\n onSuggestedModel?: (model: string) => void,\n options?: UseAiModelsOptions\n): UseAiModelsResult {\n const [visionModels, setVisionModels] = useState<string[]>([]);\n const [allModels, setAllModels] = useState<string[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const onSuggestedRef = useRef(onSuggestedModel);\n const settingsRef = useRef(settings);\n onSuggestedRef.current = onSuggestedModel;\n settingsRef.current = settings;\n\n const load = useCallback(async () => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n setLoading(true);\n setError(null);\n\n const currentSettings = settingsRef.current;\n\n try {\n const clientSettings = toServerClientSettings(currentSettings);\n const result = await fetchAiModels(clientSettings, {\n signal: controller.signal,\n modelsEndpoint: options?.modelsEndpoint,\n });\n\n if (controller.signal.aborted) return;\n\n if (!result.success) {\n setVisionModels([]);\n setAllModels([]);\n setError(result.error?.message ?? '获取模型列表失败');\n return;\n }\n\n setVisionModels(result.visionModels);\n setAllModels(result.models);\n\n if (result.suggestedVisionModel) {\n const current = currentSettings.visionModel.trim();\n const shouldAutoSelect =\n !current ||\n (!result.visionModels.includes(current) && !result.models.includes(current));\n\n if (shouldAutoSelect) {\n onSuggestedRef.current?.(result.suggestedVisionModel);\n }\n }\n } catch (err) {\n if (controller.signal.aborted) return;\n setVisionModels([]);\n setAllModels([]);\n setError(err instanceof Error ? err.message : '获取模型列表失败');\n } finally {\n if (!controller.signal.aborted) {\n setLoading(false);\n }\n }\n }, [options?.modelsEndpoint, settings.apiKey, settings.baseUrl]);\n\n useEffect(() => {\n const debounceMs = options?.debounceMs ?? 600;\n const timer = window.setTimeout(() => {\n void load();\n }, debounceMs);\n\n return () => {\n window.clearTimeout(timer);\n abortRef.current?.abort();\n };\n }, [load, options?.debounceMs]);\n\n return {\n visionModels,\n allModels,\n loading,\n error,\n refresh: load,\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\nimport type { AiServerConfigStatus } from '../../types';\n\nexport interface UseAiServerConfigOptions {\n /** 宿主实现的配置状态端点,默认 GET /api/ai/config */\n configEndpoint?: string;\n}\n\nexport function useAiServerConfig(options?: UseAiServerConfigOptions) {\n const [config, setConfig] = useState<AiServerConfigStatus | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const configEndpoint = options?.configEndpoint ?? '/api/ai/config';\n\n useEffect(() => {\n let cancelled = false;\n\n async function load() {\n try {\n const response = await fetch(configEndpoint, { credentials: 'include' });\n if (!response.ok) {\n if (!cancelled) {\n setConfig({ serverConfigured: false });\n setError(response.status === 401 ? '请先登录' : '无法读取服务端配置');\n }\n return;\n }\n const data = (await response.json()) as AiServerConfigStatus;\n if (!cancelled) {\n setConfig(data);\n setError(null);\n }\n } catch (err) {\n if (!cancelled) {\n setConfig({ serverConfigured: false });\n setError(err instanceof Error ? err.message : '无法读取服务端配置');\n }\n } finally {\n if (!cancelled) setLoading(false);\n }\n }\n\n void load();\n return () => {\n cancelled = true;\n };\n }, [configEndpoint]);\n\n return { config, loading, error };\n}\n","'use client';\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport {\n type AiApiSettings,\n DEFAULT_AI_API_SETTINGS,\n loadAiApiSettings,\n saveAiApiSettings,\n} from '../settingsCore';\n\nexport interface AiApiSettingsContextValue {\n settings: AiApiSettings;\n updateSettings: (updates: Partial<AiApiSettings>) => void;\n resetSettings: () => void;\n}\n\nexport interface AiApiSettingsProviderProps {\n children: React.ReactNode;\n /** 宿主可覆盖默认 baseUrl / model(如 MiMo) */\n defaultSettings?: Partial<AiApiSettings>;\n storageKey?: string;\n}\n\nconst AiApiSettingsContext = createContext<AiApiSettingsContextValue | null>(null);\n\nexport function AiApiSettingsProvider({\n children,\n defaultSettings,\n storageKey,\n}: AiApiSettingsProviderProps) {\n const mergedDefaults = useMemo(\n () => ({ ...DEFAULT_AI_API_SETTINGS, ...defaultSettings }),\n [defaultSettings]\n );\n\n const [settings, setSettings] = useState<AiApiSettings>(mergedDefaults);\n\n useEffect(() => {\n setSettings(loadAiApiSettings(storageKey, mergedDefaults));\n }, [storageKey, mergedDefaults]);\n\n const persist = useCallback(\n (next: AiApiSettings) => {\n setSettings(next);\n saveAiApiSettings(next, storageKey);\n },\n [storageKey]\n );\n\n const updateSettings = useCallback(\n (updates: Partial<AiApiSettings>) => {\n setSettings((prev) => {\n const next = { ...prev, ...updates };\n saveAiApiSettings(next, storageKey);\n return next;\n });\n },\n [storageKey]\n );\n\n const resetSettings = useCallback(() => {\n persist(mergedDefaults);\n }, [persist, mergedDefaults]);\n\n const value = useMemo(\n () => ({ settings, updateSettings, resetSettings }),\n [settings, updateSettings, resetSettings]\n );\n\n return (\n <AiApiSettingsContext.Provider value={value}>{children}</AiApiSettingsContext.Provider>\n );\n}\n\nexport function useAiApiSettings() {\n const ctx = useContext(AiApiSettingsContext);\n if (!ctx) {\n throw new Error('useAiApiSettings must be used within AiApiSettingsProvider');\n }\n return ctx;\n}\n","'use client';\n\nimport React, { useCallback, useState } from 'react';\nimport { CheckCircle2, Loader2, Wifi, XCircle } from 'lucide-react';\nimport { CORE_CONNECTIVITY_TEST_TASK_ID } from '../../types';\nimport type { ConnectivityTestOutput } from '../../types';\nimport { useAiApiSettings } from '../context/AiApiSettingsContext';\nimport { runAiTask } from '../aiApiClient';\nimport { toServerClientSettings } from '../settingsCore';\n\ntype TestStatus = 'idle' | 'loading' | 'success' | 'error';\n\nexport interface AiApiConnectivityTestProps {\n runEndpoint?: string;\n}\n\nexport function AiApiConnectivityTest({ runEndpoint }: AiApiConnectivityTestProps) {\n const { settings } = useAiApiSettings();\n const [status, setStatus] = useState<TestStatus>('idle');\n const [message, setMessage] = useState('');\n const [meta, setMeta] = useState<{ model?: string; latencyMs?: number } | null>(null);\n\n const handleTest = useCallback(async () => {\n setStatus('loading');\n setMessage('');\n setMeta(null);\n\n const started = Date.now();\n const clientSettings = toServerClientSettings(settings);\n\n try {\n const result = await runAiTask<Record<string, never>, ConnectivityTestOutput>(\n CORE_CONNECTIVITY_TEST_TASK_ID,\n {},\n { clientSettings, runEndpoint }\n );\n\n const latencyMs = result.meta?.latencyMs ?? Date.now() - started;\n\n if (!result.success) {\n setStatus('error');\n setMessage(result.error?.message ?? '连通性测试失败');\n setMeta({ latencyMs });\n return;\n }\n\n setStatus('success');\n setMessage(result.data?.reply ? `响应:${result.data.reply}` : '连接正常');\n setMeta({ model: result.meta?.model, latencyMs });\n } catch (err) {\n setStatus('error');\n setMessage(err instanceof Error ? err.message : '网络请求失败');\n setMeta({ latencyMs: Date.now() - started });\n }\n }, [settings, runEndpoint]);\n\n return (\n <div className=\"rounded-xl border border-slate-200 bg-slate-50/80 p-4\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h3 className=\"text-sm font-medium text-slate-900\">连通性测试</h3>\n <p className=\"mt-1 text-xs text-slate-500\">\n 使用当前填写的 Key、Base URL 与模型发起一次轻量请求(需已登录)。未填写 Key 时将使用服务端配置。\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void handleTest()}\n disabled={status === 'loading'}\n className=\"inline-flex h-10 items-center gap-2 rounded-lg bg-slate-900 px-4 text-sm font-medium text-white transition-transform hover:bg-slate-800 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {status === 'loading' ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Wifi className=\"h-4 w-4\" />\n )}\n {status === 'loading' ? '测试中…' : '测试连接'}\n </button>\n </div>\n\n {status !== 'idle' && (\n <div\n className={`mt-3 flex items-start gap-2 rounded-lg px-3 py-2.5 text-sm ${\n status === 'success'\n ? 'bg-emerald-50 text-emerald-800'\n : status === 'error'\n ? 'bg-red-50 text-red-800'\n : 'bg-white text-slate-600'\n }`}\n >\n {status === 'loading' && <Loader2 className=\"mt-0.5 h-4 w-4 shrink-0 animate-spin\" />}\n {status === 'success' && <CheckCircle2 className=\"mt-0.5 h-4 w-4 shrink-0\" />}\n {status === 'error' && <XCircle className=\"mt-0.5 h-4 w-4 shrink-0\" />}\n <div className=\"min-w-0\">\n <p className=\"text-pretty\">{status === 'loading' ? '正在连接 AI 服务…' : message}</p>\n {meta && (meta.model || meta.latencyMs !== undefined) && status !== 'loading' && (\n <p className=\"mt-1 tabular-nums text-xs opacity-80\">\n {meta.model && <span>模型 {meta.model}</span>}\n {meta.model && meta.latencyMs !== undefined && <span> · </span>}\n {meta.latencyMs !== undefined && <span>{meta.latencyMs} ms</span>}\n </p>\n )}\n </div>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport React, { useCallback, useState } from 'react';\nimport { CheckCircle2, Loader2, RefreshCw, Server } from 'lucide-react';\nimport { useAiApiSettings } from '../context/AiApiSettingsContext';\nimport { useAiModels } from '../hooks/useAiModels';\nimport { useAiServerConfig } from '../hooks/useAiServerConfig';\nimport { AiApiConnectivityTest } from './AiApiConnectivityTest';\n\nexport interface AiApiSettingsPanelProps {\n /** 宿主实现的配置状态端点 */\n serverConfigEndpoint?: string;\n modelsEndpoint?: string;\n runEndpoint?: string;\n /** 未配置服务端时的提示文案 */\n serverMissingHint?: React.ReactNode;\n apiKeyPlaceholder?: string;\n baseUrlPlaceholder?: string;\n visionModelPlaceholder?: string;\n}\n\nexport function AiApiSettingsPanel({\n serverConfigEndpoint,\n modelsEndpoint,\n runEndpoint,\n serverMissingHint,\n apiKeyPlaceholder = 'sk-…',\n baseUrlPlaceholder = 'https://api.openai.com/v1',\n visionModelPlaceholder = 'gpt-4o-mini',\n}: AiApiSettingsPanelProps) {\n const { settings, updateSettings } = useAiApiSettings();\n const { config: serverConfig, loading: serverLoading } = useAiServerConfig({\n configEndpoint: serverConfigEndpoint,\n });\n const [showOverride, setShowOverride] = useState(false);\n\n const hasBrowserKey = Boolean(settings.apiKey.trim());\n const serverReady = Boolean(serverConfig?.serverConfigured);\n const useServerOnly = serverReady && !hasBrowserKey && !showOverride;\n\n const handleSuggestedModel = useCallback(\n (model: string) => {\n updateSettings({ visionModel: model });\n },\n [updateSettings]\n );\n\n const { visionModels, allModels, loading, error, refresh } = useAiModels(\n settings,\n handleSuggestedModel,\n { modelsEndpoint }\n );\n\n const selectableModels = visionModels;\n const showDropdown = selectableModels.length > 0;\n const showAllModelsFallback = visionModels.length === 0 && allModels.length > 0;\n\n const defaultServerMissingHint = (\n <>\n 请在宿主配置文件中填写 AI API Key(如 YAML 的 <code className=\"rounded bg-amber-100 px-1\">ai.apiKey</code>\n ),或在下方的浏览器设置中填写。\n </>\n );\n\n return (\n <div className=\"space-y-6\">\n {serverLoading ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n 正在检查服务端 AI 配置…\n </div>\n ) : serverReady && !hasBrowserKey ? (\n <div className=\"rounded-xl border border-emerald-200 bg-emerald-50/80 p-4\">\n <div className=\"flex items-start gap-3\">\n <CheckCircle2 className=\"mt-0.5 h-5 w-5 shrink-0 text-emerald-600\" />\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-medium text-emerald-900\">已由服务端配置</h3>\n <p className=\"mt-1 text-xs text-emerald-800/90\">\n AI 功能将使用服务端配置的 API Key,无需在此重复填写。\n </p>\n <dl className=\"mt-3 grid gap-1 text-xs text-emerald-900/80\">\n {serverConfig?.baseUrl && (\n <div className=\"flex gap-2\">\n <dt className=\"shrink-0 font-medium\">Base URL</dt>\n <dd className=\"truncate font-mono\">{serverConfig.baseUrl}</dd>\n </div>\n )}\n {serverConfig?.visionModel && (\n <div className=\"flex gap-2\">\n <dt className=\"shrink-0 font-medium\">视觉模型</dt>\n <dd className=\"font-mono\">{serverConfig.visionModel}</dd>\n </div>\n )}\n </dl>\n <button\n type=\"button\"\n onClick={() => setShowOverride((v) => !v)}\n className=\"mt-3 text-xs font-medium text-emerald-700 underline-offset-2 hover:underline\"\n >\n {showOverride ? '收起自定义配置' : '高级:使用浏览器自定义 Key 覆盖服务端'}\n </button>\n </div>\n </div>\n </div>\n ) : !serverReady && !hasBrowserKey ? (\n <div className=\"rounded-xl border border-amber-200 bg-amber-50/80 p-4\">\n <div className=\"flex items-start gap-3\">\n <Server className=\"mt-0.5 h-5 w-5 shrink-0 text-amber-600\" />\n <div>\n <h3 className=\"text-sm font-medium text-amber-900\">服务端未配置 AI</h3>\n <p className=\"mt-1 text-xs text-amber-800/90\">\n {serverMissingHint ?? defaultServerMissingHint}\n </p>\n </div>\n </div>\n </div>\n ) : null}\n\n {!useServerOnly && (\n <>\n <div>\n <label htmlFor=\"ai-api-key\" className=\"mb-2 block text-sm font-medium text-gray-700\">\n API Key\n </label>\n <input\n id=\"ai-api-key\"\n type=\"password\"\n autoComplete=\"off\"\n value={settings.apiKey}\n onChange={(e) => updateSettings({ apiKey: e.target.value })}\n placeholder={apiKeyPlaceholder}\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n <p className=\"mt-1.5 text-xs text-gray-500\">\n 保存在本机浏览器。填写后将覆盖服务端配置;留空则使用服务端密钥。\n </p>\n </div>\n\n <div>\n <label htmlFor=\"ai-base-url\" className=\"mb-2 block text-sm font-medium text-gray-700\">\n API Base URL\n </label>\n <input\n id=\"ai-base-url\"\n type=\"url\"\n value={settings.baseUrl}\n onChange={(e) => updateSettings({ baseUrl: e.target.value })}\n placeholder={baseUrlPlaceholder}\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n <p className=\"mt-1.5 text-xs text-gray-500\">\n 填写 Key 与地址后将自动拉取可用模型并选择合适的视觉模型。\n </p>\n </div>\n </>\n )}\n\n {!useServerOnly && (\n <>\n <div>\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <label htmlFor=\"ai-vision-model\" className=\"text-sm font-medium text-gray-700\">\n 视觉模型\n </label>\n <button\n type=\"button\"\n onClick={() => void refresh()}\n disabled={loading}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 disabled:opacity-50\"\n >\n {loading ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <RefreshCw className=\"h-3.5 w-3.5\" />\n )}\n 刷新模型\n </button>\n </div>\n\n {showDropdown ? (\n <select\n id=\"ai-vision-model\"\n value={settings.visionModel}\n onChange={(e) => updateSettings({ visionModel: e.target.value })}\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\"\n >\n {selectableModels.map((model) => (\n <option key={model} value={model}>\n {model}\n </option>\n ))}\n {settings.visionModel && !selectableModels.includes(settings.visionModel) && (\n <option value={settings.visionModel}>{settings.visionModel}(当前)</option>\n )}\n </select>\n ) : (\n <input\n id=\"ai-vision-model\"\n type=\"text\"\n value={settings.visionModel}\n onChange={(e) => updateSettings({ visionModel: e.target.value })}\n placeholder={visionModelPlaceholder}\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n )}\n\n {loading && (\n <p className=\"mt-1.5 flex items-center gap-1.5 text-xs text-gray-500\">\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n 正在读取可用模型…\n </p>\n )}\n {!loading && error && (\n <p className=\"mt-1.5 text-xs text-amber-600\">\n 无法自动获取模型列表:{error}。可手动填写模型名称。\n </p>\n )}\n {!loading && !error && showDropdown && (\n <p className=\"mt-1.5 text-xs text-gray-500\">\n 已加载 {selectableModels.length} 个\n {visionModels.length > 0 ? '视觉' : '对话'}模型,可手动切换。\n </p>\n )}\n {!loading && !error && showAllModelsFallback && (\n <p className=\"mt-1.5 text-xs text-amber-600\">\n 未识别到视觉模型,请手动填写支持识图的模型名。\n </p>\n )}\n {!loading && !error && !showDropdown && !showAllModelsFallback && (\n <p className=\"mt-1.5 text-xs text-gray-500\">需支持图片输入的多模态模型。</p>\n )}\n </div>\n\n <div>\n <label htmlFor=\"ai-text-model\" className=\"mb-2 block text-sm font-medium text-gray-700\">\n 文本模型\n </label>\n <input\n id=\"ai-text-model\"\n type=\"text\"\n value={settings.textModel ?? settings.visionModel}\n onChange={(e) => updateSettings({ textModel: e.target.value })}\n placeholder={visionModelPlaceholder}\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n <p className=\"mt-1.5 text-xs text-gray-500\">纯文本与 STT 转写后的对话;默认同视觉模型。</p>\n </div>\n\n <div>\n <label htmlFor=\"ai-audio-model\" className=\"mb-2 block text-sm font-medium text-gray-700\">\n 语音转写模型(STT)\n </label>\n <input\n id=\"ai-audio-model\"\n type=\"text\"\n value={settings.audioModel ?? 'whisper-1'}\n onChange={(e) => updateSettings({ audioModel: e.target.value })}\n placeholder=\"whisper-1\"\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n <p className=\"mt-1.5 text-xs text-gray-500\">\n 多模态 auto 模式下,不支持内嵌音频时将用此模型转写(Whisper 等)。\n </p>\n </div>\n </>\n )}\n\n <AiApiConnectivityTest runEndpoint={runEndpoint} />\n </div>\n );\n}\n"]}