Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions src/renderer/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ import ProxySection from './settings/ProxySection'
import MCPServerSection from './settings/MCPServerSection'
import MitmProxySection from './settings/MitmProxySection'
import FingerprintSection from './settings/FingerprintSection'
import { useLocale, type LocaleKey } from '../i18n'

type SettingsSection = 'general' | 'llm' | 'proxy' | 'mcp-server' | 'mitm-proxy' | 'fingerprint'

const menuItems: { key: SettingsSection; icon: React.FC<{ size?: number | string }>; label: string }[] = [
{ key: 'general', icon: IconApp, label: '通用' },
{ key: 'llm', icon: IconRobot, label: 'LLM' },
{ key: 'proxy', icon: IconGlobe, label: '代理' },
{ key: 'mcp-server', icon: IconBolt, label: 'MCP Server' },
{ key: 'mitm-proxy', icon: IconShield, label: 'MITM 代理' },
{ key: 'fingerprint', icon: IconCode, label: '指纹' },
const menuItems: { key: SettingsSection; icon: React.FC<{ size?: number | string }>; labelKey: LocaleKey }[] = [
{ key: 'general', icon: IconApp, labelKey: 'settings.general' },
{ key: 'llm', icon: IconRobot, labelKey: 'settings.llm' },
{ key: 'proxy', icon: IconGlobe, labelKey: 'settings.proxy' },
{ key: 'mcp-server', icon: IconBolt, labelKey: 'settings.mcpServer' },
{ key: 'mitm-proxy', icon: IconShield, labelKey: 'settings.mitmProxy' },
{ key: 'fingerprint', icon: IconCode, labelKey: 'settings.fingerprint' },
]

const sectionComponents: Record<SettingsSection, React.ComponentType> = {
Expand All @@ -31,13 +32,14 @@ const sectionComponents: Record<SettingsSection, React.ComponentType> = {
interface Props { open: boolean; onClose: () => void; currentSessionId?: string | null }

export default function SettingsModal({ open, onClose, currentSessionId }: Props) {
const { t } = useLocale()
const [activeSection, setActiveSection] = useState<SettingsSection>('general')

const ActiveComponent = sectionComponents[activeSection]

return (
<Modal
title="Settings"
title={t('settings.title')}
open={open}
onClose={onClose}
width={900}
Expand Down Expand Up @@ -81,7 +83,7 @@ export default function SettingsModal({ open, onClose, currentSessionId }: Props
}}
>
<Icon size={16} />
<span style={{ color: 'inherit', fontSize: 'inherit' }}>{item.label}</span>
<span style={{ color: 'inherit', fontSize: 'inherit' }}>{t(item.labelKey)}</span>
</div>
)
})}
Expand Down
34 changes: 18 additions & 16 deletions src/renderer/components/settings/GeneralSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import {
import type { UpdateStatus } from '@shared/types'
import PromptTemplateModal from '../PromptTemplateModal'
import MCPServerModal from '../MCPServerModal'
import { useLocale } from '../../i18n'

export default function GeneralSection() {
const { t } = useLocale()
const [appVersion, setAppVersion] = useState('')
const [updateStatus, setUpdateStatus] = useState<UpdateStatus>({ state: 'idle' })
const [templateModalOpen, setTemplateModalOpen] = useState(false)
Expand Down Expand Up @@ -79,41 +81,41 @@ export default function GeneralSection() {
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4, flexWrap: 'wrap' }}>
{updateStatus.state === 'idle' && (
<Button size="sm" icon={<IconSync size={14} />} onClick={handleCheckUpdate}>
检查更新
{t('settings.checkForUpdates')}
</Button>
)}
{updateStatus.state === 'checking' && (
<Button size="sm" icon={<IconLoading size={14} />} disabled>
正在检查...
{t('settings.checkingUpdates')}
</Button>
)}
{updateStatus.state === 'not-available' && (
<>
<IconCheckCircle size={14} style={{ color: 'var(--color-success)' }} />
<span style={{ fontSize: 'var(--font-size-base)' }}>已是最新版本</span>
<Button size="sm" onClick={handleCheckUpdate}>重新检查</Button>
<span style={{ fontSize: 'var(--font-size-base)' }}>{t('settings.upToDate')}</span>
<Button size="sm" onClick={handleCheckUpdate}>{t('settings.checkAgain')}</Button>
</>
)}
{updateStatus.state === 'available' && (
<>
<Tag color="info">v{updateStatus.info?.version} 可用</Tag>
<span style={{ color: 'var(--text-secondary)', fontSize: 'var(--font-size-base)' }}>正在下载...</span>
<Tag color="info">{t('settings.versionAvailable', { version: updateStatus.info?.version ?? '' })}</Tag>
<span style={{ color: 'var(--text-secondary)', fontSize: 'var(--font-size-base)' }}>{t('settings.downloading')}</span>
</>
)}
{updateStatus.state === 'downloaded' && (
<>
<IconCloudDownload size={14} style={{ color: 'var(--color-accent)' }} />
<span style={{ fontSize: 'var(--font-size-base)' }}>v{updateStatus.info?.version} 已就绪</span>
<span style={{ fontSize: 'var(--font-size-base)' }}>{t('settings.versionReady', { version: updateStatus.info?.version ?? '' })}</span>
<Button variant="primary" size="sm" onClick={handleInstallUpdate}>
立即重启更新
{t('settings.restartToUpdate')}
</Button>
</>
)}
{updateStatus.state === 'error' && (
<>
<IconCloseCircle size={14} style={{ color: 'var(--color-error)' }} />
<span style={{ color: 'var(--color-error)', fontSize: 'var(--font-size-sm)' }}>{updateStatus.error}</span>
<Button size="sm" onClick={handleCheckUpdate}>重试</Button>
<Button size="sm" onClick={handleCheckUpdate}>{t('settings.retry')}</Button>
</>
)}
</div>
Expand All @@ -127,32 +129,32 @@ export default function GeneralSection() {

<div style={{ marginTop: 24 }}>
<div style={{ fontSize: 'var(--font-size-base)', fontWeight: 600, marginBottom: 12, color: 'var(--text-primary)' }}>
管理工具
{t('settings.managementTools')}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: '100%' }}>
<Button icon={<IconEdit size={14} />} block onClick={() => setTemplateModalOpen(true)}>
管理提示词模板
{t('settings.managePromptTemplates')}
</Button>
<Button icon={<IconApi size={14} />} block onClick={() => setMcpModalOpen(true)}>
管理 MCP 服务器
{t('settings.manageMcpServers')}
</Button>
</div>
</div>

