sa2kit 3.7.0 → 3.8.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.
Files changed (33) 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-GJVEYCS4.js +12 -0
  4. package/dist/chunk-GJVEYCS4.js.map +1 -0
  5. package/dist/chunk-PPV4IEWR.mjs +8 -0
  6. package/dist/chunk-PPV4IEWR.mjs.map +1 -0
  7. package/dist/{chunk-XPY45Y75.js → chunk-VVHFMAE7.js} +9 -21
  8. package/dist/chunk-VVHFMAE7.js.map +1 -0
  9. package/dist/common/aiApi/client/index.d.mts +66 -4
  10. package/dist/common/aiApi/client/index.d.ts +66 -4
  11. package/dist/common/aiApi/client/index.js +360 -15
  12. package/dist/common/aiApi/client/index.js.map +1 -1
  13. package/dist/common/aiApi/client/index.mjs +345 -11
  14. package/dist/common/aiApi/client/index.mjs.map +1 -1
  15. package/dist/common/aiApi/index.d.mts +2 -2
  16. package/dist/common/aiApi/index.d.ts +2 -2
  17. package/dist/common/aiApi/index.js +63 -62
  18. package/dist/common/aiApi/index.mjs +2 -1
  19. package/dist/common/aiApi/server/index.d.mts +1 -1
  20. package/dist/common/aiApi/server/index.d.ts +1 -1
  21. package/dist/common/aiApi/server/index.js +63 -62
  22. package/dist/common/aiApi/server/index.mjs +2 -1
  23. package/dist/index.d.mts +9 -1
  24. package/dist/index.d.ts +9 -1
  25. package/dist/index.js +3 -9
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +3 -9
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/{types-CiqMQ-uu.d.mts → types-DgbG0Of-.d.mts} +9 -1
  30. package/dist/{types-CiqMQ-uu.d.ts → types-DgbG0Of-.d.ts} +9 -1
  31. package/package.json +1 -1
  32. package/dist/chunk-XPY45Y75.js.map +0 -1
  33. package/dist/chunk-ZJLS5JU5.mjs.map +0 -1
@@ -1,4 +1,5 @@
1
- import { A as AiClientSettings, a as AiApiResponse, b as AiModelsListResponse, c as AudioStrategy } from '../../../types-CiqMQ-uu.mjs';
1
+ import { A as AiClientSettings, a as AiApiResponse, b as AiModelsListResponse, c as AudioStrategy, d as AiServerConfigStatus } from '../../../types-DgbG0Of-.mjs';
2
+ import React__default from 'react';
2
3
 
