'use client' import { useState, useEffect, useCallback } from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' import { useToast } from '@/components/ui/Toast' import { Bot, Eye, Mic, Settings, CheckCircle, XCircle, Loader2, Info, Shield, AlertTriangle, RefreshCw, Clock } from 'lucide-react' import { api } from '@/lib/api' import { USE_MOCK } from '@/contexts/AuthContext' import type { AIProvider, AIConfigResponse, ConnectionTestResponse, ModelInfo } from '@/types/ai-config' // AI 提供商选项 const providerOptions: { value: AIProvider | string; label: string }[] = [ { value: 'oneapi', label: 'OneAPI 中转服务' }, { value: 'anthropic', label: 'Anthropic Claude' }, { value: 'openai', label: 'OpenAI' }, { value: 'deepseek', label: 'DeepSeek' }, { value: 'qwen', label: '通义千问' }, { value: 'doubao', label: '豆包' }, { value: 'zhipu', label: '智谱' }, { value: 'moonshot', label: 'Moonshot' }, ] // Mock 可用模型列表 const mockModels: Record = { text: [ { id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5' }, { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4' }, { id: 'gpt-4o', name: 'GPT-4o' }, { id: 'deepseek-chat', name: 'DeepSeek Chat' }, ], vision: [ { id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5' }, { id: 'gpt-4o', name: 'GPT-4o' }, ], audio: [ { id: 'whisper-large-v3', name: 'Whisper Large V3' }, { id: 'whisper-medium', name: 'Whisper Medium' }, ], } type TestStatus = 'idle' | 'testing' | 'success' | 'failed' function ConfigSkeleton() { return (
) } export default function AIConfigPage() { const toast = useToast() const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [provider, setProvider] = useState('oneapi') const [baseUrl, setBaseUrl] = useState('https://oneapi.intelligrow.cn') const [apiKey, setApiKey] = useState('') const [showApiKey, setShowApiKey] = useState(false) const [isConfigured, setIsConfigured] = useState(false) const [llmModel, setLlmModel] = useState('claude-opus-4-5-20251101') const [visionModel, setVisionModel] = useState('claude-opus-4-5-20251101') const [asrModel, setAsrModel] = useState('whisper-large-v3') const [temperature, setTemperature] = useState(0.7) const [maxTokens, setMaxTokens] = useState(2000) const [availableModels, setAvailableModels] = useState>(mockModels) const [testResults, setTestResults] = useState>({ text: { status: 'idle' }, vision: { status: 'idle' }, audio: { status: 'idle' }, }) const loadConfig = useCallback(async () => { if (USE_MOCK) { setLoading(false) return } try { const config = await api.getAIConfig() setProvider(config.provider) setBaseUrl(config.base_url) setApiKey('') // API key is masked, don't fill it setIsConfigured(config.is_configured) setLlmModel(config.models.text) setVisionModel(config.models.vision) setAsrModel(config.models.audio) setTemperature(config.parameters.temperature) setMaxTokens(config.parameters.max_tokens) if (config.available_models && Object.keys(config.available_models).length > 0) { setAvailableModels(config.available_models) } } catch (err: any) { // 后端 404 返回 "AI 服务未配置" → Axios 拦截器转为 Error(message) // 这是正常的"尚未配置"状态,不弹错误 const msg = err?.message || '' if (msg.includes('未配置')) { setIsConfigured(false) } else { console.error('Failed to load AI config:', err) toast.error('加载 AI 配置失败') } } finally { setLoading(false) } }, [toast]) useEffect(() => { loadConfig() }, [loadConfig]) const handleTestConnection = async () => { setTestResults({ text: { status: 'testing' }, vision: { status: 'testing' }, audio: { status: 'testing' }, }) if (USE_MOCK) { await new Promise(resolve => setTimeout(resolve, 1500)) setTestResults(prev => ({ ...prev, text: { status: 'success', latency: 320 } })) await new Promise(resolve => setTimeout(resolve, 1000)) setTestResults(prev => ({ ...prev, vision: { status: 'success', latency: 450 } })) await new Promise(resolve => setTimeout(resolve, 800)) setTestResults(prev => ({ ...prev, audio: { status: 'success', latency: 280 } })) return } try { const result: ConnectionTestResponse = await api.testAIConnection({ provider: provider as AIProvider, base_url: baseUrl, api_key: apiKey || '***', // use existing key if not changed models: { text: llmModel, vision: visionModel, audio: asrModel }, }) const newResults: Record = {} for (const [key, r] of Object.entries(result.results)) { newResults[key] = { status: r.success ? 'success' : 'failed', latency: r.latency_ms ?? undefined, error: r.error ?? undefined, } } setTestResults(prev => ({ ...prev, ...newResults })) if (result.success) { toast.success(result.message) } else { toast.error(result.message) } } catch (err) { toast.error('连接测试失败') setTestResults({ text: { status: 'failed', error: '请求失败' }, vision: { status: 'failed', error: '请求失败' }, audio: { status: 'failed', error: '请求失败' }, }) } } const handleSave = async () => { setSaving(true) try { if (USE_MOCK) { await new Promise(resolve => setTimeout(resolve, 500)) } else { await api.updateAIConfig({ provider: provider as AIProvider, base_url: baseUrl, api_key: apiKey || '***', models: { text: llmModel, vision: visionModel, audio: asrModel }, parameters: { temperature, max_tokens: maxTokens }, }) } toast.success('配置已保存') } catch (err) { toast.error('保存失败') } finally { setSaving(false) } } const getTestStatusIcon = (key: string) => { const result = testResults[key] if (!result) return null switch (result.status) { case 'testing': return case 'success': return ( {result.latency && {result.latency}ms} ) case 'failed': return ( {result.error && {result.error}} ) default: return null } } if (loading) { return (

AI 服务配置

) } return (

AI 服务配置

配置 AI 服务提供商和模型参数

{isConfigured && (
已配置
)}
{/* 配置继承提示 */}

配置继承说明

品牌方配置后,所属代理商和达人将自动使用此配置。代理商和达人端不可见此配置项。

{/* AI 提供商 */} AI 提供商

推荐使用 OneAPI 等中转服务商,方便切换不同 AI 模型

{/* 模型配置 */} 模型配置 {/* 文字处理模型 */}
文字处理模型 (LLM) {getTestStatusIcon('text')}

用于 Brief 解析、语义分析、报告生成

{/* 视频分析模型 */}
视频分析模型 (Vision) {getTestStatusIcon('vision')}

用于画面语义分析、场景/风险识别

{/* 音频解析模型 */}
音频解析模型 (ASR) {getTestStatusIcon('audio')}

用于语音转文字、口播内容提取

{/* 连接配置 */} 连接配置
setBaseUrl(e.target.value)} placeholder="https://api.openai.com/v1" />
setApiKey(e.target.value)} placeholder={isConfigured ? '留空使用已保存的密钥' : 'sk-...'} />
{/* 生成参数 */} 生成参数
{temperature}
setTemperature(parseFloat(e.target.value))} className="w-full h-2 bg-bg-elevated rounded-lg appearance-none cursor-pointer accent-accent-indigo" />
精确 (0) 创意 (1)
setMaxTokens(parseInt(e.target.value))} min="100" max="8000" />
{/* 安全说明 */}

安全说明

  • • API Key 使用 AES-256-GCM 加密存储
  • • 所有 API 请求强制使用 HTTPS
  • • 仅品牌方管理员可查看/修改此配置
  • • 配置变更将被记录
{/* 操作按钮 */}
) }