返回 oh-my-ppt
ModelSettingsTab.tsx
根目录 / src / renderer / src / components / settings / ModelSettingsTab.tsx
1 import { CheckCircle2, CircleHelp, Pencil, Plus, Trash2 } from 'lucide-react'
2 import type { ModelConfig } from '../../lib/ipc'
3 import { Button } from '../ui/Button'
4 import { Card, CardContent, CardHeader, CardTitle } from '../ui/Card'
5 import { Popover, PopoverContent, PopoverTrigger } from '../ui/Popover'
6 import type { SettingsTranslate } from './types'
7
8 const MODEL_PROVIDER_LINKS = [
9 { label: 'DeepSeek', url: 'https://platform.deepseek.com' },
10 { label: 'Moonshot (Kimi)', url: 'https://platform.moonshot.cn' },
11 { label: 'GLM (智谱)', url: 'https://open.bigmodel.cn' },
12 { label: 'Qwen (通义千问)', url: 'https://bailian.console.aliyun.com/' },
13 { label: 'Doubao (豆包)', url: 'https://console.volcengine.com/ark' },
14 { label: 'Mimo (小米)', url: 'https://platform.xiaomimimo.com' },
15 { label: 'MiniMax', url: 'https://www.minimaxi.com/' },
16 { label: 'OpenAI', url: 'https://platform.openai.com' },
17 { label: 'Claude (Anthropic)', url: 'https://console.anthropic.com' },
18 { label: 'Google Gemini', url: 'https://ai.google.dev' }
19 ]
20
21 interface ModelSettingsTabProps {
22 activeModelConfig?: ModelConfig
23 activatingId: string | null
24 deletingId: string | null
25 modelConfigs: ModelConfig[]
26 t: SettingsTranslate
27 onActivate: (id: string) => void
28 onCreate: () => void
29 onDelete: (config: ModelConfig) => void
30 onEdit: (config: ModelConfig) => void
31 }
32
33 export function ModelSettingsTab({
34 activeModelConfig,
35 activatingId,
36 deletingId,
37 modelConfigs,
38 t,
39 onActivate,
40 onCreate,
41 onDelete,
42 onEdit
43 }: ModelSettingsTabProps): React.JSX.Element {
44 return (
45 <Card className="mb-4">
46 <CardHeader className="flex-row items-center justify-between p-5 pb-3">
47 <div>
48 <CardTitle className="flex items-center gap-1.5 text-base">
49 {t('settings.modelAccess')}
50 <Popover>
51 <PopoverTrigger asChild>
52 <CircleHelp className="h-3.5 w-3.5 cursor-pointer text-muted-foreground/50 transition-colors hover:text-foreground" />
53 </PopoverTrigger>
54 <PopoverContent
55 side="bottom"
56 align="start"
57 className="w-auto max-w-xs border-[#d8cfbc]/80 bg-[#fffdf8] p-3"
58 >
59 <p className="mb-2 text-[11px] font-semibold text-[#3e4a32]">
60 {t('settings.modelHelpTitle')}
61 </p>
62 <div className="flex flex-wrap gap-1.5">
63 {MODEL_PROVIDER_LINKS.map((item) => (
64 <a
65 key={item.url}
66 href={item.url}
67 target="_blank"
68 rel="noopener noreferrer"
69 className="rounded-md border border-[#d8cfbc]/80 bg-[#f5efe2]/60 px-2 py-1 text-[11px] text-[#5b6b4d] transition-colors hover:border-[#96b77f]/60 hover:bg-[#e8f0de] hover:text-[#3e4a32]"
70 >
71 {item.label}
72 </a>
73 ))}
74 </div>
75 </PopoverContent>
76 </Popover>
77 <a
78 href="https://www.ohmyppt.cc/#/model-doc"
79 target="_blank"
80 rel="noopener noreferrer"
81 className="ml-1 text-xs font-medium text-[#2f7f8f] underline-offset-4 transition-colors hover:text-[#1f6472] hover:underline"
82 >
83 {t('settings.modelHelpDocs')}
84 </a>
85 </CardTitle>
86 {activeModelConfig && (
87 <span className="mt-2 inline-flex max-w-full truncate rounded-full border border-[#96b77f]/55 bg-[#eef6e8] px-2.5 py-1 text-xs font-medium text-[#4f6b45]">
88 {t('settings.currentActiveModel', { name: activeModelConfig.name })}
89 </span>
90 )}
91 </div>
92 <Button size="sm" onClick={onCreate}>
93 <Plus className="mr-1.5 h-4 w-4" />
94 {t('settings.addModel')}
95 </Button>
96 </CardHeader>
97 <CardContent className="space-y-2.5 p-5 pt-0">
98 {modelConfigs.length === 0 ? (
99 <div className="rounded-lg border border-dashed border-[#d8ccb5]/85 bg-[#fff9ef]/70 p-6 text-sm text-muted-foreground">
100 {t('settings.noModels')}
101 </div>
102 ) : (
103 modelConfigs.map((config) => (
104 <div
105 key={config.id}
106 className={
107 config.active
108 ? 'flex flex-col gap-3 rounded-lg border border-[#96b77f]/80 bg-[#eef6e8] p-3 shadow-[inset_3px_0_0_#6f8f64] sm:flex-row sm:items-center sm:justify-between'
109 : 'flex flex-col gap-3 rounded-lg border border-[#d8ccb5]/80 bg-[#fffdf8]/78 p-3 sm:flex-row sm:items-center sm:justify-between'
110 }
111 >
112 <div className="min-w-0">
113 <div className="flex flex-wrap items-center gap-2">
114 {config.active && <CheckCircle2 className="h-4 w-4 text-[#5d7b4d]" />}
115 <p className="font-medium text-[#33402a]">{config.name}</p>
116 <span className="rounded-full bg-[#e9efde] px-2 py-0.5 text-[11px] uppercase text-[#506141]">
117 {config.provider}
118 </span>
119 </div>
120 <p className="mt-1 truncate text-xs text-muted-foreground">{config.model}</p>
121 {config.baseUrl && (
122 <p className="mt-0.5 truncate text-xs text-muted-foreground">{config.baseUrl}</p>
123 )}
124 </div>
125 <div className="flex shrink-0 flex-wrap gap-2">
126 <Button
127 size="sm"
128 variant={config.active ? 'secondary' : 'outline'}
129 disabled={config.active || activatingId === config.id}
130 onClick={() => onActivate(config.id)}
131 >
132 {config.active ? t('settings.activeModel') : t('settings.activateModel')}
133 </Button>
134 <Button size="sm" variant="ghost" onClick={() => onEdit(config)}>
135 <Pencil className="h-4 w-4" />
136 </Button>
137 <Button
138 size="sm"
139 variant="ghost"
140 disabled={deletingId === config.id}
141 onClick={() => onDelete(config)}
142 >
143 <Trash2 className="h-4 w-4" />
144 </Button>
145 </div>
146 </div>
147 ))
148 )}
149 </CardContent>
150 </Card>
151 )
152 }
153
153 lines Plain Text