sa2kit 3.6.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.
- package/dist/business/index.d.mts +2 -7
- package/dist/business/index.d.ts +2 -7
- package/dist/business/index.js +1761 -8045
- package/dist/business/index.js.map +1 -1
- package/dist/business/index.mjs +1169 -7452
- package/dist/business/index.mjs.map +1 -1
- package/dist/business/portfolio/index.d.mts +1 -2
- package/dist/business/portfolio/index.d.ts +1 -2
- package/dist/{chunk-ZJLS5JU5.mjs → chunk-6KD4CD7O.mjs} +7 -16
- package/dist/chunk-6KD4CD7O.mjs.map +1 -0
- package/dist/chunk-GJVEYCS4.js +12 -0
- package/dist/chunk-GJVEYCS4.js.map +1 -0
- package/dist/chunk-PPV4IEWR.mjs +8 -0
- package/dist/chunk-PPV4IEWR.mjs.map +1 -0
- package/dist/{chunk-XPY45Y75.js → chunk-VVHFMAE7.js} +9 -21
- package/dist/chunk-VVHFMAE7.js.map +1 -0
- package/dist/common/aiApi/client/index.d.mts +66 -4
- package/dist/common/aiApi/client/index.d.ts +66 -4
- package/dist/common/aiApi/client/index.js +360 -15
- package/dist/common/aiApi/client/index.js.map +1 -1
- package/dist/common/aiApi/client/index.mjs +345 -11
- package/dist/common/aiApi/client/index.mjs.map +1 -1
- package/dist/common/aiApi/index.d.mts +2 -2
- package/dist/common/aiApi/index.d.ts +2 -2
- package/dist/common/aiApi/index.js +63 -62
- package/dist/common/aiApi/index.mjs +2 -1
- package/dist/common/aiApi/server/index.d.mts +1 -1
- package/dist/common/aiApi/server/index.d.ts +1 -1
- package/dist/common/aiApi/server/index.js +63 -62
- package/dist/common/aiApi/server/index.mjs +2 -1
- package/dist/{index-BSmd4ikf.d.ts → index-B4wDXFL0.d.mts} +39 -2
- package/dist/{index-r2-zE3iC.d.mts → index-B4wDXFL0.d.ts} +39 -2
- package/dist/{index-DLLPTprx.d.mts → index-B9vXYzok.d.mts} +48 -13
- package/dist/{index-VFDbZxVM.d.ts → index-BZ0MhRau.d.ts} +48 -13
- package/dist/index.d.mts +398 -28
- package/dist/index.d.ts +398 -28
- package/dist/index.js +1831 -6859
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1121 -6149
- package/dist/index.mjs.map +1 -1
- package/dist/{types-CiqMQ-uu.d.mts → types-DgbG0Of-.d.mts} +9 -1
- package/dist/{types-CiqMQ-uu.d.ts → types-DgbG0Of-.d.ts} +9 -1
- package/package.json +1 -11
- package/dist/CollisionBalls-DgKtscU2.d.mts +0 -41
- package/dist/CollisionBalls-DgKtscU2.d.ts +0 -41
- package/dist/business/calendar/index.d.mts +0 -6
- package/dist/business/calendar/index.d.ts +0 -6
- package/dist/business/calendar/index.js +0 -7433
- package/dist/business/calendar/index.js.map +0 -1
- package/dist/business/calendar/index.mjs +0 -7257
- package/dist/business/calendar/index.mjs.map +0 -1
- package/dist/business/calendar/routes/index.d.mts +0 -191
- package/dist/business/calendar/routes/index.d.ts +0 -191
- package/dist/business/calendar/routes/index.js +0 -844
- package/dist/business/calendar/routes/index.js.map +0 -1
- package/dist/business/calendar/routes/index.mjs +0 -826
- package/dist/business/calendar/routes/index.mjs.map +0 -1
- package/dist/chunk-XPY45Y75.js.map +0 -1
- package/dist/chunk-ZJLS5JU5.mjs.map +0 -1
- package/dist/index-BMgdH5dL.d.mts +0 -1716
- package/dist/index-IXMAeTtN.d.ts +0 -1716
|
@@ -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"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { CORE_CONNECTIVITY_TEST_TASK_ID } from '../../../chunk-PPV4IEWR.mjs';
|
|
1
2
|
import '../../../chunk-MAI35PU6.mjs';
|
|
2
|
-
import { useState, useCallback } from 'react';
|
|
3
|
+
import React3, { createContext, useState, useCallback, useRef, useEffect, useMemo, useContext } from 'react';
|
|
4
|
+
import { Loader2, Wifi, CheckCircle2, XCircle, Server, RefreshCw } from 'lucide-react';
|
|
3
5
|
|
|
4
6
|
// src/common/aiApi/client/settingsCore.ts
|
|
5
7
|
var DEFAULT_AI_API_SETTINGS = {
|
|
@@ -11,14 +13,14 @@ var DEFAULT_AI_API_SETTINGS = {
|
|
|
11
13
|
audioStrategy: "auto"
|
|
12
14
|
};
|
|
13
15
|
var AI_API_SETTINGS_STORAGE_KEY = "ai-api-settings";
|
|
14
|
-
function loadAiApiSettings(storageKey = AI_API_SETTINGS_STORAGE_KEY) {
|
|
15
|
-
if (typeof window === "undefined") return
|
|
16
|
+
function loadAiApiSettings(storageKey = AI_API_SETTINGS_STORAGE_KEY, defaults = DEFAULT_AI_API_SETTINGS) {
|
|
17
|
+
if (typeof window === "undefined") return defaults;
|
|
16
18
|
try {
|
|
17
19
|
const raw = localStorage.getItem(storageKey);
|
|
18
|
-
if (!raw) return
|
|
19
|
-
return { ...
|
|
20
|
+
if (!raw) return defaults;
|
|
21
|
+
return { ...defaults, ...JSON.parse(raw) };
|
|
20
22
|
} catch {
|
|
21
|
-
return
|
|
23
|
+
return defaults;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
function saveAiApiSettings(settings, storageKey = AI_API_SETTINGS_STORAGE_KEY) {
|
|
@@ -38,8 +40,14 @@ function toClientSettings(settings) {
|
|
|
38
40
|
if (settings.audioStrategy) client.audioStrategy = settings.audioStrategy;
|
|
39
41
|
return Object.keys(client).length > 0 ? client : void 0;
|
|
40
42
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
+
function toServerClientSettings(settings) {
|
|
44
|
+
if (!settings.apiKey.trim()) {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
return toClientSettings(settings);
|
|
48
|
+
}
|
|
49
|
+
function pickClientSettingsFromStorage(storageKey = AI_API_SETTINGS_STORAGE_KEY, defaults = DEFAULT_AI_API_SETTINGS) {
|
|
50
|
+
return toServerClientSettings(loadAiApiSettings(storageKey, defaults));
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
// src/common/aiApi/client/aiApiClient.ts
|
|
@@ -54,7 +62,7 @@ var AiApiClientError = class extends Error {
|
|
|
54
62
|
async function runAiTask(taskId, input, options) {
|
|
55
63
|
const fetchFn = options?.fetchImpl ?? fetch;
|
|
56
64
|
const endpoint = options?.runEndpoint ?? "/api/ai/run";
|
|
57
|
-
const clientSettings = options
|
|
65
|
+
const clientSettings = options !== void 0 && "clientSettings" in options ? options.clientSettings : pickClientSettingsFromStorage();
|
|
58
66
|
const payload = {
|
|
59
67
|
taskId,
|
|
60
68
|
input,
|
|
@@ -98,11 +106,15 @@ var aiApiClient = {
|
|
|
98
106
|
async function fetchAiModels(clientSettings, options) {
|
|
99
107
|
const fetchFn = options?.fetchImpl ?? fetch;
|
|
100
108
|
const endpoint = options?.modelsEndpoint ?? "/api/ai/models";
|
|
109
|
+
const body = {};
|
|
110
|
+
if (clientSettings) {
|
|
111
|
+
body.clientSettings = clientSettings;
|
|
112
|
+
}
|
|
101
113
|
const response = await fetchFn(endpoint, {
|
|
102
114
|
method: "POST",
|
|
103
115
|
credentials: "include",
|
|
104
116
|
headers: { "Content-Type": "application/json" },
|
|
105
|
-
body: JSON.stringify(
|
|
117
|
+
body: JSON.stringify(body),
|
|
106
118
|
signal: options?.signal
|
|
107
119
|
});
|
|
108
120
|
return await response.json();
|
|
@@ -145,7 +157,329 @@ function useAiTask(taskId) {
|
|
|
145
157
|
}, []);
|
|
146
158
|
return { execute, loading, error, result, reset };
|
|
147
159
|
}
|
|
160
|
+
function useAiModels(settings, onSuggestedModel, options) {
|
|
161
|
+
const [visionModels, setVisionModels] = useState([]);
|
|
162
|
+
const [allModels, setAllModels] = useState([]);
|
|
163
|
+
const [loading, setLoading] = useState(false);
|
|
164
|
+
const [error, setError] = useState(null);
|
|
165
|
+
const abortRef = useRef(null);
|
|
166
|
+
const onSuggestedRef = useRef(onSuggestedModel);
|
|
167
|
+
const settingsRef = useRef(settings);
|
|
168
|
+
onSuggestedRef.current = onSuggestedModel;
|
|
169
|
+
settingsRef.current = settings;
|
|
170
|
+
const load = useCallback(async () => {
|
|
171
|
+
abortRef.current?.abort();
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
abortRef.current = controller;
|
|
174
|
+
setLoading(true);
|
|
175
|
+
setError(null);
|
|
176
|
+
const currentSettings = settingsRef.current;
|
|
177
|
+
try {
|
|
178
|
+
const clientSettings = toServerClientSettings(currentSettings);
|
|
179
|
+
const result = await fetchAiModels(clientSettings, {
|
|
180
|
+
signal: controller.signal,
|
|
181
|
+
modelsEndpoint: options?.modelsEndpoint
|
|
182
|
+
});
|
|
183
|
+
if (controller.signal.aborted) return;
|
|
184
|
+
if (!result.success) {
|
|
185
|
+
setVisionModels([]);
|
|
186
|
+
setAllModels([]);
|
|
187
|
+
setError(result.error?.message ?? "\u83B7\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
setVisionModels(result.visionModels);
|
|
191
|
+
setAllModels(result.models);
|
|
192
|
+
if (result.suggestedVisionModel) {
|
|
193
|
+
const current = currentSettings.visionModel.trim();
|
|
194
|
+
const shouldAutoSelect = !current || !result.visionModels.includes(current) && !result.models.includes(current);
|
|
195
|
+
if (shouldAutoSelect) {
|
|
196
|
+
onSuggestedRef.current?.(result.suggestedVisionModel);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
if (controller.signal.aborted) return;
|
|
201
|
+
setVisionModels([]);
|
|
202
|
+
setAllModels([]);
|
|
203
|
+
setError(err instanceof Error ? err.message : "\u83B7\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25");
|
|
204
|
+
} finally {
|
|
205
|
+
if (!controller.signal.aborted) {
|
|
206
|
+
setLoading(false);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}, [options?.modelsEndpoint, settings.apiKey, settings.baseUrl]);
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
const debounceMs = options?.debounceMs ?? 600;
|
|
212
|
+
const timer = window.setTimeout(() => {
|
|
213
|
+
void load();
|
|
214
|
+
}, debounceMs);
|
|
215
|
+
return () => {
|
|
216
|
+
window.clearTimeout(timer);
|
|
217
|
+
abortRef.current?.abort();
|
|
218
|
+
};
|
|
219
|
+
}, [load, options?.debounceMs]);
|
|
220
|
+
return {
|
|
221
|
+
visionModels,
|
|
222
|
+
allModels,
|
|
223
|
+
loading,
|
|
224
|
+
error,
|
|
225
|
+
refresh: load
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function useAiServerConfig(options) {
|
|
229
|
+
const [config, setConfig] = useState(null);
|
|
230
|
+
const [loading, setLoading] = useState(true);
|
|
231
|
+
const [error, setError] = useState(null);
|
|
232
|
+
const configEndpoint = options?.configEndpoint ?? "/api/ai/config";
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
let cancelled = false;
|
|
235
|
+
async function load() {
|
|
236
|
+
try {
|
|
237
|
+
const response = await fetch(configEndpoint, { credentials: "include" });
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
if (!cancelled) {
|
|
240
|
+
setConfig({ serverConfigured: false });
|
|
241
|
+
setError(response.status === 401 ? "\u8BF7\u5148\u767B\u5F55" : "\u65E0\u6CD5\u8BFB\u53D6\u670D\u52A1\u7AEF\u914D\u7F6E");
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const data = await response.json();
|
|
246
|
+
if (!cancelled) {
|
|
247
|
+
setConfig(data);
|
|
248
|
+
setError(null);
|
|
249
|
+
}
|
|
250
|
+
} catch (err) {
|
|
251
|
+
if (!cancelled) {
|
|
252
|
+
setConfig({ serverConfigured: false });
|
|
253
|
+
setError(err instanceof Error ? err.message : "\u65E0\u6CD5\u8BFB\u53D6\u670D\u52A1\u7AEF\u914D\u7F6E");
|
|
254
|
+
}
|
|
255
|
+
} finally {
|
|
256
|
+
if (!cancelled) setLoading(false);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
void load();
|
|
260
|
+
return () => {
|
|
261
|
+
cancelled = true;
|
|
262
|
+
};
|
|
263
|
+
}, [configEndpoint]);
|
|
264
|
+
return { config, loading, error };
|
|
265
|
+
}
|
|
266
|
+
var AiApiSettingsContext = createContext(null);
|
|
267
|
+
function AiApiSettingsProvider({
|
|
268
|
+
children,
|
|
269
|
+
defaultSettings,
|
|
270
|
+
storageKey
|
|
271
|
+
}) {
|
|
272
|
+
const mergedDefaults = useMemo(
|
|
273
|
+
() => ({ ...DEFAULT_AI_API_SETTINGS, ...defaultSettings }),
|
|
274
|
+
[defaultSettings]
|
|
275
|
+
);
|
|
276
|
+
const [settings, setSettings] = useState(mergedDefaults);
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
setSettings(loadAiApiSettings(storageKey, mergedDefaults));
|
|
279
|
+
}, [storageKey, mergedDefaults]);
|
|
280
|
+
const persist = useCallback(
|
|
281
|
+
(next) => {
|
|
282
|
+
setSettings(next);
|
|
283
|
+
saveAiApiSettings(next, storageKey);
|
|
284
|
+
},
|
|
285
|
+
[storageKey]
|
|
286
|
+
);
|
|
287
|
+
const updateSettings = useCallback(
|
|
288
|
+
(updates) => {
|
|
289
|
+
setSettings((prev) => {
|
|
290
|
+
const next = { ...prev, ...updates };
|
|
291
|
+
saveAiApiSettings(next, storageKey);
|
|
292
|
+
return next;
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
[storageKey]
|
|
296
|
+
);
|
|
297
|
+
const resetSettings = useCallback(() => {
|
|
298
|
+
persist(mergedDefaults);
|
|
299
|
+
}, [persist, mergedDefaults]);
|
|
300
|
+
const value = useMemo(
|
|
301
|
+
() => ({ settings, updateSettings, resetSettings }),
|
|
302
|
+
[settings, updateSettings, resetSettings]
|
|
303
|
+
);
|
|
304
|
+
return /* @__PURE__ */ React3.createElement(AiApiSettingsContext.Provider, { value }, children);
|
|
305
|
+
}
|
|
306
|
+
function useAiApiSettings() {
|
|
307
|
+
const ctx = useContext(AiApiSettingsContext);
|
|
308
|
+
if (!ctx) {
|
|
309
|
+
throw new Error("useAiApiSettings must be used within AiApiSettingsProvider");
|
|
310
|
+
}
|
|
311
|
+
return ctx;
|
|
312
|
+
}
|
|
313
|
+
function AiApiConnectivityTest({ runEndpoint }) {
|
|
314
|
+
const { settings } = useAiApiSettings();
|
|
315
|
+
const [status, setStatus] = useState("idle");
|
|
316
|
+
const [message, setMessage] = useState("");
|
|
317
|
+
const [meta, setMeta] = useState(null);
|
|
318
|
+
const handleTest = useCallback(async () => {
|
|
319
|
+
setStatus("loading");
|
|
320
|
+
setMessage("");
|
|
321
|
+
setMeta(null);
|
|
322
|
+
const started = Date.now();
|
|
323
|
+
const clientSettings = toServerClientSettings(settings);
|
|
324
|
+
try {
|
|
325
|
+
const result = await runAiTask(
|
|
326
|
+
CORE_CONNECTIVITY_TEST_TASK_ID,
|
|
327
|
+
{},
|
|
328
|
+
{ clientSettings, runEndpoint }
|
|
329
|
+
);
|
|
330
|
+
const latencyMs = result.meta?.latencyMs ?? Date.now() - started;
|
|
331
|
+
if (!result.success) {
|
|
332
|
+
setStatus("error");
|
|
333
|
+
setMessage(result.error?.message ?? "\u8FDE\u901A\u6027\u6D4B\u8BD5\u5931\u8D25");
|
|
334
|
+
setMeta({ latencyMs });
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
setStatus("success");
|
|
338
|
+
setMessage(result.data?.reply ? `\u54CD\u5E94\uFF1A${result.data.reply}` : "\u8FDE\u63A5\u6B63\u5E38");
|
|
339
|
+
setMeta({ model: result.meta?.model, latencyMs });
|
|
340
|
+
} catch (err) {
|
|
341
|
+
setStatus("error");
|
|
342
|
+
setMessage(err instanceof Error ? err.message : "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25");
|
|
343
|
+
setMeta({ latencyMs: Date.now() - started });
|
|
344
|
+
}
|
|
345
|
+
}, [settings, runEndpoint]);
|
|
346
|
+
return /* @__PURE__ */ React3.createElement("div", { className: "rounded-xl border border-slate-200 bg-slate-50/80 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("h3", { className: "text-sm font-medium text-slate-900" }, "\u8FDE\u901A\u6027\u6D4B\u8BD5"), /* @__PURE__ */ React3.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.createElement(
|
|
347
|
+
"button",
|
|
348
|
+
{
|
|
349
|
+
type: "button",
|
|
350
|
+
onClick: () => void handleTest(),
|
|
351
|
+
disabled: status === "loading",
|
|
352
|
+
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"
|
|
353
|
+
},
|
|
354
|
+
status === "loading" ? /* @__PURE__ */ React3.createElement(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ React3.createElement(Wifi, { className: "h-4 w-4" }),
|
|
355
|
+
status === "loading" ? "\u6D4B\u8BD5\u4E2D\u2026" : "\u6D4B\u8BD5\u8FDE\u63A5"
|
|
356
|
+
)), status !== "idle" && /* @__PURE__ */ React3.createElement(
|
|
357
|
+
"div",
|
|
358
|
+
{
|
|
359
|
+
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"}`
|
|
360
|
+
},
|
|
361
|
+
status === "loading" && /* @__PURE__ */ React3.createElement(Loader2, { className: "mt-0.5 h-4 w-4 shrink-0 animate-spin" }),
|
|
362
|
+
status === "success" && /* @__PURE__ */ React3.createElement(CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0" }),
|
|
363
|
+
status === "error" && /* @__PURE__ */ React3.createElement(XCircle, { className: "mt-0.5 h-4 w-4 shrink-0" }),
|
|
364
|
+
/* @__PURE__ */ React3.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React3.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.createElement("p", { className: "mt-1 tabular-nums text-xs opacity-80" }, meta.model && /* @__PURE__ */ React3.createElement("span", null, "\u6A21\u578B ", meta.model), meta.model && meta.latencyMs !== void 0 && /* @__PURE__ */ React3.createElement("span", null, " \xB7 "), meta.latencyMs !== void 0 && /* @__PURE__ */ React3.createElement("span", null, meta.latencyMs, " ms")))
|
|
365
|
+
));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/common/aiApi/client/components/AiApiSettingsPanel.tsx
|
|
369
|
+
function AiApiSettingsPanel({
|
|
370
|
+
serverConfigEndpoint,
|
|
371
|
+
modelsEndpoint,
|
|
372
|
+
runEndpoint,
|
|
373
|
+
serverMissingHint,
|
|
374
|
+
apiKeyPlaceholder = "sk-\u2026",
|
|
375
|
+
baseUrlPlaceholder = "https://api.openai.com/v1",
|
|
376
|
+
visionModelPlaceholder = "gpt-4o-mini"
|
|
377
|
+
}) {
|
|
378
|
+
const { settings, updateSettings } = useAiApiSettings();
|
|
379
|
+
const { config: serverConfig, loading: serverLoading } = useAiServerConfig({
|
|
380
|
+
configEndpoint: serverConfigEndpoint
|
|
381
|
+
});
|
|
382
|
+
const [showOverride, setShowOverride] = useState(false);
|
|
383
|
+
const hasBrowserKey = Boolean(settings.apiKey.trim());
|
|
384
|
+
const serverReady = Boolean(serverConfig?.serverConfigured);
|
|
385
|
+
const useServerOnly = serverReady && !hasBrowserKey && !showOverride;
|
|
386
|
+
const handleSuggestedModel = useCallback(
|
|
387
|
+
(model) => {
|
|
388
|
+
updateSettings({ visionModel: model });
|
|
389
|
+
},
|
|
390
|
+
[updateSettings]
|
|
391
|
+
);
|
|
392
|
+
const { visionModels, allModels, loading, error, refresh } = useAiModels(
|
|
393
|
+
settings,
|
|
394
|
+
handleSuggestedModel,
|
|
395
|
+
{ modelsEndpoint }
|
|
396
|
+
);
|
|
397
|
+
const selectableModels = visionModels;
|
|
398
|
+
const showDropdown = selectableModels.length > 0;
|
|
399
|
+
const showAllModelsFallback = visionModels.length === 0 && allModels.length > 0;
|
|
400
|
+
const defaultServerMissingHint = /* @__PURE__ */ React3.createElement(React3.Fragment, null, "\u8BF7\u5728\u5BBF\u4E3B\u914D\u7F6E\u6587\u4EF6\u4E2D\u586B\u5199 AI API Key\uFF08\u5982 YAML \u7684 ", /* @__PURE__ */ React3.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");
|
|
401
|
+
return /* @__PURE__ */ React3.createElement("div", { className: "space-y-6" }, serverLoading ? /* @__PURE__ */ React3.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.createElement(Loader2, { className: "h-4 w-4 animate-spin" }), "\u6B63\u5728\u68C0\u67E5\u670D\u52A1\u7AEF AI \u914D\u7F6E\u2026") : serverReady && !hasBrowserKey ? /* @__PURE__ */ React3.createElement("div", { className: "rounded-xl border border-emerald-200 bg-emerald-50/80 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React3.createElement(CheckCircle2, { className: "mt-0.5 h-5 w-5 shrink-0 text-emerald-600" }), /* @__PURE__ */ React3.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React3.createElement("h3", { className: "text-sm font-medium text-emerald-900" }, "\u5DF2\u7531\u670D\u52A1\u7AEF\u914D\u7F6E"), /* @__PURE__ */ React3.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.createElement("dl", { className: "mt-3 grid gap-1 text-xs text-emerald-900/80" }, serverConfig?.baseUrl && /* @__PURE__ */ React3.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React3.createElement("dt", { className: "shrink-0 font-medium" }, "Base URL"), /* @__PURE__ */ React3.createElement("dd", { className: "truncate font-mono" }, serverConfig.baseUrl)), serverConfig?.visionModel && /* @__PURE__ */ React3.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React3.createElement("dt", { className: "shrink-0 font-medium" }, "\u89C6\u89C9\u6A21\u578B"), /* @__PURE__ */ React3.createElement("dd", { className: "font-mono" }, serverConfig.visionModel))), /* @__PURE__ */ React3.createElement(
|
|
402
|
+
"button",
|
|
403
|
+
{
|
|
404
|
+
type: "button",
|
|
405
|
+
onClick: () => setShowOverride((v) => !v),
|
|
406
|
+
className: "mt-3 text-xs font-medium text-emerald-700 underline-offset-2 hover:underline"
|
|
407
|
+
},
|
|
408
|
+
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"
|
|
409
|
+
)))) : !serverReady && !hasBrowserKey ? /* @__PURE__ */ React3.createElement("div", { className: "rounded-xl border border-amber-200 bg-amber-50/80 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React3.createElement(Server, { className: "mt-0.5 h-5 w-5 shrink-0 text-amber-600" }), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("h3", { className: "text-sm font-medium text-amber-900" }, "\u670D\u52A1\u7AEF\u672A\u914D\u7F6E AI"), /* @__PURE__ */ React3.createElement("p", { className: "mt-1 text-xs text-amber-800/90" }, serverMissingHint ?? defaultServerMissingHint)))) : null, !useServerOnly && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("label", { htmlFor: "ai-api-key", className: "mb-2 block text-sm font-medium text-gray-700" }, "API Key"), /* @__PURE__ */ React3.createElement(
|
|
410
|
+
"input",
|
|
411
|
+
{
|
|
412
|
+
id: "ai-api-key",
|
|
413
|
+
type: "password",
|
|
414
|
+
autoComplete: "off",
|
|
415
|
+
value: settings.apiKey,
|
|
416
|
+
onChange: (e) => updateSettings({ apiKey: e.target.value }),
|
|
417
|
+
placeholder: apiKeyPlaceholder,
|
|
418
|
+
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"
|
|
419
|
+
}
|
|
420
|
+
), /* @__PURE__ */ React3.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.createElement("div", null, /* @__PURE__ */ React3.createElement("label", { htmlFor: "ai-base-url", className: "mb-2 block text-sm font-medium text-gray-700" }, "API Base URL"), /* @__PURE__ */ React3.createElement(
|
|
421
|
+
"input",
|
|
422
|
+
{
|
|
423
|
+
id: "ai-base-url",
|
|
424
|
+
type: "url",
|
|
425
|
+
value: settings.baseUrl,
|
|
426
|
+
onChange: (e) => updateSettings({ baseUrl: e.target.value }),
|
|
427
|
+
placeholder: baseUrlPlaceholder,
|
|
428
|
+
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"
|
|
429
|
+
}
|
|
430
|
+
), /* @__PURE__ */ React3.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.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("div", { className: "mb-2 flex items-center justify-between gap-2" }, /* @__PURE__ */ React3.createElement("label", { htmlFor: "ai-vision-model", className: "text-sm font-medium text-gray-700" }, "\u89C6\u89C9\u6A21\u578B"), /* @__PURE__ */ React3.createElement(
|
|
431
|
+
"button",
|
|
432
|
+
{
|
|
433
|
+
type: "button",
|
|
434
|
+
onClick: () => void refresh(),
|
|
435
|
+
disabled: loading,
|
|
436
|
+
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"
|
|
437
|
+
},
|
|
438
|
+
loading ? /* @__PURE__ */ React3.createElement(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ React3.createElement(RefreshCw, { className: "h-3.5 w-3.5" }),
|
|
439
|
+
"\u5237\u65B0\u6A21\u578B"
|
|
440
|
+
)), showDropdown ? /* @__PURE__ */ React3.createElement(
|
|
441
|
+
"select",
|
|
442
|
+
{
|
|
443
|
+
id: "ai-vision-model",
|
|
444
|
+
value: settings.visionModel,
|
|
445
|
+
onChange: (e) => updateSettings({ visionModel: e.target.value }),
|
|
446
|
+
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"
|
|
447
|
+
},
|
|
448
|
+
selectableModels.map((model) => /* @__PURE__ */ React3.createElement("option", { key: model, value: model }, model)),
|
|
449
|
+
settings.visionModel && !selectableModels.includes(settings.visionModel) && /* @__PURE__ */ React3.createElement("option", { value: settings.visionModel }, settings.visionModel, "\uFF08\u5F53\u524D\uFF09")
|
|
450
|
+
) : /* @__PURE__ */ React3.createElement(
|
|
451
|
+
"input",
|
|
452
|
+
{
|
|
453
|
+
id: "ai-vision-model",
|
|
454
|
+
type: "text",
|
|
455
|
+
value: settings.visionModel,
|
|
456
|
+
onChange: (e) => updateSettings({ visionModel: e.target.value }),
|
|
457
|
+
placeholder: visionModelPlaceholder,
|
|
458
|
+
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"
|
|
459
|
+
}
|
|
460
|
+
), loading && /* @__PURE__ */ React3.createElement("p", { className: "mt-1.5 flex items-center gap-1.5 text-xs text-gray-500" }, /* @__PURE__ */ React3.createElement(Loader2, { className: "h-3.5 w-3.5 animate-spin" }), "\u6B63\u5728\u8BFB\u53D6\u53EF\u7528\u6A21\u578B\u2026"), !loading && error && /* @__PURE__ */ React3.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.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.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.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.createElement("div", null, /* @__PURE__ */ React3.createElement("label", { htmlFor: "ai-text-model", className: "mb-2 block text-sm font-medium text-gray-700" }, "\u6587\u672C\u6A21\u578B"), /* @__PURE__ */ React3.createElement(
|
|
461
|
+
"input",
|
|
462
|
+
{
|
|
463
|
+
id: "ai-text-model",
|
|
464
|
+
type: "text",
|
|
465
|
+
value: settings.textModel ?? settings.visionModel,
|
|
466
|
+
onChange: (e) => updateSettings({ textModel: e.target.value }),
|
|
467
|
+
placeholder: visionModelPlaceholder,
|
|
468
|
+
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"
|
|
469
|
+
}
|
|
470
|
+
), /* @__PURE__ */ React3.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.createElement("div", null, /* @__PURE__ */ React3.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.createElement(
|
|
471
|
+
"input",
|
|
472
|
+
{
|
|
473
|
+
id: "ai-audio-model",
|
|
474
|
+
type: "text",
|
|
475
|
+
value: settings.audioModel ?? "whisper-1",
|
|
476
|
+
onChange: (e) => updateSettings({ audioModel: e.target.value }),
|
|
477
|
+
placeholder: "whisper-1",
|
|
478
|
+
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"
|
|
479
|
+
}
|
|
480
|
+
), /* @__PURE__ */ React3.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.createElement(AiApiConnectivityTest, { runEndpoint }));
|
|
481
|
+
}
|
|
148
482
|
|
|
149
|
-
export { AI_API_SETTINGS_STORAGE_KEY, AiApiClientError, DEFAULT_AI_API_SETTINGS, aiApiClient, createAiTaskRunner, fetchAiModels, loadAiApiSettings, pickClientSettingsFromStorage, runAiTask, runAiTaskOrThrow, saveAiApiSettings, toClientSettings, useAiTask };
|
|
483
|
+
export { AI_API_SETTINGS_STORAGE_KEY, AiApiClientError, AiApiConnectivityTest, AiApiSettingsPanel, AiApiSettingsProvider, DEFAULT_AI_API_SETTINGS, aiApiClient, createAiTaskRunner, fetchAiModels, loadAiApiSettings, pickClientSettingsFromStorage, runAiTask, runAiTaskOrThrow, saveAiApiSettings, toClientSettings, toServerClientSettings, useAiApiSettings, useAiModels, useAiServerConfig, useAiTask };
|
|
150
484
|
//# sourceMappingURL=index.mjs.map
|
|
151
485
|
//# sourceMappingURL=index.mjs.map
|