{"repo": "1600822305/aetherlink", "files": {"src/pages/Settings/AssistantModelSettings/VisionRecognitionTab.tsx": {"1b56c288492e5c5f51c6a29046220ff653ab983c": {"t": 1785595149.0954847, "n": 11590, "h": "e007c5f9362877b0", "c": "import React, { useState, useMemo } from 'react';\nimport {\n Box,\n Typography,\n TextField,\n Button,\n Alert,\n InputAdornment,\n IconButton,\n CircularProgress,\n FormControl,\n Select,\n MenuItem,\n} from '@mui/material';\nimport type { SelectChangeEvent } from '@mui/material';\nimport { Eye, EyeOff, CheckCircle2, XCircle, RefreshCw, RotateCcw } from 'lucide-react';\nimport { useSelector, useDispatch } from 'react-redux';\nimport { SettingGroup, Row, YStack } from '../../../components/settings/SettingComponents';\nimport CustomSwitch from '../../../components/CustomSwitch';\nimport type { RootState, AppDispatch } from '../../../shared/store';\nimport {\n setVisionEnabled,\n setVisionModelSource,\n setVisionPresetModelRef,\n updateVisionCustomConfig,\n setVisionPrompt,\n setVisionFailureStrategy,\n setVisionTimeoutMs,\n DEFAULT_VISION_PROMPT,\n type VisionModelSource,\n type VisionFailureStrategy,\n} from '../../../shared/store/slices/visionRecognitionSlice';\nimport { testVisionModelConnection, type VisionTestResult } from '../../../shared/services/vision/VisionRecognitionService';\nimport ModelPickerRow from './ModelPickerRow';\nimport { useAllModels, type ModelWithProvider } from './useAllModels';\n\n/** API 厂商预设(点击快捷填充 Base URL) */\nconst PROVIDER_PRESETS: Array<{ name: string; baseUrl: string }> = [\n { name: 'OpenAI', baseUrl: 'https://api.openai.com/v1' },\n { name: '小米 MiMo', baseUrl: 'https://api.xiaomimimo.com/v1' },\n { name: 'Kimi (Moonshot)', baseUrl: 'https://api.moonshot.cn/v1' },\n { name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1' },\n { name: '硅基流动', baseUrl: 'https://api.siliconflow.cn/v1' },\n { name: '智谱 GLM', baseUrl: 'https://open.bigmodel.cn/api/paas/v4' },\n { name: '阿里云百炼', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1' },\n];\n\nconst VisionRecognitionTab: React.FC = () => {\n const dispatch = useDispatch();\n\n const visionState = useSelector((state: RootState) => state.visionRecognition);\n const allModels = useAllModels();\n\n const [showApiKey, setShowApiKey] = useState(false);\n const [isTesting, setIsTesting] = useState(false);\n const [testResult, setTestResult] = useState(null);\n\n const selectedPresetModel = useMemo(() => {\n const ref = visionState.presetModelRef;\n if (!ref) return null;\n return allModels.find((model) => model.providerId === ref.providerId && model.id === ref.modelId) || null;\n }, [allModels, visionState.presetModelRef]);\n\n const handleModelSourceChange = (event: SelectChangeEvent) => {\n dispatch(setVisionModelSource(event.target.value as VisionModelSource));\n setTestResult(null);\n };\n\n const handlePresetModelSelect = (model: ModelWithProvider) => {\n dispatch(\n setVisionPresetModelRef({\n providerId: model.providerId || (model as any).provider,\n modelId: model.id,\n })\n );\n setTestResult(null);\n };\n\n const handleProviderPreset = (event: SelectChangeEvent) => {\n const preset = PROVIDER_PRESETS.find((p) => p.name === event.target.value);\n if (preset) {\n dispatch(updateVisionCustomConfig({ baseUrl: preset.baseUrl }));\n setTestResult(null);\n }\n };\n\n const handleCustomChange = (field: 'modelName' | 'baseUrl' | 'apiKey') =>\n (e: React.ChangeEvent) => {\n dispatch(updateVisionCustomConfig({ [field]: e.target.value }));\n setTestResult(null);\n };\n\n const handleTestConnection = async () => {\n setIsTesting(true);\n setTestResult(null);\n try {\n const result = await testVisionModelConnection(visionState);\n setTestResult(result);\n } finally {\n setIsTesting(false);\n }\n };\n\n const handleTimeoutChange = (e: React.ChangeEvent) => {\n const value = e.target.value;\n if (value === '') return;\n const seconds = parseInt(value, 10);\n if (!isNaN(seconds) && seconds >= 5 && seconds <= 600) {\n dispatch(setVisionTimeoutMs(seconds * 1000));\n }\n };\n\n const isCustom = visionState.modelSource === 'custom';\n const canTest = isCustom\n ? !!(visionState.custom.modelName && visionState.custom.baseUrl && visionState.custom.apiKey)\n : !!visionState.presetModelRef;\n\n return (\n \n\n {/* ==================== 总开关 ==================== */}\n \n \n \n 启用视觉识别\n \n 发送图片给不支持视觉的模型时,自动用视觉模型分析图片内容并提供给当前模型\n \n \n dispatch(setVisionEnabled(!visionState.enabled))}\n />\n \n \n\n {/* ==================== 模型来源 ==================== */}\n \n \n 模型来源\n \n \n \n \n\n {!isCustom && (\n \n )}\n\n {isCustom && (\n <>\n \n 厂商预设\n \n \n \n \n\n \n 模型名称\n \n \n\n \n Base URL\n \n \n\n \n API 密钥\n \n setShowApiKey(!showApiKey)} edge=\"end\">\n {showApiKey ? : }\n \n \n ),\n }}\n />\n \n \n )}\n\n \n : }\n sx={{ textTransform: 'none' }}\n >\n {isTesting ? '测试中...' : '测试 API 连接'}\n \n\n {testResult && (\n : }\n sx={{ mt: 1.5, borderRadius: 2 }}\n >\n {testResult.message}\n {testResult.success && testResult.latencyMs !== undefined && (\n \n 响应时间: {testResult.latencyMs}ms\n \n )}\n \n )}\n \n \n\n {/* ==================== 高级设置 ==================== */}\n \n \n \n \n 分析提示词\n \n 指导视觉模型如何描述图片\n \n \n dispatch(setVisionPrompt(DEFAULT_VISION_PROMPT))}\n title=\"恢复默认\"\n >\n \n \n \n dispatch(setVisionPrompt(e.target.value))}\n />\n \n\n \n \n 分析失败时\n \n 视觉模型分析失败后的处理方式\n \n \n \n dispatch(setVisionFailureStrategy(e.target.value as VisionFailureStrategy))}\n >\n 中止并报错\n 移除图片继续发送\n \n \n \n\n \n 分析超时(秒)\n \n \n \n\n {/* ==================== 配置说明 ==================== */}\n \n \n {[\n { label: '使用已配置的模型', desc: '从现有供应商中选择一个支持视觉的模型,复用其密钥与地址' },\n { label: '自定义API配置', desc: '独立填写模型名、Base URL 与密钥(OpenAI 兼容协议),与主API互不影响' },\n { label: '工作方式', desc: '仅当当前对话模型不支持图片时触发;分析结果只注入本次请求,聊天记录仍保留原图' },\n ].map((item) => (\n \n {item.label} — {item.desc}\n \n ))}\n \n \n\n \n );\n};\n\nexport default VisionRecognitionTab;\n"}, "2fc9a7e75431a6871ebb5e881412987a1520ec7a": {"t": 1785764114.289419, "n": 11590, "h": "e007c5f9362877b0", "c": "import React, { useState, useMemo } from 'react';\nimport {\n Box,\n Typography,\n TextField,\n Button,\n Alert,\n InputAdornment,\n IconButton,\n CircularProgress,\n FormControl,\n Select,\n MenuItem,\n} from '@mui/material';\nimport type { SelectChangeEvent } from '@mui/material';\nimport { Eye, EyeOff, CheckCircle2, XCircle, RefreshCw, RotateCcw } from 'lucide-react';\nimport { useSelector, useDispatch } from 'react-redux';\nimport { SettingGroup, Row, YStack } from '../../../components/settings/SettingComponents';\nimport CustomSwitch from '../../../components/CustomSwitch';\nimport type { RootState, AppDispatch } from '../../../shared/store';\nimport {\n setVisionEnabled,\n setVisionModelSource,\n setVisionPresetModelRef,\n updateVisionCustomConfig,\n setVisionPrompt,\n setVisionFailureStrategy,\n setVisionTimeoutMs,\n DEFAULT_VISION_PROMPT,\n type VisionModelSource,\n type VisionFailureStrategy,\n} from '../../../shared/store/slices/visionRecognitionSlice';\nimport { testVisionModelConnection, type VisionTestResult } from '../../../shared/services/vision/VisionRecognitionService';\nimport ModelPickerRow from './ModelPickerRow';\nimport { useAllModels, type ModelWithProvider } from './useAllModels';\n\n/** API 厂商预设(点击快捷填充 Base URL) */\nconst PROVIDER_PRESETS: Array<{ name: string; baseUrl: string }> = [\n { name: 'OpenAI', baseUrl: 'https://api.openai.com/v1' },\n { name: '小米 MiMo', baseUrl: 'https://api.xiaomimimo.com/v1' },\n { name: 'Kimi (Moonshot)', baseUrl: 'https://api.moonshot.cn/v1' },\n { name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1' },\n { name: '硅基流动', baseUrl: 'https://api.siliconflow.cn/v1' },\n { name: '智谱 GLM', baseUrl: 'https://open.bigmodel.cn/api/paas/v4' },\n { name: '阿里云百炼', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1' },\n];\n\nconst VisionRecognitionTab: React.FC = () => {\n const dispatch = useDispatch();\n\n const visionState = useSelector((state: RootState) => state.visionRecognition);\n const allModels = useAllModels();\n\n const [showApiKey, setShowApiKey] = useState(false);\n const [isTesting, setIsTesting] = useState(false);\n const [testResult, setTestResult] = useState(null);\n\n const selectedPresetModel = useMemo(() => {\n const ref = visionState.presetModelRef;\n if (!ref) return null;\n return allModels.find((model) => model.providerId === ref.providerId && model.id === ref.modelId) || null;\n }, [allModels, visionState.presetModelRef]);\n\n const handleModelSourceChange = (event: SelectChangeEvent) => {\n dispatch(setVisionModelSource(event.target.value as VisionModelSource));\n setTestResult(null);\n };\n\n const handlePresetModelSelect = (model: ModelWithProvider) => {\n dispatch(\n setVisionPresetModelRef({\n providerId: model.providerId || (model as any).provider,\n modelId: model.id,\n })\n );\n setTestResult(null);\n };\n\n const handleProviderPreset = (event: SelectChangeEvent) => {\n const preset = PROVIDER_PRESETS.find((p) => p.name === event.target.value);\n if (preset) {\n dispatch(updateVisionCustomConfig({ baseUrl: preset.baseUrl }));\n setTestResult(null);\n }\n };\n\n const handleCustomChange = (field: 'modelName' | 'baseUrl' | 'apiKey') =>\n (e: React.ChangeEvent) => {\n dispatch(updateVisionCustomConfig({ [field]: e.target.value }));\n setTestResult(null);\n };\n\n const handleTestConnection = async () => {\n setIsTesting(true);\n setTestResult(null);\n try {\n const result = await testVisionModelConnection(visionState);\n setTestResult(result);\n } finally {\n setIsTesting(false);\n }\n };\n\n const handleTimeoutChange = (e: React.ChangeEvent) => {\n const value = e.target.value;\n if (value === '') return;\n const seconds = parseInt(value, 10);\n if (!isNaN(seconds) && seconds >= 5 && seconds <= 600) {\n dispatch(setVisionTimeoutMs(seconds * 1000));\n }\n };\n\n const isCustom = visionState.modelSource === 'custom';\n const canTest = isCustom\n ? !!(visionState.custom.modelName && visionState.custom.baseUrl && visionState.custom.apiKey)\n : !!visionState.presetModelRef;\n\n return (\n \n\n {/* ==================== 总开关 ==================== */}\n \n \n \n 启用视觉识别\n \n 发送图片给不支持视觉的模型时,自动用视觉模型分析图片内容并提供给当前模型\n \n \n dispatch(setVisionEnabled(!visionState.enabled))}\n />\n \n \n\n {/* ==================== 模型来源 ==================== */}\n \n \n 模型来源\n \n \n \n \n\n {!isCustom && (\n \n )}\n\n {isCustom && (\n <>\n \n 厂商预设\n \n \n \n \n\n \n 模型名称\n \n \n\n \n Base URL\n \n \n\n \n API 密钥\n \n setShowApiKey(!showApiKey)} edge=\"end\">\n {showApiKey ? : }\n \n \n ),\n }}\n />\n \n \n )}\n\n \n : }\n sx={{ textTransform: 'none' }}\n >\n {isTesting ? '测试中...' : '测试 API 连接'}\n \n\n {testResult && (\n : }\n sx={{ mt: 1.5, borderRadius: 2 }}\n >\n {testResult.message}\n {testResult.success && testResult.latencyMs !== undefined && (\n \n 响应时间: {testResult.latencyMs}ms\n \n )}\n \n )}\n \n \n\n {/* ==================== 高级设置 ==================== */}\n \n \n \n \n 分析提示词\n \n 指导视觉模型如何描述图片\n \n \n dispatch(setVisionPrompt(DEFAULT_VISION_PROMPT))}\n title=\"恢复默认\"\n >\n \n \n \n dispatch(setVisionPrompt(e.target.value))}\n />\n \n\n \n \n 分析失败时\n \n 视觉模型分析失败后的处理方式\n \n \n \n dispatch(setVisionFailureStrategy(e.target.value as VisionFailureStrategy))}\n >\n 中止并报错\n 移除图片继续发送\n \n \n \n\n \n 分析超时(秒)\n \n \n \n\n {/* ==================== 配置说明 ==================== */}\n \n \n {[\n { label: '使用已配置的模型', desc: '从现有供应商中选择一个支持视觉的模型,复用其密钥与地址' },\n { label: '自定义API配置', desc: '独立填写模型名、Base URL 与密钥(OpenAI 兼容协议),与主API互不影响' },\n { label: '工作方式', desc: '仅当当前对话模型不支持图片时触发;分析结果只注入本次请求,聊天记录仍保留原图' },\n ].map((item) => (\n \n {item.label} — {item.desc}\n \n ))}\n \n \n\n \n );\n};\n\nexport default VisionRecognitionTab;\n"}}}}