Your Name e4959d584f feat: 完善代理商端业务逻辑与前后端框架
主要更新:
- 更新代理商端文档,明确项目由品牌方分配流程
- 新增Brief配置详情页(已配置)设计稿
- 完善工作台紧急待办中品牌新任务功能
- 整理Pencil设计文件中代理商端页面顺序
- 新增后端FastAPI框架及核心API
- 新增前端Next.js页面和组件库
- 添加.gitignore排除构建和缓存文件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 19:27:31 +08:00

336 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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">
OneAPIAnthropic ClaudeOpenAIDeepSeek
</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>
)
}