mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
wip
This commit is contained in:
parent
4186e9c990
commit
1bf5104f97
@ -7,7 +7,6 @@ This file provides guidance to AI coding assistants when working with code in th
|
||||
- **Keep it clear**: Write code that is easy to read, maintain, and explain.
|
||||
- **Match the house style**: Reuse existing patterns, naming, and conventions.
|
||||
- **Search smart**: Prefer `ast-grep` for semantic queries; fall back to `rg`/`grep` when needed.
|
||||
- **Build with HeroUI**: Use HeroUI for every new UI component; never add `antd` or `styled-components`.
|
||||
- **Log centrally**: Route all logging through `loggerService` with the right context—no `console.log`.
|
||||
- **Research via subagent**: Lean on `subagent` for external docs, APIs, news, and references.
|
||||
- **Always propose before executing**: Before making any changes, clearly explain your planned approach and wait for explicit user approval to ensure alignment and prevent unwanted modifications.
|
||||
@ -41,7 +40,6 @@ This file provides guidance to AI coding assistants when working with code in th
|
||||
- **Services** (`src/main/services/`): MCPService, KnowledgeService, WindowService, etc.
|
||||
- **Build System**: Electron-Vite with experimental rolldown-vite, yarn workspaces.
|
||||
- **State Management**: Redux Toolkit (`src/renderer/src/store/`) for predictable state.
|
||||
- **UI Components**: HeroUI (`@heroui/*`) for all new UI elements.
|
||||
|
||||
### Logging
|
||||
```typescript
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button } from '@heroui/button'
|
||||
import { Button } from 'antd'
|
||||
import { formatErrorMessage } from '@renderer/utils/error'
|
||||
import { Alert, Space } from 'antd'
|
||||
import type { ComponentType, ReactNode } from 'react'
|
||||
@ -24,10 +24,10 @@ const DefaultFallback: ComponentType<FallbackProps> = (props: FallbackProps): Re
|
||||
type="error"
|
||||
action={
|
||||
<Space>
|
||||
<Button size="sm" onPress={debug}>
|
||||
<Button size="small" onClick={debug}>
|
||||
{t('error.boundary.default.devtools')}
|
||||
</Button>
|
||||
<Button size="sm" onPress={reload}>
|
||||
<Button size="small" onClick={reload}>
|
||||
{t('error.boundary.default.reload')}
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Select, SelectItem } from '@heroui/react'
|
||||
import { Select } from 'antd'
|
||||
import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar'
|
||||
import { getProviderLogo } from '@renderer/config/providers'
|
||||
import ImageStorage from '@renderer/services/ImageStorage'
|
||||
@ -54,46 +54,46 @@ const ProviderSelect: FC<ProviderSelectProps> = ({ provider, options, onChange,
|
||||
|
||||
return (
|
||||
<Select
|
||||
selectedKeys={[provider.id]}
|
||||
onSelectionChange={(keys) => {
|
||||
const selectedKey = Array.from(keys)[0] as string
|
||||
onChange(selectedKey)
|
||||
}}
|
||||
style={style}
|
||||
className={`w-full ${className || ''}`}
|
||||
renderValue={(items) => {
|
||||
return items.map((item) => (
|
||||
<div key={item.key} className="flex items-center gap-2">
|
||||
value={provider.id}
|
||||
onChange={onChange}
|
||||
style={{ width: '100%', ...style }}
|
||||
className={className}
|
||||
options={providerOptions}
|
||||
labelRender={(props) => {
|
||||
const providerId = props.value as string
|
||||
const providerName = providerOptions.find((opt) => opt.value === providerId)?.label || ''
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-4 w-4 items-center justify-center">
|
||||
<ProviderAvatarPrimitive
|
||||
providerId={item.key as string}
|
||||
providerName={item.textValue || ''}
|
||||
logoSrc={getProviderLogoSrc(item.key as string)}
|
||||
providerId={providerId}
|
||||
providerName={providerName}
|
||||
logoSrc={getProviderLogoSrc(providerId)}
|
||||
size={16}
|
||||
/>
|
||||
</div>
|
||||
<span>{item.textValue}</span>
|
||||
<span>{providerName}</span>
|
||||
</div>
|
||||
))
|
||||
}}>
|
||||
{providerOptions.map((providerOption) => (
|
||||
<SelectItem
|
||||
key={providerOption.value}
|
||||
textValue={providerOption.label}
|
||||
startContent={
|
||||
)
|
||||
}}
|
||||
optionRender={(option) => {
|
||||
const providerId = option.value as string
|
||||
const providerName = option.label as string
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-4 w-4 items-center justify-center">
|
||||
<ProviderAvatarPrimitive
|
||||
providerId={providerOption.value}
|
||||
providerName={providerOption.label}
|
||||
logoSrc={getProviderLogoSrc(providerOption.value)}
|
||||
providerId={providerId}
|
||||
providerName={providerName}
|
||||
logoSrc={getProviderLogoSrc(providerId)}
|
||||
size={16}
|
||||
/>
|
||||
</div>
|
||||
}>
|
||||
{providerOption.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
<span>{providerName}</span>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button, Tooltip } from '@heroui/react'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import { loggerService } from '@logger'
|
||||
import type { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types'
|
||||
import { Plus } from 'lucide-react'
|
||||
@ -65,21 +65,21 @@ export const AccessibleDirsSetting = ({ base, update }: AccessibleDirsSettingPro
|
||||
<SettingsItem>
|
||||
<SettingsTitle
|
||||
actions={
|
||||
<Tooltip content={t('agent.session.accessible_paths.add')}>
|
||||
<Button variant="light" size="sm" startContent={<Plus />} isIconOnly onPress={addAccessiblePath} />
|
||||
<Tooltip title={t('agent.session.accessible_paths.add')}>
|
||||
<Button type="text" icon={<Plus size={16} />} shape="circle" onClick={addAccessiblePath} />
|
||||
</Tooltip>
|
||||
}>
|
||||
{t('agent.session.accessible_paths.label')}
|
||||
</SettingsTitle>
|
||||
<ul className="flex flex-col gap-2">
|
||||
<ul className="flex flex-col">
|
||||
{base.accessible_paths.map((path) => (
|
||||
<li
|
||||
key={path}
|
||||
className="flex items-center justify-between gap-2 rounded-medium border border-default-200 px-2 py-1">
|
||||
<span className="w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm" title={path}>
|
||||
<li key={path} className="flex items-center justify-between gap-2 py-1">
|
||||
<span
|
||||
className="w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-[var(--color-text-2)] text-sm"
|
||||
title={path}>
|
||||
{path}
|
||||
</span>
|
||||
<Button size="sm" variant="light" color="danger" onPress={() => removeAccessiblePath(path)}>
|
||||
<Button size="small" type="text" danger onClick={() => removeAccessiblePath(path)}>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</li>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Input, Tooltip } from '@heroui/react'
|
||||
import { InputNumber, Tooltip } from 'antd'
|
||||
import type { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||
import type { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||
import type {
|
||||
@ -31,34 +31,33 @@ const defaultConfiguration: AgentConfigurationState = AgentConfigurationSchema.p
|
||||
export const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ agentBase, update }) => {
|
||||
const { t } = useTranslation()
|
||||
const [configuration, setConfiguration] = useState<AgentConfigurationState>(defaultConfiguration)
|
||||
const [maxTurnsInput, setMaxTurnsInput] = useState<string>(String(defaultConfiguration.max_turns))
|
||||
const [maxTurnsInput, setMaxTurnsInput] = useState<number>(defaultConfiguration.max_turns)
|
||||
|
||||
useEffect(() => {
|
||||
if (!agentBase) {
|
||||
setConfiguration(defaultConfiguration)
|
||||
setMaxTurnsInput(String(defaultConfiguration.max_turns))
|
||||
setMaxTurnsInput(defaultConfiguration.max_turns)
|
||||
return
|
||||
}
|
||||
const parsed: AgentConfigurationState = AgentConfigurationSchema.parse(agentBase.configuration ?? {})
|
||||
setConfiguration(parsed)
|
||||
setMaxTurnsInput(String(parsed.max_turns))
|
||||
setMaxTurnsInput(parsed.max_turns)
|
||||
}, [agentBase])
|
||||
|
||||
const commitMaxTurns = useCallback(() => {
|
||||
if (!agentBase) return
|
||||
const parsedValue = Number.parseInt(maxTurnsInput, 10)
|
||||
if (!Number.isFinite(parsedValue)) {
|
||||
setMaxTurnsInput(String(configuration.max_turns))
|
||||
if (!Number.isFinite(maxTurnsInput)) {
|
||||
setMaxTurnsInput(configuration.max_turns)
|
||||
return
|
||||
}
|
||||
const sanitized = Math.max(1, parsedValue)
|
||||
const sanitized = Math.max(1, maxTurnsInput)
|
||||
if (sanitized === configuration.max_turns) {
|
||||
setMaxTurnsInput(String(configuration.max_turns))
|
||||
setMaxTurnsInput(configuration.max_turns)
|
||||
return
|
||||
}
|
||||
const next: AgentConfigurationState = { ...configuration, max_turns: sanitized }
|
||||
setConfiguration(next)
|
||||
setMaxTurnsInput(String(sanitized))
|
||||
setMaxTurnsInput(sanitized)
|
||||
update({ id: agentBase.id, configuration: next } satisfies UpdateAgentBaseForm)
|
||||
}, [agentBase, configuration, maxTurnsInput, update])
|
||||
|
||||
@ -71,27 +70,23 @@ export const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ agentBase, u
|
||||
<SettingsItem divider={false}>
|
||||
<SettingsTitle
|
||||
actions={
|
||||
<Tooltip content={t('agent.settings.advance.maxTurns.description')} placement="right">
|
||||
<Tooltip title={t('agent.settings.advance.maxTurns.description')} placement="left">
|
||||
<Info size={16} className="text-foreground-400" />
|
||||
</Tooltip>
|
||||
}>
|
||||
{t('agent.settings.advance.maxTurns.label')}
|
||||
</SettingsTitle>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
<div className="my-2 flex w-full flex-col gap-2">
|
||||
<InputNumber
|
||||
min={1}
|
||||
value={maxTurnsInput}
|
||||
onValueChange={setMaxTurnsInput}
|
||||
onChange={(value) => setMaxTurnsInput(value ?? 1)}
|
||||
onBlur={commitMaxTurns}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
commitMaxTurns()
|
||||
}
|
||||
}}
|
||||
onPressEnter={commitMaxTurns}
|
||||
aria-label={t('agent.settings.advance.maxTurns.label')}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<span className="text-foreground-500 text-xs">{t('agent.settings.advance.maxTurns.helper')}</span>
|
||||
<span className="mt-1 text-foreground-500 text-xs">{t('agent.settings.advance.maxTurns.helper')}</span>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
</SettingsContainer>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { Alert, Spinner } from '@heroui/react'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { useAgent } from '@renderer/hooks/agents/useAgent'
|
||||
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||
@ -11,6 +10,8 @@ import PluginSettings from './PluginSettings'
|
||||
import PromptSettings from './PromptSettings'
|
||||
import { AgentLabel, LeftMenu, Settings, StyledMenu, StyledModal } from './shared'
|
||||
import ToolingSettings from './ToolingSettings'
|
||||
import { Center } from '@renderer/components/Layout'
|
||||
import { Alert, Spin } from 'antd'
|
||||
|
||||
interface AgentSettingPopupShowParams {
|
||||
agentId: string
|
||||
@ -71,18 +72,25 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
|
||||
const ModalContent = () => {
|
||||
if (isLoading) {
|
||||
// TODO: use skeleton for better ux
|
||||
return <Spinner />
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<Alert color="danger" title={t('agent.get.error.failed')} />
|
||||
</div>
|
||||
<Center flex={1}>
|
||||
<Spin />
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Center flex={1}>
|
||||
<Alert type="error" message={t('agent.get.error.failed')} />
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
if (!agent) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-1">
|
||||
<LeftMenu>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Textarea } from '@heroui/react'
|
||||
import type { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingsItem, SettingsTitle } from './shared'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
|
||||
export interface DescriptionSettingProps {
|
||||
base: AgentBaseWithId | undefined | null
|
||||
@ -24,11 +24,12 @@ export const DescriptionSetting = ({ base, update }: DescriptionSettingProps) =>
|
||||
if (!base) return null
|
||||
|
||||
return (
|
||||
<SettingsItem>
|
||||
<SettingsItem divider={false}>
|
||||
<SettingsTitle>{t('common.description')}</SettingsTitle>
|
||||
<Textarea
|
||||
<TextArea
|
||||
value={description}
|
||||
onValueChange={setDescription}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
rows={4}
|
||||
onBlur={() => {
|
||||
if (description !== base.description) {
|
||||
updateDesc(description)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Input } from '@heroui/react'
|
||||
import { Input } from 'antd'
|
||||
import type { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -25,14 +25,13 @@ export const NameSetting = ({ base, update }: NameSettingsProps) => {
|
||||
<Input
|
||||
placeholder={t('common.agent_one') + t('common.name')}
|
||||
value={name}
|
||||
size="sm"
|
||||
onValueChange={(value) => setName(value)}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onBlur={() => {
|
||||
if (name !== base.name) {
|
||||
updateName(name)
|
||||
}
|
||||
}}
|
||||
className="max-w-80 flex-1"
|
||||
className="max-w-70 flex-1"
|
||||
/>
|
||||
</SettingsItem>
|
||||
)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Alert, Spinner } from '@heroui/react'
|
||||
import { Alert, Spin } from 'antd'
|
||||
import { Center } from '@renderer/components/Layout'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { useSession } from '@renderer/hooks/agents/useSession'
|
||||
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||
@ -68,15 +69,21 @@ const SessionSettingPopupContainer: React.FC<SessionSettingPopupParams> = ({ tab
|
||||
const ModalContent = () => {
|
||||
if (isLoading) {
|
||||
// TODO: use skeleton for better ux
|
||||
return <Spinner />
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<Alert color="danger" title={t('agent.get.error.failed')} />
|
||||
</div>
|
||||
<Center flex={1}>
|
||||
<Spin />
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Center flex={1}>
|
||||
<Alert type="error" message={t('agent.get.error.failed')} />
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-1">
|
||||
<LeftMenu>
|
||||
|
||||
@ -6,8 +6,6 @@ import {
|
||||
WifiOutlined,
|
||||
YuqueOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { Button } from '@heroui/button'
|
||||
import { Switch } from '@heroui/switch'
|
||||
import DividerWithText from '@renderer/components/DividerWithText'
|
||||
import { NutstoreIcon } from '@renderer/components/Icons/NutstoreIcons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
@ -24,7 +22,7 @@ import { setSkipBackupFile as _setSkipBackupFile } from '@renderer/store/setting
|
||||
import type { AppInfo } from '@renderer/types'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
import { occupiedDirs } from '@shared/config/constant'
|
||||
import { Progress, Typography } from 'antd'
|
||||
import { Button, Progress, Switch, Typography } from 'antd'
|
||||
import { FileText, FolderCog, FolderInput, FolderOpen, SaveIcon, Sparkle } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
@ -295,16 +293,11 @@ const DataSettings: FC = () => {
|
||||
<div>
|
||||
<MigrationPathRow style={{ marginTop: '20px', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Switch
|
||||
defaultSelected={shouldCopyData}
|
||||
onValueChange={(checked) => {
|
||||
shouldCopyData = checked
|
||||
}}
|
||||
size="sm">
|
||||
<span style={{ fontWeight: 'normal', fontSize: '14px' }}>
|
||||
{t('settings.data.app_data.copy_data_option')}
|
||||
</span>
|
||||
</Switch>
|
||||
|
||||
defaultChecked={shouldCopyData}
|
||||
onChange={(checked) => (shouldCopyData = checked)}
|
||||
style={{ marginRight: '8px' }}
|
||||
title={t('settings.data.app_data.copy_data_option')}
|
||||
/>
|
||||
<MigrationPathLabel style={{ fontWeight: 'normal', fontSize: '14px' }}>
|
||||
{t('settings.data.app_data.copy_data_option')}
|
||||
</MigrationPathLabel>
|
||||
@ -614,10 +607,10 @@ const DataSettings: FC = () => {
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
|
||||
<HStack gap="5px" justifyContent="space-between">
|
||||
<Button variant="ghost" size="sm" onPress={BackupPopup.show} startContent={<SaveIcon size={14} />}>
|
||||
<Button onClick={BackupPopup.show} icon={<SaveIcon size={14} />}>
|
||||
{t('settings.general.backup.button')}
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onPress={RestorePopup.show} startContent={<FolderOpen size={14} />}>
|
||||
<Button onClick={RestorePopup.show} icon={<FolderOpen size={14} />}>
|
||||
{t('settings.general.restore.button')}
|
||||
</Button>
|
||||
</HStack>
|
||||
@ -625,7 +618,7 @@ const DataSettings: FC = () => {
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.data.backup.skip_file_data_title')}</SettingRowTitle>
|
||||
<Switch isSelected={skipBackupFile} onValueChange={onSkipBackupFilesChange} size="sm" />
|
||||
<Switch checked={skipBackupFile} onChange={onSkipBackupFilesChange} />
|
||||
</SettingRow>
|
||||
<SettingRow>
|
||||
<SettingHelpText>{t('settings.data.backup.skip_file_data_help')}</SettingHelpText>
|
||||
@ -634,11 +627,7 @@ const DataSettings: FC = () => {
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.data.export_to_phone.title')}</SettingRowTitle>
|
||||
<HStack gap="5px" justifyContent="space-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onPress={ExportToPhoneLanPopup.show}
|
||||
startContent={<WifiOutlined />}>
|
||||
<Button onClick={ExportToPhoneLanPopup.show} icon={<WifiOutlined size={14} />}>
|
||||
{t('settings.data.export_to_phone.lan.title')}
|
||||
</Button>
|
||||
</HStack>
|
||||
@ -657,9 +646,7 @@ const DataSettings: FC = () => {
|
||||
</PathText>
|
||||
<StyledIcon onClick={() => handleOpenPath(appInfo?.appDataPath)} style={{ flexShrink: 0 }} />
|
||||
<HStack gap="5px" style={{ marginLeft: '8px' }}>
|
||||
<Button variant="ghost" size="sm" onClick={handleSelectAppDataPath}>
|
||||
{t('settings.data.app_data.select')}
|
||||
</Button>
|
||||
<Button onClick={handleSelectAppDataPath}>{t('settings.data.app_data.select')}</Button>
|
||||
</HStack>
|
||||
</PathRow>
|
||||
</SettingRow>
|
||||
@ -672,7 +659,7 @@ const DataSettings: FC = () => {
|
||||
</PathText>
|
||||
<StyledIcon onClick={() => handleOpenPath(appInfo?.logsPath)} style={{ flexShrink: 0 }} />
|
||||
<HStack gap="5px" style={{ marginLeft: '8px' }}>
|
||||
<Button variant="ghost" size="sm" onClick={() => handleOpenPath(appInfo?.logsPath)}>
|
||||
<Button onClick={() => handleOpenPath(appInfo?.logsPath)}>
|
||||
{t('settings.data.app_logs.button')}
|
||||
</Button>
|
||||
</HStack>
|
||||
@ -682,9 +669,7 @@ const DataSettings: FC = () => {
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.data.app_knowledge.label')}</SettingRowTitle>
|
||||
<HStack alignItems="center" gap="5px">
|
||||
<Button variant="ghost" size="sm" onClick={handleRemoveAllFiles}>
|
||||
{t('settings.data.app_knowledge.button.delete')}
|
||||
</Button>
|
||||
<Button onClick={handleRemoveAllFiles}>{t('settings.data.app_knowledge.button.delete')}</Button>
|
||||
</HStack>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
@ -694,16 +679,14 @@ const DataSettings: FC = () => {
|
||||
{cacheSize && <CacheText>({cacheSize}MB)</CacheText>}
|
||||
</SettingRowTitle>
|
||||
<HStack gap="5px">
|
||||
<Button variant="ghost" size="sm" onClick={handleClearCache}>
|
||||
{t('settings.data.clear_cache.button')}
|
||||
</Button>
|
||||
<Button onClick={handleClearCache}>{t('settings.data.clear_cache.button')}</Button>
|
||||
</HStack>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.general.reset.title')}</SettingRowTitle>
|
||||
<HStack gap="5px">
|
||||
<Button variant="ghost" size="sm" onPress={reset} color="danger">
|
||||
<Button onClick={reset} danger>
|
||||
{t('settings.general.reset.title')}
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { Alert, Skeleton } from '@heroui/react'
|
||||
import { loggerService } from '@logger'
|
||||
import { ErrorTag } from '@renderer/components/Tags/ErrorTag'
|
||||
import { isMac, isWin } from '@renderer/config/constant'
|
||||
@ -6,7 +5,7 @@ import { useOcrProviders } from '@renderer/hooks/useOcrProvider'
|
||||
import type { ImageOcrProvider, OcrProvider } from '@renderer/types'
|
||||
import { BuiltinOcrProviderIds, isImageOcrProvider } from '@renderer/types'
|
||||
import { getErrorMessage } from '@renderer/utils'
|
||||
import { Select } from 'antd'
|
||||
import { Alert, Select, Skeleton } from 'antd'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWRImmutable from 'swr/immutable'
|
||||
@ -70,27 +69,39 @@ const OcrImageSettings = ({ setProvider }: Props) => {
|
||||
<SettingRowTitle>{t('settings.tool.ocr.image_provider')}</SettingRowTitle>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
{!platformSupport && isSystem && <ErrorTag message={t('settings.tool.ocr.error.not_system')} />}
|
||||
<Skeleton isLoaded={!isLoading}>
|
||||
{!error && (
|
||||
<Select
|
||||
value={imageProvider.id}
|
||||
style={{ width: '200px' }}
|
||||
onChange={(id: string) => setImageProvider(id)}
|
||||
options={options}
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
<Alert
|
||||
color="danger"
|
||||
title={t('ocr.error.provider.get_providers')}
|
||||
description={getErrorMessage(error)}
|
||||
/>
|
||||
)}
|
||||
</Skeleton>
|
||||
<OcrProviderSelector
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
value={imageProvider.id}
|
||||
options={options}
|
||||
onChange={setImageProvider}
|
||||
/>
|
||||
</div>
|
||||
</SettingRow>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type OcrProviderSelectorProps = {
|
||||
isLoading: boolean
|
||||
error: any
|
||||
value: string
|
||||
options: Array<{ value: string; label: string }>
|
||||
onChange: (id: string) => void
|
||||
}
|
||||
|
||||
const OcrProviderSelector = ({ isLoading, error, value, options, onChange }: OcrProviderSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton.Input active style={{ width: '200px', height: '32px' }} />
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Alert type="error" message={t('ocr.error.provider.get_providers')} description={getErrorMessage(error)} />
|
||||
}
|
||||
|
||||
return <Select value={value} style={{ width: '200px' }} onChange={onChange} options={options} />
|
||||
}
|
||||
|
||||
export default OcrImageSettings
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// TODO: Refactor this component to use HeroUI
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useApiServer } from '@renderer/hooks/useApiServer'
|
||||
import type { RootState } from '@renderer/store'
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { Switch } from '@heroui/react'
|
||||
import LanguageSelect from '@renderer/components/LanguageSelect'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import db from '@renderer/databases'
|
||||
import useTranslate from '@renderer/hooks/useTranslate'
|
||||
import type { AutoDetectionMethod, Model, TranslateLanguage } from '@renderer/types'
|
||||
import { Button, Flex, Modal, Radio, Space, Tooltip } from 'antd'
|
||||
import { Button, Flex, Modal, Radio, Space, Switch, Tooltip } from 'antd'
|
||||
import { HelpCircle } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
@ -69,8 +68,8 @@ const TranslateSettings: FC<{
|
||||
<Flex align="center" justify="space-between">
|
||||
<div style={{ fontWeight: 500 }}>{t('translate.settings.preview')}</div>
|
||||
<Switch
|
||||
isSelected={enableMarkdown}
|
||||
onValueChange={(checked) => {
|
||||
checked={enableMarkdown}
|
||||
onChange={(checked) => {
|
||||
setEnableMarkdown(checked)
|
||||
db.settings.put({ id: 'translate:markdown:enabled', value: checked })
|
||||
}}
|
||||
@ -81,13 +80,7 @@ const TranslateSettings: FC<{
|
||||
<div>
|
||||
<HStack alignItems="center" justifyContent="space-between">
|
||||
<div style={{ fontWeight: 500 }}>{t('translate.settings.autoCopy')}</div>
|
||||
<Switch
|
||||
isSelected={autoCopy}
|
||||
color="primary"
|
||||
onValueChange={(isSelected) => {
|
||||
updateSettings({ autoCopy: isSelected })
|
||||
}}
|
||||
/>
|
||||
<Switch checked={autoCopy} onChange={(checked) => updateSettings({ autoCopy: checked })} />
|
||||
</HStack>
|
||||
</div>
|
||||
|
||||
@ -95,11 +88,10 @@ const TranslateSettings: FC<{
|
||||
<Flex align="center" justify="space-between">
|
||||
<div style={{ fontWeight: 500 }}>{t('translate.settings.scroll_sync')}</div>
|
||||
<Switch
|
||||
isSelected={isScrollSyncEnabled}
|
||||
color="primary"
|
||||
onValueChange={(isSelected) => {
|
||||
setIsScrollSyncEnabled(isSelected)
|
||||
db.settings.put({ id: 'translate:scroll:sync', value: isSelected })
|
||||
checked={isScrollSyncEnabled}
|
||||
onChange={(checked) => {
|
||||
setIsScrollSyncEnabled(checked)
|
||||
db.settings.put({ id: 'translate:scroll:sync', value: checked })
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
@ -149,10 +141,9 @@ const TranslateSettings: FC<{
|
||||
</HStack>
|
||||
</div>
|
||||
<Switch
|
||||
isSelected={isBidirectional}
|
||||
color="primary"
|
||||
onValueChange={(isSelected) => {
|
||||
setIsBidirectional(isSelected)
|
||||
checked={isBidirectional}
|
||||
onChange={(checked) => {
|
||||
setIsBidirectional(checked)
|
||||
// 双向翻译设置不需要持久化,它只是界面状态
|
||||
}}
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user