import { ref } from 'vue' import { testModelConnectivity } from '../../services/settings.js' import { useToast } from '../../composables/useToast.js' import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue' const MODEL_SECRET_MASK = '********' const MODEL_TEST_CONFIGS = { main: { label: '主模型', providerKey: 'mainProvider', modelKey: 'mainModel', endpointKey: 'mainEndpoint', apiKeyKey: 'mainApiKey', capability: 'chat' }, backup: { label: '备份模型', providerKey: 'backupProvider', modelKey: 'backupModel', endpointKey: 'backupEndpoint', apiKeyKey: 'backupApiKey', capability: 'chat' }, embedding: { label: 'Embedding 模型', providerKey: 'embeddingProvider', modelKey: 'embeddingModel', endpointKey: 'embeddingEndpoint', apiKeyKey: 'embeddingApiKey', capability: 'embedding' }, reranker: { label: 'Reranker 模型', providerKey: 'rerankerProvider', modelKey: 'rerankerModel', endpointKey: 'rerankerEndpoint', apiKeyKey: 'rerankerApiKey', capability: 'reranker' } } const CUSTOM_OPENAI_PROVIDER = 'Custom OpenAI Compatible' const PROVIDER_ENDPOINTS = { MiniMax: 'https://api.minimaxi.com/v1', GLM: 'https://open.bigmodel.cn/api/paas/v4/', Kimi: 'https://api.moonshot.ai/v1', Ali: 'https://dashscope.aliyuncs.com/compatible-mode/v1', Codex: 'https://api.openai.com/v1', Claude: 'https://api.anthropic.com/v1/', Gemini: 'https://generativelanguage.googleapis.com/v1beta/openai/', [CUSTOM_OPENAI_PROVIDER]: '' } const RERANKER_PROVIDER_ENDPOINTS = { Ali: 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank', [CUSTOM_OPENAI_PROVIDER]: '' } const LEGACY_PROVIDER_MAP = { 'OpenAI Compatible': 'Codex', 'Azure OpenAI': CUSTOM_OPENAI_PROVIDER, Ollama: CUSTOM_OPENAI_PROVIDER, '自定义网关': CUSTOM_OPENAI_PROVIDER } function normalizeValue(value) { return String(value ?? '').trim() } function normalizeProviderValue(value, fallback = 'Codex') { const normalized = normalizeValue(value) const providerOptions = Object.keys(PROVIDER_ENDPOINTS) if (providerOptions.includes(normalized)) { return normalized } if (LEGACY_PROVIDER_MAP[normalized]) { return LEGACY_PROVIDER_MAP[normalized] } return fallback } function getProviderEndpoint(provider) { return PROVIDER_ENDPOINTS[provider] ?? '' } function getRerankerEndpoint(provider) { return RERANKER_PROVIDER_ENDPOINTS[provider] ?? getProviderEndpoint(provider) } function isModelConfigReady(provider, model, endpoint) { return Boolean(normalizeValue(provider) && normalizeValue(model) && normalizeValue(endpoint)) } function isModelSecretMask(value) { return value === MODEL_SECRET_MASK } export default { name: 'LlmSettingsPanel', components: { EnterpriseSelect }, props: { llmForm: { type: Object, required: true }, providerOptions: { type: Array, required: true } }, setup(props) { const { toast } = useToast() const modelTestState = ref({ main: { status: 'idle', message: '' }, backup: { status: 'idle', message: '' }, embedding: { status: 'idle', message: '' }, reranker: { status: 'idle', message: '' } }) function applyProviderPreset(testKey) { const config = MODEL_TEST_CONFIGS[testKey] const provider = normalizeProviderValue(props.llmForm[config.providerKey], CUSTOM_OPENAI_PROVIDER) props.llmForm[config.providerKey] = provider props.llmForm[config.endpointKey] = testKey === 'reranker' ? getRerankerEndpoint(provider) : getProviderEndpoint(provider) } function getModelTestState(testKey) { return modelTestState.value[testKey] || { status: 'idle', message: '' } } function isModelTesting(testKey) { return getModelTestState(testKey).status === 'testing' } function clearModelSecretMask(testKey) { const config = MODEL_TEST_CONFIGS[testKey] if (isModelSecretMask(props.llmForm[config.apiKeyKey])) { props.llmForm[config.apiKeyKey] = '' } } async function testModelConnection(testKey) { const config = MODEL_TEST_CONFIGS[testKey] const provider = props.llmForm[config.providerKey] const model = props.llmForm[config.modelKey] const endpoint = props.llmForm[config.endpointKey] const apiKey = props.llmForm[config.apiKeyKey] if (!isModelConfigReady(provider, model, endpoint)) { const message = `请先完整填写${config.label}的供应商、模型名称和接口地址。` modelTestState.value[testKey] = { status: 'error', message } toast(message) return } modelTestState.value[testKey] = { status: 'testing', message: '正在测试模型连通性...' } const payload = { provider, model, endpoint, api_key: isModelSecretMask(apiKey) ? '' : apiKey, capability: config.capability, slot: testKey } try { const result = await testModelConnectivity(payload) modelTestState.value[testKey] = { status: result.ok ? 'success' : 'error', message: result.detail || (result.ok ? '模型连接成功。' : '模型连接失败。') } toast(modelTestState.value[testKey].message) } catch (error) { const message = error.message || '模型测试请求失败,请确认 FastAPI 已启动。' modelTestState.value[testKey] = { status: 'error', message } toast(message) } } return { applyProviderPreset, getModelTestState, isModelTesting, clearModelSecretMask, testModelConnection } } }