1 line
16 KiB
JSON
1 line
16 KiB
JSON
{"repo": "andychion/clikon-agent", "files": {"src/renderer/pages/settings/components/EditModeModal.tsx": {"f93a8d2f65f4e329da3f4a073dd5811afc517af1": {"t": 1785595505.7794263, "n": 15459, "h": "389cf9c0713a3667", "c": "import type { IProvider } from '@/common/storage';\nimport ModalHOC from '@/renderer/utils/ModalHOC';\nimport { Form, Input, Message, Select } from '@arco-design/web-react';\nimport React, { useEffect, useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport AionModal from '@/renderer/components/base/AionModal';\nimport { LinkCloud } from '@icon-park/react';\nimport { ipcBridge } from '@/common';\nimport useModeModeList from '../../../hooks/useModeModeList';\n\n// Provider Logo imports\nimport GeminiLogo from '@/renderer/assets/logos/gemini.svg';\nimport OpenAILogo from '@/renderer/assets/logos/openai.svg';\nimport AnthropicLogo from '@/renderer/assets/logos/anthropic.svg';\nimport BedrockLogo from '@/renderer/assets/logos/bedrock.svg';\nimport DeepSeekLogo from '@/renderer/assets/logos/deepseek.svg';\nimport OpenRouterLogo from '@/renderer/assets/logos/openrouter.svg';\nimport SiliconFlowLogo from '@/renderer/assets/logos/siliconflow.png';\nimport QwenLogo from '@/renderer/assets/logos/qwen.svg';\nimport KimiLogo from '@/renderer/assets/logos/kimi.svg';\nimport ZhipuLogo from '@/renderer/assets/logos/zhipu.svg';\nimport XaiLogo from '@/renderer/assets/logos/xai.svg';\nimport VolcengineLogo from '@/renderer/assets/logos/volcengine.svg';\nimport BaiduLogo from '@/renderer/assets/logos/baidu.svg';\nimport TencentLogo from '@/renderer/assets/logos/tencent.svg';\nimport LingyiLogo from '@/renderer/assets/logos/lingyiwanwu.svg';\nimport PoeLogo from '@/renderer/assets/logos/poe.svg';\nimport ModelScopeLogo from '@/renderer/assets/logos/modelscope.svg';\nimport InfiniAILogo from '@/renderer/assets/logos/infiniai.svg';\nimport CtyunLogo from '@/renderer/assets/logos/ctyun.svg';\nimport StepFunLogo from '@/renderer/assets/logos/stepfun.svg';\nimport NewApiLogo from '@/renderer/assets/logos/newapi.svg';\n\n/**\n * 供应商配置(包含名称、URL、Logo)\n * Provider config (includes name, URL, logo)\n */\nconst PROVIDER_CONFIGS = [\n { name: 'Gemini', url: '', logo: GeminiLogo, platform: 'gemini' },\n { name: 'Gemini (Vertex AI)', url: '', logo: GeminiLogo, platform: 'gemini-vertex-ai' },\n { name: 'New API', url: '', logo: NewApiLogo, platform: 'new-api' },\n { name: 'OpenAI', url: 'https://api.openai.com/v1', logo: OpenAILogo },\n { name: 'Anthropic', url: 'https://api.anthropic.com/v1', logo: AnthropicLogo },\n { name: 'AWS Bedrock', url: '', logo: BedrockLogo, platform: 'bedrock' },\n { name: 'DeepSeek', url: 'https://api.deepseek.com', logo: DeepSeekLogo },\n { name: 'OpenRouter', url: 'https://openrouter.ai/api/v1', logo: OpenRouterLogo },\n { name: 'SiliconFlow-CN', url: 'https://api.siliconflow.cn/v1', logo: SiliconFlowLogo },\n { name: 'SiliconFlow', url: 'https://api.siliconflow.com/v1', logo: SiliconFlowLogo },\n { name: 'Dashscope', url: 'https://dashscope.aliyuncs.com/compatible-mode/v1', logo: QwenLogo },\n { name: 'Moonshot (China)', url: 'https://api.moonshot.cn/v1', logo: KimiLogo },\n { name: 'Moonshot (Global)', url: 'https://api.moonshot.ai/v1', logo: KimiLogo },\n { name: 'Zhipu', url: 'https://open.bigmodel.cn/api/paas/v4', logo: ZhipuLogo },\n { name: 'xAI', url: 'https://api.x.ai/v1', logo: XaiLogo },\n { name: 'Ark', url: 'https://ark.cn-beijing.volces.com/api/v3', logo: VolcengineLogo },\n { name: 'Qianfan', url: 'https://qianfan.baidubce.com/v2', logo: BaiduLogo },\n { name: 'Hunyuan', url: 'https://api.hunyuan.cloud.tencent.com/v1', logo: TencentLogo },\n { name: 'Lingyi', url: 'https://api.lingyiwanwu.com/v1', logo: LingyiLogo },\n { name: 'Poe', url: 'https://api.poe.com/v1', logo: PoeLogo },\n { name: 'ModelScope', url: 'https://api-inference.modelscope.cn/v1', logo: ModelScopeLogo },\n { name: 'InfiniAI', url: 'https://cloud.infini-ai.com/maas/v1', logo: InfiniAILogo },\n { name: 'Ctyun', url: 'https://wishub-x1.ctyun.cn/v1', logo: CtyunLogo },\n { name: 'StepFun', url: 'https://api.stepfun.com/v1', logo: StepFunLogo },\n];\n\n/**\n * 根据名称或 URL 获取供应商 Logo\n * Get provider logo by name or URL\n */\nconst getProviderLogo = (name?: string, baseUrl?: string, platform?: string): string | null => {\n if (!name && !baseUrl && !platform) return null;\n\n // 优先按 platform 匹配(Gemini 系列)\n if (platform) {\n const byPlatform = PROVIDER_CONFIGS.find((p) => p.platform === platform);\n if (byPlatform) return byPlatform.logo;\n }\n\n // 按名称精确匹配\n const byName = PROVIDER_CONFIGS.find((p) => p.name === name);\n if (byName) return byName.logo;\n\n // 按名称模糊匹配(忽略大小写)\n const byNameLower = PROVIDER_CONFIGS.find((p) => p.name.toLowerCase() === name?.toLowerCase());\n if (byNameLower) return byNameLower.logo;\n\n // 按 URL 匹配\n if (baseUrl) {\n const byUrl = PROVIDER_CONFIGS.find((p) => p.url && baseUrl.includes(p.url.replace('https://', '').split('/')[0]));\n if (byUrl) return byUrl.logo;\n }\n\n return null;\n};\n\n/**\n * 供应商 Logo 组件\n * Provider Logo Component\n */\nconst ProviderLogo: React.FC<{ logo: string | null; name: string; size?: number }> = ({ logo, name, size = 20 }) => {\n if (logo) {\n return <img src={logo} alt={name} className='object-contain shrink-0' style={{ width: size, height: size }} />;\n }\n return <LinkCloud theme='outline' size={size} className='text-t-secondary flex shrink-0' />;\n};\n\nconst EditModeModal = ModalHOC<{ data?: IProvider; onChange(data: IProvider): void }>(({ modalProps, modalCtrl, ...props }) => {\n const { t } = useTranslation();\n const { data } = props;\n const [form] = Form.useForm();\n const [message, messageContext] = Message.useMessage();\n\n // Watch bedrockAuthMethod only for UI conditional rendering (not for auto-refresh)\n const bedrockAuthMethod = Form.useWatch('bedrockAuthMethod', form);\n const isBedrock = data?.platform === 'bedrock';\n\n // 获取供应商 Logo / Get provider logo\n const providerLogo = useMemo(() => {\n return getProviderLogo(data?.name, data?.baseUrl, data?.platform);\n }, [data?.name, data?.baseUrl, data?.platform]);\n\n // For Bedrock, don't pass bedrockConfig to avoid auto-refresh on input changes\n // We'll build it dynamically in onFocus\n const modelListState = useModeModeList(data?.platform || 'gemini', data?.baseUrl, data?.apiKey, true, undefined);\n\n useEffect(() => {\n if (data) {\n form.setFieldsValue({\n ...data,\n model: data.model && data.model.length > 0 ? (data.model.length === 1 ? data.model[0] : data.model) : undefined,\n bedrockAuthMethod: data.bedrockConfig?.authMethod || 'accessKey',\n bedrockRegion: data.bedrockConfig?.region || 'us-east-1',\n bedrockAccessKeyId: data.bedrockConfig?.accessKeyId || '',\n bedrockSecretAccessKey: data.bedrockConfig?.secretAccessKey || '',\n bedrockProfile: data.bedrockConfig?.profile || '',\n });\n }\n }, [data, form]);\n\n return (\n <AionModal\n visible={modalProps.visible}\n onCancel={modalCtrl.close}\n header={{ title: t('settings.editModel'), showClose: true }}\n style={{ minHeight: '400px', maxHeight: '90vh', borderRadius: 16 }}\n contentStyle={{ background: 'var(--bg-1)', borderRadius: 16, padding: '20px 24px 16px', overflow: 'auto' }}\n onOk={async () => {\n const values = await form.validate();\n const updatedProvider: IProvider = {\n ...(data || {}),\n ...values,\n // Ensure model is always an array\n model: Array.isArray(values.model) ? values.model : [values.model],\n };\n\n // Add Bedrock configuration if platform is Bedrock\n if (isBedrock) {\n updatedProvider.bedrockConfig = {\n authMethod: values.bedrockAuthMethod,\n region: values.bedrockRegion,\n ...(values.bedrockAuthMethod === 'accessKey'\n ? {\n accessKeyId: values.bedrockAccessKeyId,\n secretAccessKey: values.bedrockSecretAccessKey,\n }\n : {\n profile: values.bedrockProfile,\n }),\n };\n }\n\n props.onChange(updatedProvider);\n modalCtrl.close();\n }}\n okText={t('common.save')}\n cancelText={t('common.cancel')}\n >\n {messageContext}\n <div className='py-20px'>\n <Form form={form} layout='vertical'>\n {/* 模型供应商名称(可编辑,带 Logo)/ Model Provider name (editable, with Logo) */}\n <Form.Item\n label={\n <div className='flex items-center gap-6px'>\n <ProviderLogo logo={providerLogo} name={data?.name || ''} size={16} />\n <span>{t('settings.modelProvider')}</span>\n </div>\n }\n field='name'\n required\n rules={[{ required: true }]}\n >\n <Input placeholder={t('settings.modelProvider')} />\n </Form.Item>\n\n {/* Base URL - 仅 Gemini 平台显示(用于自定义代理)/ Base URL - only for Gemini platform (for custom proxy) */}\n <Form.Item hidden={isBedrock} label={t('settings.baseUrl')} required={data?.platform !== 'gemini' && data?.platform !== 'gemini-vertex-ai' && !isBedrock} rules={[{ required: data?.platform !== 'gemini' && data?.platform !== 'gemini-vertex-ai' && !isBedrock }]} field={'baseUrl'} disabled>\n <Input></Input>\n </Form.Item>\n\n <Form.Item hidden={isBedrock} label={t('settings.apiKey')} required={!isBedrock} rules={[{ required: !isBedrock }]} field={'apiKey'} extra={<div className='text-11px text-t-secondary mt-2'>💡 {t('settings.multiApiKeyEditTip')}</div>}>\n <Input.TextArea rows={4} placeholder={t('settings.apiKeyPlaceholder')} />\n </Form.Item>\n\n {/* AWS Bedrock Authentication Method */}\n <Form.Item hidden={!isBedrock} label={t('settings.bedrock.authMethod')} field={'bedrockAuthMethod'} required={isBedrock} rules={[{ required: isBedrock }]}>\n <Select>\n <Select.Option value='accessKey'>{t('settings.bedrock.authMethodAccessKey')}</Select.Option>\n <Select.Option value='profile'>{t('settings.bedrock.authMethodProfile')}</Select.Option>\n </Select>\n </Form.Item>\n\n {/* AWS Region */}\n <Form.Item hidden={!isBedrock} label={t('settings.bedrock.region')} field={'bedrockRegion'} required={isBedrock} rules={[{ required: isBedrock }]} extra={t('settings.bedrock.regionHint')}>\n <Select showSearch>\n <Select.Option value='us-east-1'>US East (N. Virginia)</Select.Option>\n <Select.Option value='us-west-2'>US West (Oregon)</Select.Option>\n <Select.Option value='eu-west-1'>Europe (Ireland)</Select.Option>\n <Select.Option value='eu-central-1'>Europe (Frankfurt)</Select.Option>\n <Select.Option value='ap-southeast-1'>Asia Pacific (Singapore)</Select.Option>\n <Select.Option value='ap-northeast-1'>Asia Pacific (Tokyo)</Select.Option>\n <Select.Option value='ap-southeast-2'>Asia Pacific (Sydney)</Select.Option>\n <Select.Option value='ca-central-1'>Canada (Central)</Select.Option>\n </Select>\n </Form.Item>\n\n {/* Access Key ID */}\n <Form.Item hidden={!isBedrock || bedrockAuthMethod !== 'accessKey'} label={t('settings.bedrock.accessKeyId')} field={'bedrockAccessKeyId'} required={isBedrock && bedrockAuthMethod === 'accessKey'} rules={[{ required: isBedrock && bedrockAuthMethod === 'accessKey' }]}>\n <Input.Password placeholder='AKIA...' visibilityToggle />\n </Form.Item>\n\n {/* Secret Access Key */}\n <Form.Item hidden={!isBedrock || bedrockAuthMethod !== 'accessKey'} label={t('settings.bedrock.secretAccessKey')} field={'bedrockSecretAccessKey'} required={isBedrock && bedrockAuthMethod === 'accessKey'} rules={[{ required: isBedrock && bedrockAuthMethod === 'accessKey' }]}>\n <Input.Password visibilityToggle />\n </Form.Item>\n\n {/* AWS Profile */}\n <Form.Item hidden={!isBedrock || bedrockAuthMethod !== 'profile'} label={t('settings.bedrock.profile')} field={'bedrockProfile'} required={isBedrock && bedrockAuthMethod === 'profile'} rules={[{ required: isBedrock && bedrockAuthMethod === 'profile' }]} extra={t('settings.bedrock.profileHint')}>\n <Input placeholder='default' />\n </Form.Item>\n\n {/* Model Selection */}\n <Form.Item label={t('settings.modelName')} field={'model'} required rules={[{ required: true }]} validateStatus={modelListState.error ? 'error' : undefined} help={modelListState.error}>\n <Select\n loading={modelListState.isLoading}\n showSearch\n allowCreate\n mode={data?.model && data.model.length > 1 ? 'multiple' : undefined}\n onFocus={async () => {\n // For Bedrock, build bedrockConfig from current form values and fetch models\n if (isBedrock) {\n const values = form.getFields();\n if (!values.bedrockAuthMethod || !values.bedrockRegion) {\n message.error(t('settings.bedrock.fillRequiredFields'));\n return;\n }\n if (values.bedrockAuthMethod === 'accessKey' && (!values.bedrockAccessKeyId || !values.bedrockSecretAccessKey)) {\n message.error(t('settings.bedrock.fillRequiredFields'));\n return;\n }\n if (values.bedrockAuthMethod === 'profile' && !values.bedrockProfile) {\n message.error(t('settings.bedrock.fillRequiredFields'));\n return;\n }\n // Build bedrockConfig and fetch models manually\n const bedrockConfig = {\n authMethod: values.bedrockAuthMethod,\n region: values.bedrockRegion,\n ...(values.bedrockAuthMethod === 'accessKey'\n ? {\n accessKeyId: values.bedrockAccessKeyId,\n secretAccessKey: values.bedrockSecretAccessKey,\n }\n : {\n profile: values.bedrockProfile,\n }),\n };\n try {\n const res = await ipcBridge.mode.fetchModelList.invoke({\n platform: data?.platform || 'bedrock',\n api_key: '',\n bedrockConfig,\n });\n if (res.success) {\n const models =\n res.data?.mode.map((v: any) => {\n if (typeof v === 'string') {\n return { label: v, value: v };\n } else {\n return { label: v.name, value: v.id };\n }\n }) || [];\n // Update the model list state manually\n void modelListState.mutate({ models }, false);\n } else {\n message.error(res.msg || 'Failed to fetch models');\n }\n } catch (error: any) {\n message.error(error.message || 'Failed to fetch models');\n }\n return;\n }\n void modelListState.mutate();\n }}\n options={modelListState.data?.models || []}\n />\n </Form.Item>\n </Form>\n </div>\n </AionModal>\n );\n});\n\nexport default EditModeModal;\n"}}}} |