{/* Error Logs */}
<div style={{ marginTop: 24 }}>
<div style={{ fontSize: 'var(--font-size-base)', fontWeight: 600, marginBottom: 12, color: 'var(--text-primary)' }}>
错误日志
{t('settings.errorLogs')}
</div>
<div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--text-secondary)', marginBottom: 8 }}>
遇到问题时可导出日志文件发送给开发者以便排查
{t('settings.errorLogsHelp')}
</div>
<div style={{ display: 'flex', gap: 8 }}>
<Button icon={<IconFileText size={14} />} onClick={() => window.electronAPI.openLogFolder()}>
打开日志目录
{t('settings.openLogFolder')}
</Button>
<Button icon={<IconExport size={14} />} onClick={() => window.electronAPI.exportLogs()}>
导出日志
{t('settings.exportLogs')}
</Button>
</div>
</div>
Expand Down
10 changes: 6 additions & 4 deletions src/renderer/components/settings/LLMSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import { Input, PasswordInput, Select, InputNumber, Button, useToast } from '../../ui'
import type { LLMProviderConfig, LLMProviderType, OpenAIApiType } from '@shared/types'
import { useLocale } from '../../i18n'

const defaultUrls: Record<LLMProviderType, string> = {
openai: 'https://api.openai.com/v1',
Expand All @@ -22,6 +23,7 @@ const fieldStyle: React.CSSProperties = {

export default function LLMSection() {
const toast = useToast()
const { t } = useLocale()
const [name, setName] = useState<LLMProviderType>('openai')
const [apiType, setApiType] = useState<OpenAIApiType | undefined>('completions')
const [baseUrl, setBaseUrl] = useState(defaultUrls.openai)
Expand Down Expand Up @@ -57,7 +59,7 @@ export default function LLMSection() {

const handleSave = async () => {
if (!baseUrl || !apiKey || !model) {
toast.warning('请填写必填项(Base URL、API Key、Model)')
toast.warning(t('settings.llmRequiredFields'))
return
}
const config: LLMProviderConfig = {
Expand All @@ -69,7 +71,7 @@ export default function LLMSection() {
...(showApiType && apiType ? { apiType } : {}),
}
await window.electronAPI.saveLLMConfig(config)
toast.success('LLM 配置已保存')
toast.success(t('settings.llmSaved'))
}

return (
Expand All @@ -83,7 +85,7 @@ export default function LLMSection() {
{ label: 'OpenAI', value: 'openai' },
{ label: 'Anthropic', value: 'anthropic' },
{ label: 'MiniMax', value: 'minimax' },
{ label: 'Custom (OpenAI Compatible)', value: 'custom' },
{ label: t('settings.customOpenAICompatible'), value: 'custom' },
]}
/>
</div>
Expand Down Expand Up @@ -141,7 +143,7 @@ export default function LLMSection() {
</div>

<Button variant="primary" block onClick={handleSave}>
保存 LLM 配置
{t('settings.saveLlmConfig')}
</Button>
</div>
)
Expand Down
20 changes: 10 additions & 10 deletions src/renderer/components/settings/MCPServerSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,20 @@ export default function MCPServerSection() {
<div>
<Badge
color={running ? 'var(--color-success)' : 'var(--text-muted)'}
label={running ? '运行中' : '已停止'}
label={running ? t('settings.running') : t('settings.stopped')}
style={{ fontSize: 'var(--font-size-sm)' }}
/>
</div>

<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span style={{ fontSize: 'var(--font-size-base)' }}>启用 MCP Server</span>
<span style={{ fontSize: 'var(--font-size-base)' }}>{t('settings.enableMcpServer')}</span>
<Switch checked={enabled} onChange={setEnabled} />
</div>

{enabled && (
<>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontSize: 'var(--font-size-base)' }}>端口</span>
<span style={{ fontSize: 'var(--font-size-base)' }}>{t('settings.port')}</span>
<InputNumber
min={1024}
max={65535}
Expand All @@ -76,7 +76,7 @@ export default function MCPServerSection() {
/>
</div>
<div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--text-secondary)' }}>
外部工具配置 URL:{' '}
{t('settings.externalToolConfigUrl')}{' '}
<code style={{
background: 'var(--color-surface)',
padding: '2px 6px',
Expand All @@ -91,7 +91,7 @@ export default function MCPServerSection() {
{/* Auth section */}
<div style={{ borderTop: '1px solid var(--color-border)', paddingTop: 12, marginTop: 4 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span style={{ fontSize: 'var(--font-size-base)' }}>鉴权</span>
<span style={{ fontSize: 'var(--font-size-base)' }}>{t('settings.auth')}</span>
<Switch checked={authEnabled} onChange={setAuthEnabled} />
</div>

Expand All @@ -105,11 +105,11 @@ export default function MCPServerSection() {
onBlur={() => setTokenVisible(false)}
style={{ flex: 1, fontFamily: 'var(--font-mono)', fontSize: 'var(--font-size-2xs)' }}
/>
<Button onClick={copyToken} style={btnStyle}>复制</Button>
<Button onClick={regenerateToken} style={btnStyle}>重新生成</Button>
<Button onClick={copyToken} style={btnStyle}>{t('settings.copy')}</Button>
<Button onClick={regenerateToken} style={btnStyle}>{t('settings.regenerate')}</Button>
</div>
<div style={{ fontSize: 'var(--font-size-2xs)', color: 'var(--text-muted)', lineHeight: 1.5 }}>
外部工具需在请求头中设置: <code style={{
{t('settings.externalToolAuthHeader')} <code style={{
background: 'var(--color-surface)',
padding: '1px 4px',
borderRadius: 3,
Expand All @@ -125,11 +125,11 @@ export default function MCPServerSection() {
<Button variant="primary" block onClick={async () => {
const config: MCPServerSettings = { enabled, port, authEnabled, authToken }
await window.electronAPI.saveMCPServerConfig(config)
toast.success('MCP Server 配置已保存')
toast.success(t('settings.mcpServerSaved'))
const status = await window.electronAPI.getMCPServerStatus()
setRunning(status.running)
}}>
保存 MCP Server 设置
{t('settings.saveMcpServerSettings')}
</Button>
</div>
)
Expand Down
Loading