主要更新: - 更新代理商端文档,明确项目由品牌方分配流程 - 新增Brief配置详情页(已配置)设计稿 - 完善工作台紧急待办中品牌新任务功能 - 整理Pencil设计文件中代理商端页面顺序 - 新增后端FastAPI框架及核心API - 新增前端Next.js页面和组件库 - 添加.gitignore排除构建和缓存文件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
336 lines
13 KiB
TypeScript
336 lines
13 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||
import { Button } from '@/components/ui/Button'
|
||
import { Input } from '@/components/ui/Input'
|
||
import { Select } from '@/components/ui/Select'
|
||
import { SuccessTag, ErrorTag, PendingTag } from '@/components/ui/Tag'
|
||
import {
|
||
Bot,
|
||
Eye,
|
||
Mic,
|
||
Settings,
|
||
CheckCircle,
|
||
XCircle,
|
||
Loader2,
|
||
Info,
|
||
Shield,
|
||
AlertTriangle
|
||
} from 'lucide-react'
|
||
|
||
// AI 提供商选项
|
||
const providerOptions = [
|
||
{ value: 'oneapi', label: 'OneAPI 中转服务' },
|
||
{ value: 'anthropic', label: 'Anthropic Claude' },
|
||
{ value: 'openai', label: 'OpenAI' },
|
||
{ value: 'deepseek', label: 'DeepSeek' },
|
||
{ value: 'custom', label: '自定义' },
|
||
]
|
||
|
||
// 模拟可用模型列表
|
||
const availableModels = {
|
||
llm: [
|
||
{ value: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5', tags: ['推荐', '高性能'] },
|
||
{ value: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4', tags: ['性价比'] },
|
||
{ value: 'gpt-4o', label: 'GPT-4o', tags: ['文字', '视觉'] },
|
||
{ value: 'deepseek-chat', label: 'DeepSeek Chat', tags: ['高性价比'] },
|
||
],
|
||
vision: [
|
||
{ value: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5', tags: ['推荐'] },
|
||
{ value: 'gpt-4o', label: 'GPT-4o', tags: ['视觉'] },
|
||
{ value: 'doubao-seed-1.6-thinking-vision', label: '豆包 Vision', tags: ['中文优化'] },
|
||
],
|
||
asr: [
|
||
{ value: 'whisper-large-v3', label: 'Whisper Large V3', tags: ['推荐'] },
|
||
{ value: 'whisper-medium', label: 'Whisper Medium', tags: ['快速'] },
|
||
{ value: 'paraformer-zh', label: '达摩院 Paraformer', tags: ['中文优化'] },
|
||
],
|
||
}
|
||
|
||
type TestResult = {
|
||
llm: 'idle' | 'testing' | 'success' | 'failed'
|
||
vision: 'idle' | 'testing' | 'success' | 'failed'
|
||
asr: 'idle' | 'testing' | 'success' | 'failed'
|
||
}
|
||
|
||
export default function AIConfigPage() {
|
||
const [provider, setProvider] = useState('oneapi')
|
||
const [baseUrl, setBaseUrl] = useState('https://oneapi.intelligrow.cn')
|
||
const [apiKey, setApiKey] = useState('')
|
||
const [showApiKey, setShowApiKey] = 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 [testResults, setTestResults] = useState<TestResult>({
|
||
llm: 'idle',
|
||
vision: 'idle',
|
||
asr: 'idle',
|
||
})
|
||
|
||
const handleTestConnection = async () => {
|
||
// 模拟测试连接
|
||
setTestResults({ llm: 'testing', vision: 'testing', asr: 'testing' })
|
||
|
||
// 模拟延迟
|
||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||
setTestResults(prev => ({ ...prev, llm: 'success' }))
|
||
|
||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||
setTestResults(prev => ({ ...prev, vision: 'success' }))
|
||
|
||
await new Promise(resolve => setTimeout(resolve, 800))
|
||
setTestResults(prev => ({ ...prev, asr: 'success' }))
|
||
}
|
||
|
||
const handleSave = () => {
|
||
alert('配置已保存')
|
||
}
|
||
|
||
const getTestStatusIcon = (status: string) => {
|
||
switch (status) {
|
||
case 'testing':
|
||
return <Loader2 size={16} className="text-blue-500 animate-spin" />
|
||
case 'success':
|
||
return <CheckCircle size={16} className="text-green-500" />
|
||
case 'failed':
|
||
return <XCircle size={16} className="text-red-500" />
|
||
default:
|
||
return null
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-6 max-w-4xl">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-text-primary">AI 服务配置</h1>
|
||
<p className="text-sm text-text-secondary mt-1">配置 AI 服务提供商和模型参数</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 配置继承提示 */}
|
||
<div className="p-4 bg-accent-indigo/10 rounded-lg border border-accent-indigo/30">
|
||
<div className="flex items-start gap-3">
|
||
<Info size={20} className="text-accent-indigo flex-shrink-0 mt-0.5" />
|
||
<div>
|
||
<p className="text-sm text-accent-indigo font-medium">配置继承说明</p>
|
||
<p className="text-sm text-accent-indigo/80 mt-1">
|
||
品牌方配置后,所属代理商和达人将自动使用此配置。代理商和达人端不可见此配置项。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* AI 提供商 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Bot size={18} className="text-blue-500" />
|
||
AI 提供商
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-text-primary mb-1">提供商选择</label>
|
||
<select
|
||
className="w-full px-3 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
|
||
value={provider}
|
||
onChange={(e) => setProvider(e.target.value)}
|
||
>
|
||
{providerOptions.map(opt => (
|
||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||
))}
|
||
</select>
|
||
<p className="text-xs text-text-tertiary mt-1">
|
||
支持 OneAPI、Anthropic Claude、OpenAI、DeepSeek 等提供商
|
||
</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 模型配置 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Settings size={18} className="text-purple-500" />
|
||
模型配置
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-6">
|
||
{/* 文字处理模型 */}
|
||
<div className="p-4 bg-bg-elevated rounded-lg">
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<Bot size={16} className="text-accent-indigo" />
|
||
<span className="font-medium text-text-primary">文字处理模型 (LLM)</span>
|
||
{getTestStatusIcon(testResults.llm)}
|
||
</div>
|
||
<select
|
||
className="w-full px-3 py-2 border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-indigo bg-bg-card text-text-primary"
|
||
value={llmModel}
|
||
onChange={(e) => setLlmModel(e.target.value)}
|
||
>
|
||
{availableModels.llm.map(model => (
|
||
<option key={model.value} value={model.value}>
|
||
{model.label} [{model.tags.join(', ')}]
|
||
</option>
|
||
))}
|
||
</select>
|
||
<p className="text-xs text-text-tertiary mt-2">用于 Brief 解析、语义分析、报告生成</p>
|
||
</div>
|
||
|
||
{/* 视频分析模型 */}
|
||
<div className="p-4 bg-bg-elevated rounded-lg">
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<Eye size={16} className="text-accent-green" />
|
||
<span className="font-medium text-text-primary">视频分析模型 (Vision)</span>
|
||
{getTestStatusIcon(testResults.vision)}
|
||
</div>
|
||
<select
|
||
className="w-full px-3 py-2 border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-indigo bg-bg-card text-text-primary"
|
||
value={visionModel}
|
||
onChange={(e) => setVisionModel(e.target.value)}
|
||
>
|
||
{availableModels.vision.map(model => (
|
||
<option key={model.value} value={model.value}>
|
||
{model.label} [{model.tags.join(', ')}]
|
||
</option>
|
||
))}
|
||
</select>
|
||
<p className="text-xs text-text-tertiary mt-2">用于画面语义分析、场景/风险识别(Logo 检测由系统内置 CV 完成)</p>
|
||
</div>
|
||
|
||
{/* 音频解析模型 */}
|
||
<div className="p-4 bg-bg-elevated rounded-lg">
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<Mic size={16} className="text-orange-400" />
|
||
<span className="font-medium text-text-primary">音频解析模型 (ASR)</span>
|
||
{getTestStatusIcon(testResults.asr)}
|
||
</div>
|
||
<select
|
||
className="w-full px-3 py-2 border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-indigo bg-bg-card text-text-primary"
|
||
value={asrModel}
|
||
onChange={(e) => setAsrModel(e.target.value)}
|
||
>
|
||
{availableModels.asr.map(model => (
|
||
<option key={model.value} value={model.value}>
|
||
{model.label} [{model.tags.join(', ')}]
|
||
</option>
|
||
))}
|
||
</select>
|
||
<p className="text-xs text-text-tertiary mt-2">用于语音转文字、口播内容提取</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 连接配置 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>连接配置</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-text-primary mb-1">Base URL</label>
|
||
<input
|
||
type="text"
|
||
className="w-full px-3 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
|
||
value={baseUrl}
|
||
onChange={(e) => setBaseUrl(e.target.value)}
|
||
placeholder="https://api.openai.com/v1"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-text-primary mb-1">API Key</label>
|
||
<div className="flex gap-2">
|
||
<input
|
||
type={showApiKey ? 'text' : 'password'}
|
||
className="flex-1 px-3 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
|
||
value={apiKey}
|
||
onChange={(e) => setApiKey(e.target.value)}
|
||
placeholder="sk-..."
|
||
/>
|
||
<Button
|
||
variant="secondary"
|
||
size="sm"
|
||
onClick={() => setShowApiKey(!showApiKey)}
|
||
>
|
||
{showApiKey ? '隐藏' : '显示'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 生成参数 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>生成参数</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-6">
|
||
<div>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<label className="text-sm font-medium text-text-primary">Temperature</label>
|
||
<span className="text-sm text-text-secondary">{temperature}</span>
|
||
</div>
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="1"
|
||
step="0.1"
|
||
value={temperature}
|
||
onChange={(e) => setTemperature(parseFloat(e.target.value))}
|
||
className="w-full h-2 bg-bg-elevated rounded-lg appearance-none cursor-pointer accent-accent-indigo"
|
||
/>
|
||
<div className="flex justify-between text-xs text-text-tertiary mt-1">
|
||
<span>精确 (0)</span>
|
||
<span>创意 (1)</span>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-text-primary mb-2">Max Tokens</label>
|
||
<input
|
||
type="number"
|
||
className="w-32 px-3 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
|
||
value={maxTokens}
|
||
onChange={(e) => setMaxTokens(parseInt(e.target.value))}
|
||
min="100"
|
||
max="8000"
|
||
/>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 安全说明 */}
|
||
<div className="p-4 bg-bg-elevated rounded-lg border border-border-subtle">
|
||
<div className="flex items-start gap-3">
|
||
<Shield size={20} className="text-text-tertiary flex-shrink-0 mt-0.5" />
|
||
<div className="text-sm text-text-secondary">
|
||
<p className="font-medium text-text-primary mb-1">安全说明</p>
|
||
<ul className="space-y-1 text-xs">
|
||
<li>• API Key 使用 AES-256-GCM 加密存储</li>
|
||
<li>• 所有 API 请求强制使用 HTTPS</li>
|
||
<li>• 仅品牌方管理员可查看/修改此配置</li>
|
||
<li>• 配置变更将记录到审计日志</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 操作按钮 */}
|
||
<div className="flex items-center justify-between pt-4 border-t border-border-subtle">
|
||
<Button variant="secondary" onClick={handleTestConnection}>
|
||
测试连接
|
||
</Button>
|
||
<Button onClick={handleSave}>
|
||
保存配置
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|