3
4
  declare class AiApiClientError extends Error {
4
5
  readonly code?: string | undefined;
@@ -63,9 +64,70 @@ interface AiApiSettings {
63
64
  }
64
65
  declare const DEFAULT_AI_API_SETTINGS: AiApiSettings;
65
66
  declare const AI_API_SETTINGS_STORAGE_KEY = "ai-api-settings";
66
- declare function loadAiApiSettings(storageKey?: string): AiApiSettings;
67
+ declare function loadAiApiSettings(storageKey?: string, defaults?: AiApiSettings): AiApiSettings;
67
68
  declare function saveAiApiSettings(settings: AiApiSettings, storageKey?: string): void;
68
69
  declare function toClientSettings(settings: AiApiSettings): AiClientSettings | undefined;
69
- declare function pickClientSettingsFromStorage(storageKey?: string): AiClientSettings | undefined;
70
+ /**
71
+ * 浏览器未填写 API Key 时,完全依赖服务端环境变量(AI_API_KEY 等),
72
+ * 避免 localStorage 中的 baseUrl/model 覆盖宿主 YAML 配置。
73
+ */
74
+ declare function toServerClientSettings(settings: AiApiSettings): AiClientSettings | undefined;
75
+ /** 仅当浏览器填写了 API Key 时才向服务端传递 clientSettings */
76
+ declare function pickClientSettingsFromStorage(storageKey?: string, defaults?: AiApiSettings): AiClientSettings | undefined;
77
+
78
+ interface UseAiModelsOptions {
79
+ modelsEndpoint?: string;
80
+ debounceMs?: number;
81
+ }
82
+ interface UseAiModelsResult {
83
+ visionModels: string[];
84
+ allModels: string[];
85
+ loading: boolean;
86
+ error: string | null;
87
+ refresh: () => void;
88
+ }
89
+ declare function useAiModels(settings: AiApiSettings, onSuggestedModel?: (model: string) => void, options?: UseAiModelsOptions): UseAiModelsResult;
90
+
91
+ interface UseAiServerConfigOptions {
92
+ /** 宿主实现的配置状态端点,默认 GET /api/ai/config */
93
+ configEndpoint?: string;
94
+ }
95
+ declare function useAiServerConfig(options?: UseAiServerConfigOptions): {
96
+ config: AiServerConfigStatus | null;
97
+ loading: boolean;
98
+ error: string | null;
99
+ };
100
+
101
+ interface AiApiSettingsContextValue {
102
+ settings: AiApiSettings;
103
+ updateSettings: (updates: Partial<AiApiSettings>) => void;
104
+ resetSettings: () => void;
105
+ }
106
+ interface AiApiSettingsProviderProps {
107
+ children: React__default.ReactNode;
108
+ /** 宿主可覆盖默认 baseUrl / model(如 MiMo) */
109
+ defaultSettings?: Partial<AiApiSettings>;
110
+ storageKey?: string;
111
+ }
112
+ declare function AiApiSettingsProvider({ children, defaultSettings, storageKey, }: AiApiSettingsProviderProps): React__default.JSX.Element;
113
+ declare function useAiApiSettings(): AiApiSettingsContextValue;
114
+
115
+ interface AiApiSettingsPanelProps {
116
+ /** 宿主实现的配置状态端点 */
117
+ serverConfigEndpoint?: string;
118
+ modelsEndpoint?: string;
119
+ runEndpoint?: string;
120
+ /** 未配置服务端时的提示文案 */
121
+ serverMissingHint?: React__default.ReactNode;
122
+ apiKeyPlaceholder?: string;
123
+ baseUrlPlaceholder?: string;
124
+ visionModelPlaceholder?: string;
125
+ }
126
+ declare function AiApiSettingsPanel({ serverConfigEndpoint, modelsEndpoint, runEndpoint, serverMissingHint, apiKeyPlaceholder, baseUrlPlaceholder, visionModelPlaceholder, }: AiApiSettingsPanelProps): React__default.JSX.Element;
127
+
128
+ interface AiApiConnectivityTestProps {
129
+ runEndpoint?: string;
130
+ }
131
+ declare function AiApiConnectivityTest({ runEndpoint }: AiApiConnectivityTestProps): React__default.JSX.Element;
70
132
 
71
- export { AI_API_SETTINGS_STORAGE_KEY, AiApiClientError, type AiApiClientOptions, type AiApiSettings, DEFAULT_AI_API_SETTINGS, aiApiClient, createAiTaskRunner, fetchAiModels, loadAiApiSettings, pickClientSettingsFromStorage, runAiTask, runAiTaskOrThrow, saveAiApiSettings, toClientSettings, useAiTask };
133
+ export { AI_API_SETTINGS_STORAGE_KEY, AiApiClientError, type AiApiClientOptions, AiApiConnectivityTest, type AiApiConnectivityTestProps, type AiApiSettings, type AiApiSettingsContextValue, AiApiSettingsPanel, type AiApiSettingsPanelProps, AiApiSettingsProvider, type AiApiSettingsProviderProps, DEFAULT_AI_API_SETTINGS, type UseAiModelsOptions, type UseAiModelsResult, type UseAiServerConfigOptions, aiApiClient, createAiTaskRunner, fetchAiModels, loadAiApiSettings, pickClientSettingsFromStorage, runAiTask, runAiTaskOrThrow, saveAiApiSettings, toClientSettings, toServerClientSettings, useAiApiSettings, useAiModels, useAiServerConfig, useAiTask };
@@ -1,4 +1,5 @@
1
- import { A as AiClientSettings, a as AiApiResponse, b as AiModelsListResponse, c as AudioStrategy } from '../../../types-CiqMQ-uu.js';
1
+ import { A as AiClientSettings, a as AiApiResponse, b as AiModelsListResponse, c as AudioStrategy, d as AiServerConfigStatus } from '../../../types-DgbG0Of-.js';
2
+ import React__default from 'react';
2
3
 
3
4
  declare class AiApiClientError extends Error {
4
5
  readonly code?: string | undefined;
@@ -63,9 +64,70 @@ interface AiApiSettings {
63
64
  }
64
65
  declare const DEFAULT_AI_API_SETTINGS: AiApiSettings;
65
66
  declare const AI_API_SETTINGS_STORAGE_KEY = "ai-api-settings";
66
- declare function loadAiApiSettings(storageKey?: string): AiApiSettings;
67
+ declare function loadAiApiSettings(storageKey?: string, defaults?: AiApiSettings): AiApiSettings;
67
68
  declare function saveAiApiSettings(settings: AiApiSettings, storageKey?: string): void;
68
69
  declare function toClientSettings(settings: AiApiSettings): AiClientSettings | undefined;
69
- declare function pickClientSettingsFromStorage(storageKey?: string): AiClientSettings | undefined;
70
+ /**
71
+ * 浏览器未填写 API Key 时,完全依赖服务端环境变量(AI_API_KEY 等),
72
+ * 避免 localStorage 中的 baseUrl/model 覆盖宿主 YAML 配置。
73
+ */
74
+ declare function toServerClientSettings(settings: AiApiSettings): AiClientSettings | undefined;
75
+ /** 仅当浏览器填写了 API Key 时才向服务端传递 clientSettings */
76
+ declare function pickClientSettingsFromStorage(storageKey?: string, defaults?: AiApiSettings): AiClientSettings | undefined;
77
+
78
+ interface UseAiModelsOptions {
79
+ modelsEndpoint?: string;
80
+ debounceMs?: number;
81
+ }
82
+ interface UseAiModelsResult {
83
+ visionModels: string[];
84
+ allModels: string[];
85
+ loading: boolean;
86
+ error: string | null;
87
+ refresh: () => void;
88
+ }
89
+ declare function useAiModels(settings: AiApiSettings, onSuggestedModel?: (model: string) => void, options?: UseAiModelsOptions): UseAiModelsResult;
90
+
91
+ interface UseAiServerConfigOptions {
92
+ /** 宿主实现的配置状态端点,默认 GET /api/ai/config */
93
+ configEndpoint?: string;
94
+ }
95
+ declare function useAiServerConfig(options?: UseAiServerConfigOptions): {
96
+ config: AiServerConfigStatus | null;
97
+ loading: boolean;
98
+ error: string | null;
99
+ };
100
+
101
+ interface AiApiSettingsContextValue {
102
+ settings: AiApiSettings;
103
+ updateSettings: (updates: Partial<AiApiSettings>) => void;
104
+ resetSettings: () => void;
105
+ }
106
+ interface AiApiSettingsProviderProps {
107
+ children: React__default.ReactNode;
108
+ /** 宿主可覆盖默认 baseUrl / model(如 MiMo) */
109
+ defaultSettings?: Partial<AiApiSettings>;
110
+ storageKey?: string;
111
+ }
112
+ declare function AiApiSettingsProvider({ children, defaultSettings, storageKey, }: AiApiSettingsProviderProps): React__default.JSX.Element;
113
+ declare function useAiApiSettings(): AiApiSettingsContextValue;
114
+
115
+ interface AiApiSettingsPanelProps {
116
+ /** 宿主实现的配置状态端点 */
117
+ serverConfigEndpoint?: string;
118
+ modelsEndpoint?: string;
119
+ runEndpoint?: string;
120
+ /** 未配置服务端时的提示文案 */
121
+ serverMissingHint?: React__default.ReactNode;
122
+ apiKeyPlaceholder?: string;
123
+ baseUrlPlaceholder?: string;
124
+ visionModelPlaceholder?: string;
125
+ }
126
+ declare function AiApiSettingsPanel({ serverConfigEndpoint, modelsEndpoint, runEndpoint, serverMissingHint, apiKeyPlaceholder, baseUrlPlaceholder, visionModelPlaceholder, }: AiApiSettingsPanelProps): React__default.JSX.Element;
127
+
128
+ interface AiApiConnectivityTestProps {
129
+ runEndpoint?: string;
130
+ }
131
+ declare function AiApiConnectivityTest({ runEndpoint }: AiApiConnectivityTestProps): React__default.JSX.Element;
70
132
 
71
- export { AI_API_SETTINGS_STORAGE_KEY, AiApiClientError, type AiApiClientOptions, type AiApiSettings, DEFAULT_AI_API_SETTINGS, aiApiClient, createAiTaskRunner, fetchAiModels, loadAiApiSettings, pickClientSettingsFromStorage, runAiTask, runAiTaskOrThrow, saveAiApiSettings, toClientSettings, useAiTask };
133
+ export { AI_API_SETTINGS_STORAGE_KEY, AiApiClientError, type AiApiClientOptions, AiApiConnectivityTest, type AiApiConnectivityTestProps, type AiApiSettings, type AiApiSettingsContextValue, AiApiSettingsPanel, type AiApiSettingsPanelProps, AiApiSettingsProvider, type AiApiSettingsProviderProps, DEFAULT_AI_API_SETTINGS, type UseAiModelsOptions, type UseAiModelsResult, type UseAiServerConfigOptions, aiApiClient, createAiTaskRunner, fetchAiModels, loadAiApiSettings, pickClientSettingsFromStorage, runAiTask, runAiTaskOrThrow, saveAiApiSettings, toClientSettings, toServerClientSettings, useAiApiSettings, useAiModels, useAiServerConfig, useAiTask };
@@ -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