import React, { useEffect, useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { getPricingMappingLookup, modelApi, ParsedPricingResponse } from '../services/modelApi'; import { ArrowLeft, Loader2, Image, Video, Mic, FileText, CheckCircle2, XCircle, Copy } from '../icons/commonIcons'; import { copyToClipboard } from '../utils/clipboard'; const pricingKeyLabels: Record = { input: '输入', output: '输出', input_thinking: '输入(思考)', output_thinking: '输出(思考)', input_cache_hit: '输入(缓存命中)', input_cache_hit_thinking: '输入(缓存命中·思考)', input_batch: '输入(批量)', input_batch_thinking: '输入(批量·思考)', output_batch: '输出(批量)', output_thinking_batch: '输出(批量·思考)', cache_write: '缓存写入', cache_read: '缓存读取', explicit_cache_create: '显式缓存写入', explicit_cache_hit: '显式缓存命中', explicit_cache_create_thinking: '显式缓存写入(思考)', explicit_cache_hit_thinking: '显式缓存命中(思考)', image_input: '图片输入', text_input: '文本输入' }; const limitKeyLabels: Record = { max_input_length: '最大输入长度', max_output_length: '最大输出长度', rpm: '每分钟请求数 (RPM)', tpm: 'TPM', context_length: '上下文长度', note: '备注' }; const labelTranslations: Record = { image_generation: '图像生成', video_generation: '视频生成', audio_generation: '音频生成', image_input: '图片输入', text_input: '文本输入', model_experience: '模型体验', function_calling: 'function calling', structured_output: '结构化输出', internet_search: '网页搜索', web_search: '网页搜索', prefix_continuation: '前缀续写', cache: 'cache缓存', batch_inference: '批量推理', model_tuning: '模型微调', model_optimization: '模型优化', text: '文本', image: '图片', video: '视频', audio: '音频', input: '输入', output: '输出', tuning: '微调' }; const formatValue = (value: any, unit?: string): string => { if (value === undefined || value === null || value === '') return '—'; return `${value}${unit ? ` ${unit}` : ''}`; }; // 渲染价格单元格(支持原价划线 + 折扣价 + 折扣标签) const renderPriceCell = (value: any, originalValue: any, unit: string, discountRate?: number) => { const hasDiscount = originalValue !== undefined && originalValue !== null && Number(originalValue) > 0 && Math.abs(Number(originalValue) - Number(value)) > 0.000001; // 优先用传入的 discountRate,否则从原价/折扣价推算 const rate = discountRate ?? (hasDiscount && Number(originalValue) > 0 ? Number(value) / Number(originalValue) : 1); const tenths = Math.round(rate * 10); const discountLabel = hasDiscount && tenths < 10 ? `${tenths}折` : undefined; return ( {hasDiscount && ( {formatValue(originalValue, unit)} {discountLabel && ( {discountLabel} )} )} {formatValue(value, unit)} ); }; const getRangeLabel = (item: any): string => item?.input_range || item?.range || item?.inputRange || item?.tier_range || '—'; const translateLabel = (keyOrLabel: string): string => { if (!keyOrLabel) return '—'; if (/[\u4e00-\u9fa5]/.test(keyOrLabel)) return keyOrLabel; if (labelTranslations[keyOrLabel]) return labelTranslations[keyOrLabel]; if (keyOrLabel.includes('_')) return keyOrLabel.replace(/_/g, ' '); return keyOrLabel; }; const getDisplayLabel = (key: string): string => pricingKeyLabels[key] || translateLabel(key) || key; // API示例代码生成(根据模型分类返回对应示例) const generateApiExamples = (modelCode: string, categories: number[] = []) => { const baseUrl = import.meta.env.VITE_OPENAPI_BASE_URL || window.location.origin; const api = `${baseUrl}/api/v1`; // 判断主分类(优先级:图像编辑 > 图像生成 > 视频 > TTS > STT > Embedding > Rerank > LLM) const has = (c: number) => categories.includes(c); const code = modelCode.toLowerCase(); const isRealtime = code.includes('realtime'); const isClone = code.includes('clone'); // realtime 模型:WebSocket 协议,平台不支持代理,不可通过本平台 API 调用 if (isRealtime) { return { _isUnsupported: true, reason: '该模型使用 WebSocket 实时流协议,当前平台仅支持 HTTP 接口代理,暂不支持通过平台 API Key 调用此模型。' } as any; } // cosyvoice-clone 系列:两步流程(先创建音色,再合成) if (isClone && has(2)) { return { curl: `# ${modelCode} 声音克隆模型,需要两步调用\n\n# 第一步:上传参考音频,创建克隆音色(3~10秒清晰人声)\ncurl ${baseUrl}/api/audio/voice/create \\\n -H "Authorization: Bearer YOUR_API_KEY" \\\n -F "file=@/path/to/reference.mp3" \\\n -F "target_model=${modelCode}" \\\n -F "prefix=my_voice" \\\n -F "voice_name=我的音色"\n# 返回 voice_id,等待 status 变为 OK\n\n# 第二步:用 voice_id 合成语音\ncurl ${baseUrl}/api/v1/audio/speech \\\n -H "Authorization: Bearer YOUR_API_KEY" \\\n -H "Content-Type: application/json" \\\n -d '{"model": "${modelCode}", "input": "你好,世界", "voice": "YOUR_VOICE_ID"}' \\\n --output cloned.mp3`, python: `import requests\nimport time\n\nheaders = {"Authorization": "Bearer YOUR_API_KEY"}\nbase = "${baseUrl}"\n\n# 第一步:上传参考音频,创建克隆音色\nwith open("/path/to/reference.mp3", "rb") as f:\n resp = requests.post(\n f"{base}/api/audio/voice/create",\n headers=headers,\n files={"file": f},\n data={"target_model": "${modelCode}", "prefix": "my_voice", "voice_name": "我的音色"}\n )\nvoice_id = resp.json()["data"]["voice_id"]\nprint(f"voice_id: {voice_id}") # 等待 status 变为 OK\n\n# 第二步:用 voice_id 合成语音\nresp = requests.post(\n f"{base}/api/v1/audio/speech",\n headers={**headers, "Content-Type": "application/json"},\n json={"model": "${modelCode}", "input": "你好,世界", "voice": voice_id}\n)\nwith open("cloned.mp3", "wb") as f:\n f.write(resp.content)`, nodejs: `import fs from 'fs';\n\nconst base = '${baseUrl}';\nconst headers = { 'Authorization': 'Bearer YOUR_API_KEY' };\n\n// 第一步:上传参考音频,创建克隆音色\nconst form = new FormData();\nform.append('file', fs.createReadStream('/path/to/reference.mp3'));\nform.append('target_model', '${modelCode}');\nform.append('prefix', 'my_voice');\nform.append('voice_name', '我的音色');\nconst step1 = await fetch(\`\${base}/api/audio/voice/create\`, { method: 'POST', headers, body: form });\nconst { voice_id } = (await step1.json()).data;\nconsole.log('voice_id:', voice_id); // 等待 status 变为 OK\n\n// 第二步:用 voice_id 合成语音\nconst step2 = await fetch(\`\${base}/api/v1/audio/speech\`, {\n method: 'POST',\n headers: { ...headers, 'Content-Type': 'application/json' },\n body: JSON.stringify({ model: '${modelCode}', input: '你好,世界', voice: voice_id })\n});\nfs.writeFileSync('cloned.mp3', Buffer.from(await step2.arrayBuffer()));`, java: `// 第一步:POST ${baseUrl}/api/audio/voice/create\n// multipart: file=<音频>, target_model=${modelCode}, prefix=my_voice, voice_name=我的音色\n// 返回 voice_id,等待 status=OK\n\n// 第二步:POST ${baseUrl}/api/v1/audio/speech\n// JSON: { "model": "${modelCode}", "input": "你好,世界", "voice": "" }` }; } // OCR 模型:必须传图片 const OCR_MODELS = ['qwen-vl-ocr']; if (OCR_MODELS.includes(modelCode)) { return { curl: `curl ${api}/chat/completions \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{ "model": "${modelCode}", "messages": [ { "role": "user", "content": [ {"type": "image_url", "image_url": {"url": "https://example.com/your-image.jpg"}}, {"type": "text", "text": "请识别图片中的文字"} ] } ] }'`, python: `from openai import OpenAI\n\nclient = OpenAI(api_key="YOUR_API_KEY", base_url="${api}")\n\nresponse = client.chat.completions.create(\n model="${modelCode}",\n messages=[\n {\n "role": "user",\n "content": [\n {"type": "image_url", "image_url": {"url": "https://example.com/your-image.jpg"}},\n {"type": "text", "text": "请识别图片中的文字"}\n ]\n }\n ]\n)\nprint(response.choices[0].message.content)`, nodejs: `import OpenAI from 'openai';\n\nconst client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: '${api}' });\n\nconst res = await client.chat.completions.create({\n model: '${modelCode}',\n messages: [\n {\n role: 'user',\n content: [\n { type: 'image_url', image_url: { url: 'https://example.com/your-image.jpg' } },\n { type: 'text', text: '请识别图片中的文字' }\n ]\n }\n ]\n});\nconsole.log(res.choices[0].message.content);`, java: `// ${modelCode} 是 OCR 模型,消息中必须包含图片\n// 调用 ${api}/chat/completions\n// content 需包含 image_url 和 text 两个 part` }; } if (has(6)) { // 图像编辑(图生图) return { curl: `curl ${api}/images/edits \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -F "model=${modelCode}" \\ -F "prompt=把图片变成油画风格" \\ -F "image=@/path/to/image.png"`, python: `import requests\n\nresponse = requests.post(\n "${api}/images/edits",\n headers={"Authorization": "Bearer YOUR_API_KEY"},\n data={"model": "${modelCode}", "prompt": "把图片变成油画风格"},\n files={"image": open("/path/to/image.png", "rb")}\n)\nprint(response.json())`, nodejs: `const form = new FormData();\nform.append("model", "${modelCode}");\nform.append("prompt", "把图片变成油画风格");\nform.append("image", fs.createReadStream("/path/to/image.png"));\n\nconst res = await fetch("${api}/images/edits", {\n method: "POST",\n headers: { "Authorization": "Bearer YOUR_API_KEY" },\n body: form\n});\nconsole.log(await res.json());`, java: `// 使用 multipart/form-data 调用 ${api}/images/edits\n// model=${modelCode}, prompt=把图片变成油画风格, image=` }; } if (has(4)) { // 图像生成(文生图) return { curl: `curl ${api}/images/generations \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{"model": "${modelCode}", "prompt": "一只可爱的猫咪", "n": 1}'`, python: `from openai import OpenAI\n\nclient = OpenAI(api_key="YOUR_API_KEY", base_url="${api}")\nresponse = client.images.generate(model="${modelCode}", prompt="一只可爱的猫咪", n=1)\nprint(response.data[0].url)`, nodejs: `import OpenAI from 'openai';\nconst client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: '${api}' });\nconst res = await client.images.generate({ model: '${modelCode}', prompt: '一只可爱的猫咪', n: 1 });\nconsole.log(res.data[0].url);`, java: `// 调用 ${api}/images/generations\n// model=${modelCode}, prompt=一只可爱的猫咪` }; } if (has(5)) { // 视频生成 return { curl: `curl ${api}/videos/generations \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{"model": "${modelCode}", "prompt": "一段美丽的风景视频"}'`, python: `import requests\nresponse = requests.post(\n "${api}/videos/generations",\n headers={"Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json"},\n json={"model": "${modelCode}", "prompt": "一段美丽的风景视频"}\n)\nprint(response.json())`, nodejs: `const res = await fetch("${api}/videos/generations", {\n method: "POST",\n headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" },\n body: JSON.stringify({ model: "${modelCode}", prompt: "一段美丽的风景视频" })\n});\nconsole.log(await res.json());`, java: `// 调用 ${api}/videos/generations\n// model=${modelCode}` }; } if (has(2)) { // TTS — plus 模型只支持 longanyang/longanhuan,其他用 longxiaochun_v3 const ttsVoice = code.includes('plus') ? 'longanyang' : 'longxiaochun_v3'; return { curl: `curl ${api}/audio/speech \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{"model": "${modelCode}", "input": "你好,世界", "voice": "${ttsVoice}", "response_format": "mp3"}' \\ --output speech.mp3`, python: `from openai import OpenAI\nclient = OpenAI(api_key="YOUR_API_KEY", base_url="${api}")\nresponse = client.audio.speech.create(model="${modelCode}", input="你好,世界", voice="${ttsVoice}", response_format="mp3")\nresponse.stream_to_file("speech.mp3")`, nodejs: `import OpenAI from 'openai';\nconst client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: '${api}' });\nconst mp3 = await client.audio.speech.create({ model: '${modelCode}', input: '你好,世界', voice: '${ttsVoice}', response_format: 'mp3' });\nconst buffer = Buffer.from(await mp3.arrayBuffer());\nawait fs.promises.writeFile('speech.mp3', buffer);`, java: `// 调用 ${api}/audio/speech\n// model=${modelCode}, voice=${ttsVoice}` }; } if (has(3)) { // STT return { curl: `curl ${api}/audio/transcriptions \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -F "model=${modelCode}" \\ -F "file=@/path/to/audio.mp3"`, python: `from openai import OpenAI\nclient = OpenAI(api_key="YOUR_API_KEY", base_url="${api}")\nwith open("/path/to/audio.mp3", "rb") as f:\n transcript = client.audio.transcriptions.create(model="${modelCode}", file=f)\nprint(transcript.text)`, nodejs: `import OpenAI from 'openai';\nconst client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: '${api}' });\nconst transcript = await client.audio.transcriptions.create({ model: '${modelCode}', file: fs.createReadStream('/path/to/audio.mp3') });\nconsole.log(transcript.text);`, java: `// 调用 ${api}/audio/transcriptions\n// model=${modelCode}` }; } if (has(7)) { // Embedding return { curl: `curl ${api}/embeddings \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{"model": "${modelCode}", "input": "你好,世界"}'`, python: `from openai import OpenAI\nclient = OpenAI(api_key="YOUR_API_KEY", base_url="${api}")\nresponse = client.embeddings.create(model="${modelCode}", input="你好,世界")\nprint(response.data[0].embedding)`, nodejs: `import OpenAI from 'openai';\nconst client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: '${api}' });\nconst res = await client.embeddings.create({ model: '${modelCode}', input: '你好,世界' });\nconsole.log(res.data[0].embedding);`, java: `// 调用 ${api}/embeddings\n// model=${modelCode}` }; } if (has(8)) { // Rerank return { curl: `curl ${api}/rerank \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{"model": "${modelCode}", "query": "什么是人工智能", "documents": ["人工智能是计算机科学的一个分支", "机器学习是AI的子领域"]}'`, python: `import requests\nresponse = requests.post(\n "${api}/rerank",\n headers={"Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json"},\n json={"model": "${modelCode}", "query": "什么是人工智能", "documents": ["人工智能是计算机科学的一个分支", "机器学习是AI的子领域"]}\n)\nprint(response.json())`, nodejs: `const res = await fetch("${api}/rerank", {\n method: "POST",\n headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" },\n body: JSON.stringify({ model: "${modelCode}", query: "什么是人工智能", documents: ["人工智能是计算机科学的一个分支", "机器学习是AI的子领域"] })\n});\nconsole.log(await res.json());`, java: `// 调用 ${api}/rerank\n// model=${modelCode}` }; } if (has(1)) { // 多模态(图文/视频理解)—— 返回两套示例 return { _isMultimodal: true, text: { curl: `curl ${api}/chat/completions \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{ "model": "${modelCode}", "messages": [ {"role": "user", "content": "你好,请介绍一下你自己"} ], "stream": false }'`, python: `from openai import OpenAI\n\nclient = OpenAI(api_key="YOUR_API_KEY", base_url="${api}")\n\nresponse = client.chat.completions.create(\n model="${modelCode}",\n messages=[{"role": "user", "content": "你好,请介绍一下你自己"}]\n)\nprint(response.choices[0].message.content)`, nodejs: `import OpenAI from 'openai';\n\nconst client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: '${api}' });\n\nconst res = await client.chat.completions.create({\n model: '${modelCode}',\n messages: [{ role: 'user', content: '你好,请介绍一下你自己' }]\n});\nconsole.log(res.choices[0].message.content);`, java: `OpenAIClient client = OpenAIOkHttpClient.builder()\n .apiKey("YOUR_API_KEY")\n .baseUrl("${api}")\n .build();\n\nChatCompletionCreateParams params = ChatCompletionCreateParams.builder()\n .model("${modelCode}")\n .addMessage(ChatCompletionMessageParam.ofUser(\n ChatCompletionUserMessageParam.builder().content("你好,请介绍一下你自己").build()))\n .build();\n\nChatCompletion completion = client.chat().completions().create(params);\nSystem.out.println(completion.choices().get(0).message().content());` }, multimodal: { curl: `curl ${api}/chat/completions \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{ "model": "${modelCode}", "messages": [ { "role": "user", "content": [ {"type": "image_url", "image_url": {"url": "https://example.com/your-image.jpg"}}, {"type": "text", "text": "请描述这张图片的内容"} ] } ], "stream": false }'`, python: `from openai import OpenAI\n\nclient = OpenAI(api_key="YOUR_API_KEY", base_url="${api}")\n\nresponse = client.chat.completions.create(\n model="${modelCode}",\n messages=[\n {\n "role": "user",\n "content": [\n {"type": "image_url", "image_url": {"url": "https://example.com/your-image.jpg"}},\n {"type": "text", "text": "请描述这张图片的内容"}\n ]\n }\n ]\n)\nprint(response.choices[0].message.content)`, nodejs: `import OpenAI from 'openai';\n\nconst client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: '${api}' });\n\nconst res = await client.chat.completions.create({\n model: '${modelCode}',\n messages: [\n {\n role: 'user',\n content: [\n { type: 'image_url', image_url: { url: 'https://example.com/your-image.jpg' } },\n { type: 'text', text: '请描述这张图片的内容' }\n ]\n }\n ]\n});\nconsole.log(res.choices[0].message.content);`, java: `OpenAIClient client = OpenAIOkHttpClient.builder()\n .apiKey("YOUR_API_KEY")\n .baseUrl("${api}")\n .build();\n\n// content 为多模态列表,包含 image_url 和 text\n// 请参考 OpenAI Java SDK 的多模态消息构建方式` } } as any; } // 默认:LLM return { curl: `curl ${api}/chat/completions \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{ "model": "${modelCode}", "messages": [ {"role": "user", "content": "你好"} ], "stream": false }'`, python: `from openai import OpenAI\n\nclient = OpenAI(\n api_key="YOUR_API_KEY",\n base_url="${api}"\n)\n\nresponse = client.chat.completions.create(\n model="${modelCode}",\n messages=[{"role": "user", "content": "你好"}]\n)\nprint(response.choices[0].message.content)`, nodejs: `import OpenAI from 'openai';\n\nconst client = new OpenAI({ apiKey: 'YOUR_API_KEY', baseURL: '${api}' });\n\nconst res = await client.chat.completions.create({\n model: '${modelCode}',\n messages: [{ role: 'user', content: '你好' }]\n});\nconsole.log(res.choices[0].message.content);`, java: `OpenAIClient client = OpenAIOkHttpClient.builder()\n .apiKey("YOUR_API_KEY")\n .baseUrl("${api}")\n .build();\n\nChatCompletionCreateParams params = ChatCompletionCreateParams.builder()\n .model("${modelCode}")\n .addMessage(ChatCompletionMessageParam.ofUser(\n ChatCompletionUserMessageParam.builder().content("你好").build()))\n .build();\n\nChatCompletion completion = client.chat().completions().create(params);\nSystem.out.println(completion.choices().get(0).message().content());` }; }; const ModelPricingDetail: React.FC = () => { const navigate = useNavigate(); const { modelCode } = useParams(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState<'curl' | 'python' | 'nodejs' | 'java'>('curl'); const [activeInputMode, setActiveInputMode] = useState<'text' | 'multimodal'>('text'); const [copied, setCopied] = useState(false); useEffect(() => { const fetchDetail = async () => { if (!modelCode) { setError('模型代码缺失'); setLoading(false); return; } try { setLoading(true); let resolvedCode = modelCode; try { const mapping = await getPricingMappingLookup(); resolvedCode = mapping.get(modelCode) || modelCode; } catch { resolvedCode = modelCode; } const response = await modelApi.getParsedPricing(resolvedCode); setData(response?.data ?? null); } catch { setError('获取模型详情失败'); } finally { setLoading(false); } }; fetchDetail(); }, [modelCode]); const pricingView = useMemo(() => { if (!data?.model_pricing) return null; if (Array.isArray(data.model_pricing)) { const arr = data.model_pricing as any[]; const looksLikeList = arr.length > 0 && arr.every((it) => (it && (it.item || it.name) && (it.price !== undefined || it.amount !== undefined || it.value !== undefined)) && !it.input_range && !it.range && !it.tier_range && !it.inputRange); if (looksLikeList) return { type: 'list', items: arr } as const; const tiers = arr; const columns = new Set(); tiers.forEach((tier) => { Object.keys(tier || {}).forEach((key) => { // 过滤掉元数据字段和 _original 后缀字段(原价字段,不单独渲染) if (!['unit', 'input_range', 'range', 'inputRange', 'tier_range', 'discount_rate'].includes(key) && !key.endsWith('_original')) { columns.add(key); } }); }); return { type: 'tier', tiers, columns: Array.from(columns) } as const; } if (typeof data.model_pricing === 'object') { const pricing = data.model_pricing as Record; const entries = Object.entries(pricing || {}); const looksLikeNamedPriceObjects = entries.length > 0 && entries.every(([, v]) => v && typeof v === 'object' && (v.price !== undefined || v.amount !== undefined || v.value !== undefined || v.price_cny_per_image !== undefined)); if (looksLikeNamedPriceObjects) { const items = entries.map(([k, v]) => ({ item: translateLabel(v.item ?? v.name ?? k), price: v.price ?? v.amount ?? v.value ?? v.price_cny_per_image, unit: v.unit ?? v.unit_text ?? '' })); return { type: 'list', items } as const; } return { type: 'simple', pricing } as const; } return null; }, [data]); const featureList = useMemo(() => { const features = data?.model_capabilities?.features; if (!features || typeof features !== 'object') return []; return Object.entries(features).map(([key, value]) => ({ key, label: translateLabel(key), enabled: Boolean(value) })); }, [data]); const [showTier, setShowTier] = useState(false); const [selectedTierIndex, setSelectedTierIndex] = useState(0); useEffect(() => { setSelectedTierIndex(0); setShowTier(false); }, [pricingView?.type]); const apiExamples = useMemo(() => data?.model_code ? generateApiExamples(data.model_code, data.categories || []) : null, [data?.model_code, data?.categories]); const renderModelInfo = () => (
{data?.model_intro && (

模型介绍

{data.model_intro}

)} {data?.model_tags?.length ? (

模型标签

{data.model_tags.map((tag) => ({tag}))}
) : null} {data?.model_capabilities && (

模型能力

输入模态
{(data.model_capabilities.input_modalities || []).map((item: string) => { const icon = item?.toLowerCase().includes('image') ? : item?.toLowerCase().includes('video') ?
输出模态
{(data.model_capabilities.output_modalities || []).map((item: string) => { const icon = item?.toLowerCase().includes('image') ? : item?.toLowerCase().includes('video') ?
{featureList.length ? (
功能特性
{featureList.map((feature) => (
{feature.label} {feature.enabled ? : }
))}
) : null}
)} {pricingView && (

价格信息

{pricingView.type === 'tier' && (
)}
{pricingView.type === 'simple' && (() => { const p = pricingView.pricing as any; const unit = p.unit; const entries = Object.entries(p).filter(([k, v]) => k !== 'unit' && v !== null && v !== undefined && !Array.isArray(v) && typeof v !== 'object'); const isInputKey = (k: string) => /(^|_)input|image_input|text_input|输入/i.test(k); const isOutputKey = (k: string) => /(^|_)output|输出/i.test(k); const left: [string, any][] = [], right: [string, any][] = [], other: [string, any][] = []; entries.forEach((entry) => { const [key] = entry; if (isInputKey(key)) left.push(entry); else if (isOutputKey(key)) right.push(entry); else other.push(entry); }); other.forEach((it) => { if (left.length <= right.length) left.push(it); else right.push(it); }); const renderEntry = ([key, value]: [string, any]) => (
{getDisplayLabel(key)}{renderPriceCell(value, p[`${key}_original`], unit, p.discount_rate)}
); return (
{left.map(renderEntry)}
{right.map(renderEntry)}
); })()} {pricingView.type === 'list' && (() => { const items = pricingView.items || []; const isInput = (l: string) => /(^|_)input|image_input|text_input|输入/i.test(String(l)); const isOutput = (l: string) => /(^|_)output|输出/i.test(String(l)); const left: any[] = [], right: any[] = [], other: any[] = []; items.forEach((it) => { const rawLabel = it.item || it.name || it.title || ''; if (isInput(rawLabel)) left.push(it); else if (isOutput(rawLabel)) right.push(it); else other.push(it); }); other.forEach((it) => { if (left.length <= right.length) left.push(it); else right.push(it); }); const renderPill = (it: any, idx: number) => (
{translateLabel(it.item || it.name || it.title || '—')}
{renderPriceCell(it.price ?? it.amount ?? it.value, it.price_original, it.unit || '', it.discount_rate)}
); return (
{left.map(renderPill)}
{right.map(renderPill)}
); })()} {pricingView.type === 'tier' && !showTier && (() => { const tier = pricingView.tiers[selectedTierIndex] || {}; const cols = pricingView.columns || []; const preferredOrder = ['input', 'input_thinking', 'input_cache_hit', 'input_cache_hit_thinking', 'input_batch', 'input_batch_thinking', 'output', 'output_thinking', 'output_batch', 'output_thinking_batch', 'cache_write', 'cache_read', 'explicit_cache_create', 'explicit_cache_hit', 'explicit_cache_create_thinking', 'explicit_cache_hit_thinking', 'image_input', 'text_input']; const inputCols = cols.filter((c: string) => /(^|_)input|image_input|text_input/i.test(c)); const outputCols = cols.filter((c: string) => /(^|_)output/i.test(c)); const remaining = cols.filter((c: string) => !inputCols.includes(c) && !outputCols.includes(c)); const sortByPreferred = (arr: string[]) => arr.slice().sort((a, b) => { const ia = preferredOrder.indexOf(a), ib = preferredOrder.indexOf(b); if (ia === -1 && ib === -1) return a.localeCompare(b); if (ia === -1) return 1; if (ib === -1) return -1; return ia - ib; }); let left = sortByPreferred(inputCols), right = sortByPreferred(outputCols); if (left.length === 0 && right.length === 0) { const half = Math.ceil(cols.length / 2); left = cols.slice(0, half); right = cols.slice(half); } else { sortByPreferred(remaining).forEach((c: string) => { if (left.length > right.length) right.push(c); else left.push(c); }); } const renderRow = (col: string) => (
{getDisplayLabel(col)}
{renderPriceCell(tier?.[col], tier?.[`${col}_original`], tier?.unit, tier?.discount_rate)}
); return (
{left.length > 0 &&
{left.map(renderRow)}
}{right.length > 0 &&
{right.map(renderRow)}
}
); })()} {pricingView.type === 'tier' && showTier && (
{pricingView.columns.map((col) => ())}{pricingView.tiers.map((tier, index) => ({pricingView.columns.map((col) => ())}))}
输入范围{getDisplayLabel(col)}
{getRangeLabel(tier)}{renderPriceCell((tier as any)?.[col], (tier as any)?.[`${col}_original`], (tier as any)?.unit)}
)}
)} {Array.isArray(data?.tool_call_pricing) && data.tool_call_pricing.length > 0 && (

工具调用价格

{data.tool_call_pricing.map((item: any, index: number) => ())}
工具价格
{item.tool || '—'}{formatValue(item.price ?? item.amount ?? item.value, item.unit)}
)} {data?.model_limits && (

模型限制

{Object.entries(data.model_limits).map(([key, value]) => key !== 'note' && (
{limitKeyLabels[key] || key.replace(/_/g, ' ')}{formatValue(value)}
))}
)}
); const renderApiExamples = () => { if (!apiExamples) return null; // realtime 等不支持的模型:显示提示卡片,不展示代码和调试台 if ((apiExamples as any)._isUnsupported) { return (
暂不支持通过平台 API 调用
{(apiExamples as any).reason}
); } const isMultimodal = (apiExamples as any)._isMultimodal === true; const examples = isMultimodal ? (apiExamples as any)[activeInputMode] : apiExamples; const tabs = [ { key: 'curl' as const, label: 'cURL' }, { key: 'python' as const, label: 'Python' }, { key: 'nodejs' as const, label: 'Node.js' }, { key: 'java' as const, label: 'Java' } ]; return (

API 调用示例

{isMultimodal && (
)}
{tabs.map((tab) => ( ))}
{examples[activeTab]}

请将 YOUR_API_KEY 替换为您在开放平台获取的 API Key

{data?.categories?.includes(2) && !isMultimodal && (() => { const isPlus = (data.model_code || '').toLowerCase().includes('plus'); const voices = isPlus ? [{ id: 'longanyang', name: '龙安阳' }, { id: 'longanhuan', name: '龙安欢' }] : [ { id: 'longxiaochun_v3', name: '龙小淳' }, { id: 'longhua_v3', name: '龙华' }, { id: 'longcheng_v3', name: '龙成' }, { id: 'longwan_v3', name: '龙婉' }, { id: 'longxiaoxia_v3', name: '龙小夏' }, { id: 'longshu_v3', name: '龙书' }, { id: 'longfei_v3', name: '龙飞' }, { id: 'longxiu_v3', name: '龙秀' }, { id: 'longmiao_v3', name: '龙苗' }, { id: 'longyingxiao_v3', name: '龙盈晓' }, { id: 'longhuhu_v3', name: '龙虎虎' }, { id: 'longniuniu_v3', name: '龙妞妞' }, { id: 'longpaopao_v3', name: '龙泡泡' }, { id: 'longjielidou_v3', name: '龙节力豆' }, { id: 'longxian_v3', name: '龙鲜' }, { id: 'longjiaxin_v3', name: '龙嘉欣' }, { id: 'longanyue_v3', name: '龙安悦' }, { id: 'longlaotie_v3', name: '龙老铁' }, { id: 'longshange_v3', name: '龙山歌' }, { id: 'loongkyong_v3', name: '韩语女声' }, { id: 'loongriko_v3', name: '日语女声' }, { id: 'longhouge_v3', name: '龙猴哥' }, { id: 'longjiqi_v3', name: '龙机器' }, { id: 'longdaiyu_v3', name: '龙黛玉' }, { id: 'longlaobo_v3', name: '龙老伯' }, { id: 'longlaoyi_v3', name: '龙老姨' }, ]; return (
支持的音色 {isPlus ? '(plus 专属)' : ''}
{voices.map(v => ( {v.id}({v.name}) ))}
); })()}
); }; const isRealtimeModel = (data?.model_code || modelCode || '').toLowerCase().includes('realtime'); return (

模型详情

{data?.model_code || modelCode}

{data?.is_api_enabled && !isRealtimeModel && 支持API调用} {isRealtimeModel && 实时流协议·暂不支持平台API}
{loading && (
加载中...
)} {!loading && error && (
{error}
)} {!loading && !error && data && (
{renderModelInfo()}
{(data.is_api_enabled || isRealtimeModel) &&
{renderApiExamples()}
}
)}
); }; export default ModelPricingDetail;