= {
argTypes: {
variant: {
control: { type: 'select' },
- options: ['solid', 'bordered', 'light', 'flat', 'faded', 'shadow', 'ghost']
- },
- color: {
- control: { type: 'select' },
- options: ['default', 'primary', 'secondary', 'success', 'warning', 'danger']
+ options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link']
},
size: {
control: { type: 'select' },
- options: ['sm', 'md', 'lg']
+ options: ['default', 'sm', 'lg', 'icon', 'icon-sm', 'icon-lg']
},
- radius: {
- control: { type: 'select' },
- options: ['none', 'sm', 'md', 'lg', 'full']
- },
- isDisabled: {
+ disabled: {
control: { type: 'boolean' }
},
- isLoading: {
- control: { type: 'boolean' }
- },
- fullWidth: {
- control: { type: 'boolean' }
- },
- isIconOnly: {
+ asChild: {
control: { type: 'boolean' }
}
}
@@ -55,27 +41,12 @@ export const Default: Story = {
export const Variants: Story = {
render: () => (
-
-
-
-
-
-
+
+
+
+
-
- )
-}
-
-// 不同颜色
-export const Colors: Story = {
- render: () => (
-
-
-
-
-
-
-
+
)
}
@@ -83,23 +54,21 @@ export const Colors: Story = {
// 不同尺寸
export const Sizes: Story = {
render: () => (
-
+
-
+
)
}
-// 不同圆角
-export const Radius: Story = {
+// 图标按钮
+export const IconButtons: Story = {
render: () => (
-
-
-
-
-
-
+
+
+
+
)
}
@@ -109,8 +78,7 @@ export const States: Story = {
render: () => (
-
-
+
)
}
@@ -119,9 +87,15 @@ export const States: Story = {
export const WithIcons: Story = {
render: () => (
-
-
-
+
+
+
)
}
@@ -130,9 +104,7 @@ export const WithIcons: Story = {
export const FullWidth: Story = {
render: () => (
-
+
)
}
@@ -141,10 +113,45 @@ export const FullWidth: Story = {
export const Interactive: Story = {
render: () => (
-
-
)
}
+
+// 组合示例
+export const Combinations: Story = {
+ render: () => (
+
+
+
+ Small Default
+
+
+ Small Destructive
+
+
+ Small Outline
+
+
+
+ Default
+ Destructive
+ Outline
+
+
+
+ Large Default
+
+
+ Large Destructive
+
+
+ Large Outline
+
+
+
+ )
+}
diff --git a/packages/ui/stories/components/base/CopyButton.stories.tsx b/packages/ui/stories/components/base/CopyButton.stories.tsx
index fbe0b2faaf..db78b10c6a 100644
--- a/packages/ui/stories/components/base/CopyButton.stories.tsx
+++ b/packages/ui/stories/components/base/CopyButton.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
-import CopyButton from '../../../src/components/base/CopyButton'
+import CopyButton from '../../../src/components/primitives/copyButton'
const meta: Meta
= {
title: 'Base/CopyButton',
diff --git a/packages/ui/stories/components/base/CustomCollapse.stories.tsx b/packages/ui/stories/components/base/CustomCollapse.stories.tsx
deleted file mode 100644
index c05dbbfc54..0000000000
--- a/packages/ui/stories/components/base/CustomCollapse.stories.tsx
+++ /dev/null
@@ -1,459 +0,0 @@
-import { Button } from '@heroui/react'
-import type { Meta, StoryObj } from '@storybook/react'
-import { AlertTriangle, CreditCard, Info, Monitor, Settings, Shield } from 'lucide-react'
-import { useState } from 'react'
-
-import CustomCollapse, { Accordion, AccordionItem } from '../../../src/components/base/CustomCollapse'
-
-const meta: Meta = {
- title: 'Base/CustomCollapse',
- component: CustomCollapse,
- parameters: {
- layout: 'padded'
- },
- tags: ['autodocs'],
- argTypes: {
- children: {
- control: false,
- description: '面板内容'
- },
- accordionProps: {
- control: false,
- description: 'Accordion 组件的属性'
- },
- accordionItemProps: {
- control: false,
- description: 'AccordionItem 组件的属性'
- }
- }
-}
-
-export default meta
-type Story = StoryObj
-
-// 基础用法
-export const Default: Story = {
- args: {
- accordionItemProps: {
- title: '默认折叠面板'
- },
- children: (
-
-
这是折叠面板的内容。
-
可以包含任何内容,包括文本、图片、表单等。
-
- )
- }
-}
-
-// 带副标题
-export const WithSubtitle: Story = {
- args: {
- accordionProps: {
- defaultExpandedKeys: ['1']
- },
- accordionItemProps: {
- title: '带副标题的折叠面板',
- subtitle: 这是副标题内容
- },
- children: (
-
-
面板内容
-
可以在 subtitle 属性中设置副标题
-
- )
- }
-}
-
-// HeroUI 样式变体
-export const VariantLight: Story = {
- args: {
- accordionProps: {
- variant: 'light'
- },
- accordionItemProps: {
- title: 'Light 变体'
- },
- children: (
-
-
这是 HeroUI 的 Light 变体样式。
-
- )
- }
-}
-
-export const VariantShadow: Story = {
- args: {
- accordionProps: {
- variant: 'shadow',
- className: 'p-2'
- },
- accordionItemProps: {
- title: 'Shadow 变体',
- subtitle: '带阴影的面板样式'
- },
- children: (
-
-
这是 HeroUI 的 Shadow 变体样式。
-
- )
- }
-}
-
-export const VariantBordered: Story = {
- args: {
- accordionProps: {
- variant: 'bordered'
- },
- accordionItemProps: {
- title: 'Bordered 变体(默认)'
- },
- children: (
-
-
这是 HeroUI 的 Bordered 变体样式。
-
- )
- }
-}
-
-export const VariantSplitted: Story = {
- args: {
- accordionProps: {
- variant: 'splitted'
- },
- accordionItemProps: {
- title: 'Splitted 变体'
- },
- children: (
-
-
这是 HeroUI 的 Splitted 变体样式。
-
- )
- }
-}
-
-// 富内容标题
-export const RichLabel: Story = {
- args: {
- accordionItemProps: {
- title: (
-
-
- 设置面板
-
- )
- },
- children: (
-
-
-
- 通知设置
-
- 开启
-
-
-
- 自动更新
-
- 关闭
-
-
-
-
- )
- }
-}
-
-// 带警告提示
-export const WithWarning: Story = {
- args: {
- accordionItemProps: {
- title: (
-
-
- 连接的设备
-
- ),
- subtitle: (
-
- 2个问题需要立即修复
-
- )
- },
- children: (
-
-
检测到以下设备连接异常:
-
- - 外部显示器连接不稳定
- - 蓝牙键盘配对失败
-
-
- )
- }
-}
-
-// 禁用状态
-export const Disabled: Story = {
- args: {
- accordionProps: {
- isDisabled: true,
- defaultExpandedKeys: ['1']
- },
- accordionItemProps: {
- title: '禁用的折叠面板'
- },
- children: (
-
- )
- }
-}
-
-// 受控模式
-export const ControlledMode: Story = {
- render: function ControlledMode() {
- const [selectedKeys, setSelectedKeys] = useState>(new Set(['1']))
-
- return (
-
-
- setSelectedKeys(new Set(['1']))} color="primary">
- 展开
-
- setSelectedKeys(new Set())} color="default">
- 收起
-
-
-
{
- if (keys !== 'all') {
- setSelectedKeys(keys as Set)
- }
- }
- }}
- accordionItemProps={{
- title: '受控的折叠面板'
- }}>
-
-
这是一个受控的折叠面板
-
通过按钮控制展开和收起状态
-
-
-
当前状态:{selectedKeys.size > 0 ? '展开' : '收起'}
-
- )
- }
-}
-
-// 多个单面板组合
-export const MultipleSinglePanels: Story = {
- render: () => (
-
- )
-}
-
-// 使用原生 HeroUI Accordion 的多面板示例
-export const NativeAccordionMultiple: Story = {
- render: () => (
-
-
原生 HeroUI Accordion 多面板
-
- }
- subtitle={
-
- 2个问题需要立即修复
-
- }>
-
-
- }
- subtitle="3个应用有读取权限">
-
-
- }
- subtitle={请完善您的个人资料}>
-
-
-
- 卡片已过期
- *4812
-
- }
- startContent={}
- subtitle={请立即更新}>
-
-
-
-
- )
-}
-
-// 富内容面板
-export const RichContent: Story = {
- args: {
- accordionItemProps: {
- title: (
-
-
-
- 详细信息
-
-
e.stopPropagation()}>
-
- 保存
-
-
- 取消
-
-
-
- )
- },
- children: (
-
-
-
基本信息
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
- }
-}
-
-// 自定义样式
-export const CustomStyles: Story = {
- args: {
- accordionProps: {
- style: {
- backgroundColor: 'rgba(255, 193, 7, 0.1)',
- borderColor: 'var(--color-warning)'
- }
- },
- accordionItemProps: {
- title: (
-
- )
- },
- children: (
-
- )
- }
-}
-
-// 原生 HeroUI Accordion 多面板受控模式
-export const NativeAccordionControlled: Story = {
- render: function NativeAccordionControlled() {
- const [activeKeys, setActiveKeys] = useState>(new Set(['1']))
-
- return (
-
-
- setActiveKeys(new Set(['1', '2', '3']))} color="primary">
- 全部展开
-
- setActiveKeys(new Set())} color="default">
- 全部收起
-
- setActiveKeys(new Set(['2']))} color="default">
- 只展开第二个
-
-
-
{
- if (keys !== 'all') {
- setActiveKeys(keys as Set)
- }
- }}>
-
-
-
-
-
-
-
-
-
-
-
- 当前展开的面板:{activeKeys.size > 0 ? Array.from(activeKeys).join(', ') : '无'}
-
-
- )
- }
-}
diff --git a/packages/ui/stories/components/base/CustomTag.stories.tsx b/packages/ui/stories/components/base/CustomTag.stories.tsx
index 5b3ac9121f..5e8bc1e6e0 100644
--- a/packages/ui/stories/components/base/CustomTag.stories.tsx
+++ b/packages/ui/stories/components/base/CustomTag.stories.tsx
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
import { AlertTriangleIcon, StarIcon } from 'lucide-react'
import { action } from 'storybook/actions'
-import CustomTag from '../../../src/components/base/CustomTag'
+import CustomTag from '../../../src/components/primitives/customTag'
const meta: Meta = {
title: 'Base/CustomTag',
diff --git a/packages/ui/stories/components/base/DividerWithText.stories.tsx b/packages/ui/stories/components/base/DividerWithText.stories.tsx
index f5e68a54ce..915f10b0cc 100644
--- a/packages/ui/stories/components/base/DividerWithText.stories.tsx
+++ b/packages/ui/stories/components/base/DividerWithText.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
-import DividerWithText from '../../../src/components/base/DividerWithText'
+import DividerWithText from '../../../src/components/primitives/dividerWithText'
const meta: Meta = {
title: 'Base/DividerWithText',
diff --git a/packages/ui/stories/components/base/EmojiAvatar.stories.tsx b/packages/ui/stories/components/base/EmojiAvatar.stories.tsx
index 54e6e09903..e1c2368214 100644
--- a/packages/ui/stories/components/base/EmojiAvatar.stories.tsx
+++ b/packages/ui/stories/components/base/EmojiAvatar.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
-import EmojiAvatar from '../../../src/components/base/Avatar/EmojiAvatar'
+import EmojiAvatar from '../../../src/components/primitives/Avatar/EmojiAvatar'
const meta: Meta = {
title: 'Display/EmojiAvatar',
diff --git a/packages/ui/stories/components/base/EmojiIcon.stories.tsx b/packages/ui/stories/components/base/EmojiIcon.stories.tsx
index 2b9438abd3..d76d277f9d 100644
--- a/packages/ui/stories/components/base/EmojiIcon.stories.tsx
+++ b/packages/ui/stories/components/base/EmojiIcon.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
-import EmojiIcon from '../../../src/components/base/EmojiIcon'
+import EmojiIcon from '../../../src/components/primitives/emojiIcon'
const meta: Meta = {
title: 'Base/EmojiIcon',
diff --git a/packages/ui/stories/components/base/ErrorBoundary.stories.tsx b/packages/ui/stories/components/base/ErrorBoundary.stories.tsx
index 16e9ea39f2..e40f62e3ae 100644
--- a/packages/ui/stories/components/base/ErrorBoundary.stories.tsx
+++ b/packages/ui/stories/components/base/ErrorBoundary.stories.tsx
@@ -2,8 +2,8 @@ import { Button } from '@heroui/react'
import type { Meta, StoryObj } from '@storybook/react'
import { useState } from 'react'
-import type { CustomFallbackProps } from '../../../src/components/base/ErrorBoundary'
-import { ErrorBoundary } from '../../../src/components/base/ErrorBoundary'
+import type { CustomFallbackProps } from '../../../src/components/primitives/ErrorBoundary'
+import { ErrorBoundary } from '../../../src/components/primitives/ErrorBoundary'
// 错误组件 - 用于触发错误
const ThrowErrorComponent = ({ shouldThrow = false, errorMessage = '这是一个模拟错误' }) => {
diff --git a/packages/ui/stories/components/base/IndicatorLight.stories.tsx b/packages/ui/stories/components/base/IndicatorLight.stories.tsx
index 5278f4d984..dbb080ae6d 100644
--- a/packages/ui/stories/components/base/IndicatorLight.stories.tsx
+++ b/packages/ui/stories/components/base/IndicatorLight.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
-import IndicatorLight from '../../../src/components/base/IndicatorLight'
+import IndicatorLight from '../../../src/components/primitives/indicatorLight'
const meta: Meta = {
title: 'Base/IndicatorLight',
diff --git a/packages/ui/stories/components/base/Spinner.stories.tsx b/packages/ui/stories/components/base/Spinner.stories.tsx
index 68d3fc305b..730f5e56bd 100644
--- a/packages/ui/stories/components/base/Spinner.stories.tsx
+++ b/packages/ui/stories/components/base/Spinner.stories.tsx
@@ -2,7 +2,7 @@ import { Button } from '@heroui/react'
import type { Meta, StoryObj } from '@storybook/react'
import { useState } from 'react'
-import Spinner from '../../../src/components/base/Spinner'
+import Spinner from '../../../src/components/primitives/spinner'
const meta: Meta = {
title: 'Base/Spinner',
diff --git a/packages/ui/stories/components/base/StatusTag.stories.tsx b/packages/ui/stories/components/base/StatusTag.stories.tsx
deleted file mode 100644
index ab843d4f8e..0000000000
--- a/packages/ui/stories/components/base/StatusTag.stories.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react'
-
-import { ErrorTag, InfoTag, StatusTag, SuccessTag, WarnTag } from '../../../src/components/base/StatusTag'
-
-const meta: Meta = {
- title: 'Base/StatusTag',
- component: StatusTag,
- parameters: {
- layout: 'centered'
- },
- tags: ['autodocs'],
- argTypes: {
- type: {
- control: { type: 'select' },
- options: ['success', 'error', 'warning', 'info']
- },
- iconSize: { control: { type: 'range', min: 10, max: 24, step: 1 } },
- message: { control: 'text' },
- color: { control: 'color' }
- }
-}
-
-export default meta
-type Story = StoryObj
-
-// Default
-export const Default: Story = {
- args: {
- type: 'success',
- message: 'Success'
- }
-}
-
-// All Types
-export const AllTypes: Story = {
- render: () => (
-
-
-
-
-
-
- )
-}
-
-// Convenience Components
-export const ConvenienceComponents: Story = {
- render: () => (
-
-
-
-
-
-
- )
-}
-
-// Different Icon Sizes
-export const IconSizes: Story = {
- render: () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-// Custom Colors
-export const CustomColors: Story = {
- render: () => (
-
-
-
-
-
-
- )
-}
-
-// In Context
-export const InContext: Story = {
- render: () => (
-
-
-
Form Submission
-
Your form has been processed.
-
-
-
-
-
Validation Error
-
Please fix the following issues:
-
-
-
-
-
System Status
-
-
-
-
-
-
-
-
- )
-}
-
-// Use Cases
-export const UseCases: Story = {
- render: () => (
-
-
-
Success States
-
-
-
-
-
-
-
-
-
-
Error States
-
-
-
-
-
-
-
-
-
-
Warning States
-
-
-
-
-
-
-
-
-
-
Info States
-
-
-
-
-
-
-
-
- )
-}
-
-// Long Messages
-export const LongMessages: Story = {
- render: () => (
-
-
-
-
-
-
- )
-}
diff --git a/packages/ui/stories/components/base/TextBadge.stories.tsx b/packages/ui/stories/components/base/TextBadge.stories.tsx
deleted file mode 100644
index a62120193d..0000000000
--- a/packages/ui/stories/components/base/TextBadge.stories.tsx
+++ /dev/null
@@ -1,383 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react'
-
-import TextBadge from '../../../src/components/base/TextBadge'
-
-const meta: Meta = {
- title: 'Base/TextBadge',
- component: TextBadge,
- parameters: {
- layout: 'centered'
- },
- tags: ['autodocs'],
- argTypes: {
- text: {
- control: 'text',
- description: '徽章显示的文字'
- },
- style: {
- control: false,
- description: '自定义样式对象'
- }
- }
-}
-
-export default meta
-type Story = StoryObj
-
-export const Default: Story = {
- args: {
- text: '新'
- }
-}
-
-export const ShortText: Story = {
- args: {
- text: 'V2'
- }
-}
-
-export const LongText: Story = {
- args: {
- text: '热门推荐'
- }
-}
-
-export const Numbers: Story = {
- args: {
- text: '99+'
- }
-}
-
-export const Status: Story = {
- args: {
- text: '已完成'
- }
-}
-
-export const Version: Story = {
- args: {
- text: 'v1.2.0'
- }
-}
-
-export const CustomStyle: Story = {
- args: {
- text: '自定义',
- style: {
- backgroundColor: '#10b981',
- color: 'white',
- fontSize: '11px'
- }
- }
-}
-
-export const CustomClassName: Story = {
- args: {
- text: '特殊样式',
- className:
- 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 border border-purple-200 dark:border-purple-700'
- }
-}
-
-export const ColorVariations: Story = {
- render: () => (
-
- )
-}
-
-export const StatusBadges: Story = {
- render: () => (
-
- )
-}
-
-export const InUserInterface: Story = {
- render: () => (
-
-
界面应用示例
-
-
- {/* 导航菜单 */}
-
-
导航菜单
-
-
-
- {/* 卡片列表 */}
-
-
文章列表
-
-
-
-
-
React 18 新特性介绍
-
介绍 React 18 的并发特性...
-
-
-
-
-
-
-
-
-
-
-
-
Node.js 性能优化指南
-
深入了解 Node.js 性能优化...
-
-
-
-
-
-
-
-
-
-
-
-
TypeScript 最佳实践
-
TypeScript 开发的最佳实践...
-
-
-
-
-
-
-
-
-
-
- {/* 用户列表 */}
-
-
-
- )
-}
-
-export const VersionTags: Story = {
- render: () => (
-
- )
-}
-
-export const NumberBadges: Story = {
- render: () => (
-
-
数字徽章
-
-
-
通知数量
-
-
- 消息
-
-
-
- 任务
-
-
-
- 评论
-
-
-
-
-
-
-
统计数据
-
-
- 访问量
-
-
-
- 下载
-
-
-
- 收藏
-
-
-
-
-
-
- )
-}
-
-export const SizeVariations: Story = {
- render: () => (
-
- )
-}
-
-export const OutlineBadges: Story = {
- render: () => (
-
- )
-}
diff --git a/packages/ui/stories/components/display/Ellipsis.stories.tsx b/packages/ui/stories/components/display/Ellipsis.stories.tsx
index 38608e5ca3..add2c98e06 100644
--- a/packages/ui/stories/components/display/Ellipsis.stories.tsx
+++ b/packages/ui/stories/components/display/Ellipsis.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
-import Ellipsis from '../../../src/components/display/Ellipsis'
+import Ellipsis from '../../../src/components/composites/Ellipsis'
const meta = {
title: 'Display/Ellipsis',
diff --git a/packages/ui/stories/components/display/ExpandableText.stories.tsx b/packages/ui/stories/components/display/ExpandableText.stories.tsx
index 3f73cb82f9..2f02cec9f1 100644
--- a/packages/ui/stories/components/display/ExpandableText.stories.tsx
+++ b/packages/ui/stories/components/display/ExpandableText.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
-import ExpandableText from '../../../src/components/display/ExpandableText'
+import ExpandableText from '../../../src/components/composites/ExpandableText'
const meta: Meta = {
title: 'Display/ExpandableText',
diff --git a/packages/ui/stories/components/display/ListItem.stories.tsx b/packages/ui/stories/components/display/ListItem.stories.tsx
index c700e72170..71ceff3410 100644
--- a/packages/ui/stories/components/display/ListItem.stories.tsx
+++ b/packages/ui/stories/components/display/ListItem.stories.tsx
@@ -16,7 +16,7 @@ import {
} from 'lucide-react'
import { action } from 'storybook/actions'
-import ListItem from '../../../src/components/display/ListItem'
+import ListItem from '../../../src/components/composites/ListItem'
const meta: Meta = {
title: 'Display/ListItem',
diff --git a/packages/ui/stories/components/display/MaxContextCount.stories.tsx b/packages/ui/stories/components/display/MaxContextCount.stories.tsx
index 361ce1cb6d..7713fe6631 100644
--- a/packages/ui/stories/components/display/MaxContextCount.stories.tsx
+++ b/packages/ui/stories/components/display/MaxContextCount.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
-import MaxContextCount from '../../../src/components/display/MaxContextCount'
+import MaxContextCount from '../../../src/components/composites/MaxContextCount'
const meta: Meta = {
title: 'Display/MaxContextCount',
diff --git a/packages/ui/stories/components/display/ThinkingEffect.stories.tsx b/packages/ui/stories/components/display/ThinkingEffect.stories.tsx
index 8faed695b8..89425d9be3 100644
--- a/packages/ui/stories/components/display/ThinkingEffect.stories.tsx
+++ b/packages/ui/stories/components/display/ThinkingEffect.stories.tsx
@@ -2,7 +2,7 @@ import { Button } from '@heroui/react'
import type { Meta, StoryObj } from '@storybook/react'
import { useEffect, useMemo, useState } from 'react'
-import ThinkingEffect from '../../../src/components/display/ThinkingEffect'
+import ThinkingEffect from '../../../src/components/composites/ThinkingEffect'
const meta: Meta = {
title: 'Display/ThinkingEffect',
diff --git a/packages/ui/stories/components/interactive/CodeEditor.stories.tsx b/packages/ui/stories/components/interactive/CodeEditor.stories.tsx
index c5ccbe6c5b..03be17e036 100644
--- a/packages/ui/stories/components/interactive/CodeEditor.stories.tsx
+++ b/packages/ui/stories/components/interactive/CodeEditor.stories.tsx
@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { action } from 'storybook/actions'
-import type { LanguageConfig } from '../../../src/components/interactive/CodeEditor'
-import CodeEditor, { getCmThemeByName, getCmThemeNames } from '../../../src/components/interactive/CodeEditor'
+import type { LanguageConfig } from '../../../src/components/composites/CodeEditor'
+import CodeEditor, { getCmThemeByName, getCmThemeNames } from '../../../src/components/composites/CodeEditor'
// 示例语言配置 - 为 Storybook 提供更丰富的语言支持演示
const exampleLanguageConfig: LanguageConfig = {
diff --git a/packages/ui/stories/components/interactive/Selector.stories.tsx b/packages/ui/stories/components/interactive/Selector.stories.tsx
index 15f3e0925d..6d853ddde0 100644
--- a/packages/ui/stories/components/interactive/Selector.stories.tsx
+++ b/packages/ui/stories/components/interactive/Selector.stories.tsx
@@ -1,7 +1,7 @@
import type { Meta } from '@storybook/react'
import { useState } from 'react'
-import Selector from '../../../src/components/base/Selector'
+import Selector from '../../../src/components/primitives/Selector'
const meta: Meta = {
title: 'Interactive/Selector',
diff --git a/packages/ui/stories/components/interactive/Sortable.stories.tsx b/packages/ui/stories/components/interactive/Sortable.stories.tsx
index 33c456403e..701616dcff 100644
--- a/packages/ui/stories/components/interactive/Sortable.stories.tsx
+++ b/packages/ui/stories/components/interactive/Sortable.stories.tsx
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
import clsx from 'clsx'
import { useMemo, useState } from 'react'
-import { Sortable } from '../../../src/components/interactive/Sortable'
+import { Sortable } from '../../../src/components/composites/Sortable'
import { useDndReorder } from '../../../src/hooks'
type ExampleItem = { id: number; label: string }
diff --git a/packages/ui/stories/components/layout/HorizontalScrollContainer.stories.tsx b/packages/ui/stories/components/layout/HorizontalScrollContainer.stories.tsx
index 8d67057943..5546365f3b 100644
--- a/packages/ui/stories/components/layout/HorizontalScrollContainer.stories.tsx
+++ b/packages/ui/stories/components/layout/HorizontalScrollContainer.stories.tsx
@@ -1,7 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
-import HorizontalScrollContainer from '../../../src/components/layout/HorizontalScrollContainer'
+import HorizontalScrollContainer from '../../../src/components/composites/HorizontalScrollContainer'
const meta: Meta = {
title: 'Layout/HorizontalScrollContainer',
diff --git a/packages/ui/stories/components/layout/Scrollbar.stories.tsx b/packages/ui/stories/components/layout/Scrollbar.stories.tsx
index 8c8cd0cb90..46334333a7 100644
--- a/packages/ui/stories/components/layout/Scrollbar.stories.tsx
+++ b/packages/ui/stories/components/layout/Scrollbar.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
-import Scrollbar from '../../../src/components/layout/Scrollbar'
+import Scrollbar from '../../../src/components/composites/Scrollbar'
const meta: Meta = {
title: 'Layout/Scrollbar',
diff --git a/scripts/auto-translate-i18n.ts b/scripts/auto-translate-i18n.ts
index 3ba98d932f..b108a1e941 100644
--- a/scripts/auto-translate-i18n.ts
+++ b/scripts/auto-translate-i18n.ts
@@ -56,7 +56,7 @@ Performance Optimization Recommendations:
- For unstable services: MAX_CONCURRENT_TRANSLATIONS=2, TRANSLATION_DELAY_MS=500
Environment Variables:
-- BASE_LOCALE: Base locale for translation (default: 'en-us')
+- TRANSLATION_BASE_LOCALE: Base locale for translation (default: 'en-us')
- TRANSLATION_BASE_URL: Custom API endpoint URL
- TRANSLATION_MODEL: Custom translation model name
*/
@@ -257,7 +257,7 @@ const main = async () => {
validateConfig()
const localesDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
- const baseLocale = process.env.BASE_LOCALE ?? 'en-us'
+ const baseLocale = process.env.TRANSLATION_BASE_LOCALE ?? 'en-us'
const baseFileName = `${baseLocale}.json`
const baseLocalePath = path.join(__dirname, '../src/renderer/src/i18n/locales', baseFileName)
if (!fs.existsSync(baseLocalePath)) {
@@ -279,6 +279,8 @@ const main = async () => {
})
.map((filename) => path.join(localesDir, filename))
+ console.info(`📂 Base Locale: ${baseLocale}`)
+
console.info('📂 Files to translate:')
files.forEach((filePath) => {
const filename = path.basename(filePath, '.json')
diff --git a/scripts/sync-i18n.ts b/scripts/sync-i18n.ts
index 6b58756a5d..4077c5ace0 100644
--- a/scripts/sync-i18n.ts
+++ b/scripts/sync-i18n.ts
@@ -5,7 +5,7 @@ import { sortedObjectByKeys } from './sort'
const localesDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
const translateDir = path.join(__dirname, '../src/renderer/src/i18n/translate')
-const baseLocale = process.env.BASE_LOCALE ?? 'zh-cn'
+const baseLocale = process.env.TRANSLATION_BASE_LOCALE ?? 'en-us'
const baseFileName = `${baseLocale}.json`
const baseFilePath = path.join(localesDir, baseFileName)
@@ -13,45 +13,45 @@ type I18NValue = string | { [key: string]: I18NValue }
type I18N = { [key: string]: I18NValue }
/**
- * 递归同步 target 对象,使其与 template 对象保持一致
- * 1. 如果 template 中存在 target 中缺少的 key,则添加('[to be translated]')
- * 2. 如果 target 中存在 template 中不存在的 key,则删除
- * 3. 对于子对象,递归同步
+ * Recursively sync target object to match template object structure
+ * 1. Add keys that exist in template but missing in target (with '[to be translated]')
+ * 2. Remove keys that exist in target but not in template
+ * 3. Recursively sync nested objects
*
- * @param target 目标对象(需要更新的语言对象)
- * @param template 主模板对象(中文)
- * @returns 返回是否对 target 进行了更新
+ * @param target Target object (language object to be updated)
+ * @param template Base locale object (Chinese)
+ * @returns Returns whether target was updated
*/
function syncRecursively(target: I18N, template: I18N): void {
- // 添加 template 中存在但 target 中缺少的 key
+ // Add keys that exist in template but missing in target
for (const key in template) {
if (!(key in target)) {
target[key] =
typeof template[key] === 'object' && template[key] !== null ? {} : `[to be translated]:${template[key]}`
- console.log(`添加新属性:${key}`)
+ console.log(`Added new property: ${key}`)
}
if (typeof template[key] === 'object' && template[key] !== null) {
if (typeof target[key] !== 'object' || target[key] === null) {
target[key] = {}
}
- // 递归同步子对象
+ // Recursively sync nested objects
syncRecursively(target[key], template[key])
}
}
- // 删除 target 中存在但 template 中没有的 key
+ // Remove keys that exist in target but not in template
for (const targetKey in target) {
if (!(targetKey in template)) {
- console.log(`移除多余属性:${targetKey}`)
+ console.log(`Removed excess property: ${targetKey}`)
delete target[targetKey]
}
}
}
/**
- * 检查 JSON 对象中是否存在重复键,并收集所有重复键
- * @param obj 要检查的对象
- * @returns 返回重复键的数组(若无重复则返回空数组)
+ * Check JSON object for duplicate keys and collect all duplicates
+ * @param obj Object to check
+ * @returns Returns array of duplicate keys (empty array if no duplicates)
*/
function checkDuplicateKeys(obj: I18N): string[] {
const keys = new Set()
@@ -62,7 +62,7 @@ function checkDuplicateKeys(obj: I18N): string[] {
const fullPath = path ? `${path}.${key}` : key
if (keys.has(fullPath)) {
- // 发现重复键时,添加到数组中(避免重复添加)
+ // When duplicate key found, add to array (avoid duplicate additions)
if (!duplicateKeys.includes(fullPath)) {
duplicateKeys.push(fullPath)
}
@@ -70,7 +70,7 @@ function checkDuplicateKeys(obj: I18N): string[] {
keys.add(fullPath)
}
- // 递归检查子对象
+ // Recursively check nested objects
if (typeof obj[key] === 'object' && obj[key] !== null) {
checkObject(obj[key], fullPath)
}
@@ -83,7 +83,7 @@ function checkDuplicateKeys(obj: I18N): string[] {
function syncTranslations() {
if (!fs.existsSync(baseFilePath)) {
- console.error(`主模板文件 ${baseFileName} 不存在,请检查路径或文件名`)
+ console.error(`Base locale file ${baseFileName} does not exist, please check path or filename`)
return
}
@@ -92,24 +92,24 @@ function syncTranslations() {
try {
baseJson = JSON.parse(baseContent)
} catch (error) {
- console.error(`解析 ${baseFileName} 出错。${error}`)
+ console.error(`Error parsing ${baseFileName}. ${error}`)
return
}
- // 检查主模板是否存在重复键
+ // Check if base locale has duplicate keys
const duplicateKeys = checkDuplicateKeys(baseJson)
if (duplicateKeys.length > 0) {
- throw new Error(`主模板文件 ${baseFileName} 存在以下重复键:\n${duplicateKeys.join('\n')}`)
+ throw new Error(`Base locale file ${baseFileName} has the following duplicate keys:\n${duplicateKeys.join('\n')}`)
}
- // 为主模板排序
+ // Sort base locale
const sortedJson = sortedObjectByKeys(baseJson)
if (JSON.stringify(baseJson) !== JSON.stringify(sortedJson)) {
try {
fs.writeFileSync(baseFilePath, JSON.stringify(sortedJson, null, 2) + '\n', 'utf-8')
- console.log(`主模板已排序`)
+ console.log(`Base locale has been sorted`)
} catch (error) {
- console.error(`写入 ${baseFilePath} 出错。`, error)
+ console.error(`Error writing ${baseFilePath}.`, error)
return
}
}
@@ -124,7 +124,7 @@ function syncTranslations() {
.map((filename) => path.join(translateDir, filename))
const files = [...localeFiles, ...translateFiles]
- // 同步键
+ // Sync keys
for (const filePath of files) {
const filename = path.basename(filePath)
let targetJson: I18N = {}
@@ -132,7 +132,7 @@ function syncTranslations() {
const fileContent = fs.readFileSync(filePath, 'utf-8')
targetJson = JSON.parse(fileContent)
} catch (error) {
- console.error(`解析 ${filename} 出错,跳过此文件。`, error)
+ console.error(`Error parsing ${filename}, skipping this file.`, error)
continue
}
@@ -142,9 +142,9 @@ function syncTranslations() {
try {
fs.writeFileSync(filePath, JSON.stringify(sortedJson, null, 2) + '\n', 'utf-8')
- console.log(`文件 ${filename} 已排序并同步更新为主模板的内容`)
+ console.log(`File ${filename} has been sorted and synced to match base locale content`)
} catch (error) {
- console.error(`写入 ${filename} 出错。${error}`)
+ console.error(`Error writing ${filename}. ${error}`)
}
}
}
diff --git a/src/main/index.ts b/src/main/index.ts
index ed257f9f08..60b872b43e 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -19,6 +19,7 @@ import process from 'node:process'
import { registerIpc } from './ipc'
import { agentService } from './services/agents'
import { apiServerService } from './services/ApiServerService'
+import { appMenuService } from './services/AppMenuService'
import mcpService from './services/MCPService'
import { nodeTraceService } from './services/NodeTraceService'
import {
@@ -219,6 +220,9 @@ if (!app.requestSingleInstanceLock()) {
const mainWindow = windowService.createMainWindow()
new TrayService()
+
+ // Setup macOS application menu
+ appMenuService?.setupApplicationMenu()
nodeTraceService.init()
app.on('activate', function () {
diff --git a/src/main/services/AppMenuService.ts b/src/main/services/AppMenuService.ts
new file mode 100644
index 0000000000..8e870d432e
--- /dev/null
+++ b/src/main/services/AppMenuService.ts
@@ -0,0 +1,85 @@
+import { isMac } from '@main/constant'
+import { windowService } from '@main/services/WindowService'
+import { getAppLanguage, locales } from '@main/utils/language'
+import { IpcChannel } from '@shared/IpcChannel'
+import type { MenuItemConstructorOptions } from 'electron'
+import { app, Menu, shell } from 'electron'
+export class AppMenuService {
+ public setupApplicationMenu(): void {
+ const locale = locales[getAppLanguage()]
+ const { common } = locale.translation
+
+ const template: MenuItemConstructorOptions[] = [
+ {
+ label: app.name,
+ submenu: [
+ {
+ label: common.about + ' ' + app.name,
+ click: () => {
+ // Emit event to navigate to About page
+ const mainWindow = windowService.getMainWindow()
+ if (mainWindow && !mainWindow.isDestroyed()) {
+ mainWindow.webContents.send(IpcChannel.Windows_NavigateToAbout)
+ windowService.showMainWindow()
+ }
+ }
+ },
+ { type: 'separator' },
+ { role: 'services' },
+ { type: 'separator' },
+ { role: 'hide' },
+ { role: 'hideOthers' },
+ { role: 'unhide' },
+ { type: 'separator' },
+ { role: 'quit' }
+ ]
+ },
+ {
+ role: 'fileMenu'
+ },
+ {
+ role: 'editMenu'
+ },
+ {
+ role: 'viewMenu'
+ },
+ {
+ role: 'windowMenu'
+ },
+ {
+ role: 'help',
+ submenu: [
+ {
+ label: 'Website',
+ click: () => {
+ shell.openExternal('https://cherry-ai.com')
+ }
+ },
+ {
+ label: 'Documentation',
+ click: () => {
+ shell.openExternal('https://cherry-ai.com/docs')
+ }
+ },
+ {
+ label: 'Feedback',
+ click: () => {
+ shell.openExternal('https://github.com/CherryHQ/cherry-studio/issues/new/choose')
+ }
+ },
+ {
+ label: 'Releases',
+ click: () => {
+ shell.openExternal('https://github.com/CherryHQ/cherry-studio/releases')
+ }
+ }
+ ]
+ }
+ ]
+
+ const menu = Menu.buildFromTemplate(template)
+ Menu.setApplicationMenu(menu)
+ }
+}
+
+export const appMenuService = isMac ? new AppMenuService() : null
diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts
index 8ff25e356d..239890c7a7 100644
--- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts
+++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts
@@ -192,7 +192,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
extra_body: {
google: {
thinking_config: {
- thinking_budget: 0
+ thinkingBudget: 0
}
}
}
@@ -327,8 +327,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
extra_body: {
google: {
thinking_config: {
- thinking_budget: -1,
- include_thoughts: true
+ thinkingBudget: -1,
+ includeThoughts: true
}
}
}
@@ -338,8 +338,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
extra_body: {
google: {
thinking_config: {
- thinking_budget: budgetTokens,
- include_thoughts: true
+ thinkingBudget: budgetTokens,
+ includeThoughts: true
}
}
}
@@ -670,7 +670,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
} else if (isClaudeReasoningModel(model) && reasoningEffort.thinking?.budget_tokens) {
suffix = ` --thinking_budget ${reasoningEffort.thinking.budget_tokens}`
} else if (isGeminiReasoningModel(model) && reasoningEffort.extra_body?.google?.thinking_config) {
- suffix = ` --thinking_budget ${reasoningEffort.extra_body.google.thinking_config.thinking_budget}`
+ suffix = ` --thinking_budget ${reasoningEffort.extra_body.google.thinking_config.thinkingBudget}`
}
// FIXME: poe 不支持多个text part,上传文本文件的时候用的不是file part而是text part,因此会出问题
// 临时解决方案是强制poe用string content,但是其实poe部分支持array
diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts
index 90a62c3d3b..b9131be661 100644
--- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts
+++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts
@@ -341,29 +341,28 @@ export class OpenAIResponseAPIClient extends OpenAIBaseClient<
}
}
switch (message.type) {
- case 'function_call_output':
- {
- let str = ''
- if (typeof message.output === 'string') {
- str = message.output
- } else {
- for (const part of message.output) {
- switch (part.type) {
- case 'input_text':
- str += part.text
- break
- case 'input_image':
- str += part.image_url || ''
- break
- case 'input_file':
- str += part.file_data || ''
- break
- }
+ case 'function_call_output': {
+ let str = ''
+ if (typeof message.output === 'string') {
+ str = message.output
+ } else {
+ for (const part of message.output) {
+ switch (part.type) {
+ case 'input_text':
+ str += part.text
+ break
+ case 'input_image':
+ str += part.image_url || ''
+ break
+ case 'input_file':
+ str += part.file_data || ''
+ break
}
}
- sum += estimateTextTokens(str)
}
+ sum += estimateTextTokens(str)
break
+ }
case 'function_call':
sum += estimateTextTokens(message.arguments)
break
diff --git a/src/renderer/src/aiCore/legacy/middleware/feat/ImageGenerationMiddleware.ts b/src/renderer/src/aiCore/legacy/middleware/feat/ImageGenerationMiddleware.ts
index 174adaa6cc..0876903426 100644
--- a/src/renderer/src/aiCore/legacy/middleware/feat/ImageGenerationMiddleware.ts
+++ b/src/renderer/src/aiCore/legacy/middleware/feat/ImageGenerationMiddleware.ts
@@ -82,6 +82,12 @@ export const ImageGenerationMiddleware: CompletionsMiddleware =
const options = { signal, timeout: defaultTimeout }
if (imageFiles.length > 0) {
+ const model = assistant.model
+ const provider = context.apiClientInstance.provider
+ // https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/dall-e?tabs=gpt-image-1#call-the-image-edit-api
+ if (model.id.toLowerCase().includes('gpt-image-1-mini') && provider.type === 'azure-openai') {
+ throw new Error('Azure OpenAI GPT-Image-1-Mini model does not support image editing.')
+ }
response = await sdk.images.edit(
{
model: assistant.model.id,
diff --git a/src/renderer/src/aiCore/middleware/AiSdkMiddlewareBuilder.ts b/src/renderer/src/aiCore/middleware/AiSdkMiddlewareBuilder.ts
index 12ad483121..e792e72bb5 100644
--- a/src/renderer/src/aiCore/middleware/AiSdkMiddlewareBuilder.ts
+++ b/src/renderer/src/aiCore/middleware/AiSdkMiddlewareBuilder.ts
@@ -1,11 +1,13 @@
import type { WebSearchPluginConfig } from '@cherrystudio/ai-core/built-in/plugins'
import { loggerService } from '@logger'
-import type { MCPTool, Message, Model, Provider } from '@renderer/types'
+import { type MCPTool, type Message, type Model, type Provider } from '@renderer/types'
import type { Chunk } from '@renderer/types/chunk'
import type { LanguageModelMiddleware } from 'ai'
import { extractReasoningMiddleware, simulateStreamingMiddleware } from 'ai'
+import { isOpenRouterGeminiGenerateImageModel } from '../utils/image'
import { noThinkMiddleware } from './noThinkMiddleware'
+import { openrouterGenerateImageMiddleware } from './openrouterGenerateImageMiddleware'
import { toolChoiceMiddleware } from './toolChoiceMiddleware'
const logger = loggerService.withContext('AiSdkMiddlewareBuilder')
@@ -214,15 +216,16 @@ function addProviderSpecificMiddlewares(builder: AiSdkMiddlewareBuilder, config:
/**
* 添加模型特定的中间件
*/
-function addModelSpecificMiddlewares(_: AiSdkMiddlewareBuilder, config: AiSdkMiddlewareConfig): void {
- if (!config.model) return
+function addModelSpecificMiddlewares(builder: AiSdkMiddlewareBuilder, config: AiSdkMiddlewareConfig): void {
+ if (!config.model || !config.provider) return
// 可以根据模型ID或特性添加特定中间件
// 例如:图像生成模型、多模态模型等
-
- // 示例:某些模型需要特殊处理
- if (config.model.id.includes('dalle') || config.model.id.includes('midjourney')) {
- // 图像生成相关中间件
+ if (isOpenRouterGeminiGenerateImageModel(config.model, config.provider)) {
+ builder.add({
+ name: 'openrouter-gemini-image-generation',
+ middleware: openrouterGenerateImageMiddleware()
+ })
}
}
diff --git a/src/renderer/src/aiCore/middleware/openrouterGenerateImageMiddleware.ts b/src/renderer/src/aiCore/middleware/openrouterGenerateImageMiddleware.ts
new file mode 100644
index 0000000000..792192b931
--- /dev/null
+++ b/src/renderer/src/aiCore/middleware/openrouterGenerateImageMiddleware.ts
@@ -0,0 +1,33 @@
+import type { LanguageModelMiddleware } from 'ai'
+
+/**
+ * Returns a LanguageModelMiddleware that ensures the OpenRouter provider is configured to support both
+ * image and text modalities.
+ * https://openrouter.ai/docs/features/multimodal/image-generation
+ *
+ * Remarks:
+ * - The middleware declares middlewareVersion as 'v2'.
+ * - transformParams asynchronously clones the incoming params and sets
+ * providerOptions.openrouter.modalities = ['image', 'text'], preserving other providerOptions and
+ * openrouter fields when present.
+ * - Intended to ensure the provider can handle image and text generation without altering other
+ * parameter values.
+ *
+ * @returns LanguageModelMiddleware - a middleware that augments providerOptions for OpenRouter to include image and text modalities.
+ */
+export function openrouterGenerateImageMiddleware(): LanguageModelMiddleware {
+ return {
+ middlewareVersion: 'v2',
+
+ transformParams: async ({ params }) => {
+ const transformedParams = { ...params }
+ transformedParams.providerOptions = {
+ ...transformedParams.providerOptions,
+ openrouter: { ...transformedParams.providerOptions?.openrouter, modalities: ['image', 'text'] }
+ }
+ transformedParams
+
+ return transformedParams
+ }
+ }
+}
diff --git a/src/renderer/src/aiCore/plugins/telemetryPlugin.ts b/src/renderer/src/aiCore/plugins/telemetryPlugin.ts
index 1d34d2835e..485d339d25 100644
--- a/src/renderer/src/aiCore/plugins/telemetryPlugin.ts
+++ b/src/renderer/src/aiCore/plugins/telemetryPlugin.ts
@@ -50,7 +50,7 @@ class AdapterTracer {
this.cachedParentContext = undefined
}
- logger.info('AdapterTracer created with parent context info', {
+ logger.debug('AdapterTracer created with parent context info', {
topicId,
modelName,
parentTraceId: this.parentSpanContext?.traceId,
@@ -63,7 +63,7 @@ class AdapterTracer {
startActiveSpan any>(name: string, options: any, fn: F): ReturnType
startActiveSpan any>(name: string, options: any, context: any, fn: F): ReturnType
startActiveSpan any>(name: string, arg2?: any, arg3?: any, arg4?: any): ReturnType {
- logger.info('AdapterTracer.startActiveSpan called', {
+ logger.debug('AdapterTracer.startActiveSpan called', {
spanName: name,
topicId: this.topicId,
modelName: this.modelName,
@@ -89,7 +89,7 @@ class AdapterTracer {
// 包装span的end方法
const originalEnd = span.end.bind(span)
span.end = (endTime?: any) => {
- logger.info('AI SDK span.end() called in startActiveSpan - about to convert span', {
+ logger.debug('AI SDK span.end() called in startActiveSpan - about to convert span', {
spanName: name,
spanId: span.spanContext().spanId,
traceId: span.spanContext().traceId,
@@ -102,14 +102,14 @@ class AdapterTracer {
// 转换并保存 span 数据
try {
- logger.info('Converting AI SDK span to SpanEntity (from startActiveSpan)', {
+ logger.debug('Converting AI SDK span to SpanEntity (from startActiveSpan)', {
spanName: name,
spanId: span.spanContext().spanId,
traceId: span.spanContext().traceId,
topicId: this.topicId,
modelName: this.modelName
})
- logger.info('span', span)
+ logger.silly('span', span)
const spanEntity = AiSdkSpanAdapter.convertToSpanEntity({
span,
topicId: this.topicId,
@@ -119,7 +119,7 @@ class AdapterTracer {
// 保存转换后的数据
window.api.trace.saveEntity(spanEntity)
- logger.info('AI SDK span converted and saved successfully (from startActiveSpan)', {
+ logger.debug('AI SDK span converted and saved successfully (from startActiveSpan)', {
spanName: name,
spanId: span.spanContext().spanId,
traceId: span.spanContext().traceId,
@@ -152,7 +152,7 @@ class AdapterTracer {
if (this.parentSpanContext) {
try {
const ctx = trace.setSpanContext(otelContext.active(), this.parentSpanContext)
- logger.info('Created active context with parent SpanContext for startActiveSpan', {
+ logger.debug('Created active context with parent SpanContext for startActiveSpan', {
spanName: name,
parentTraceId: this.parentSpanContext.traceId,
parentSpanId: this.parentSpanContext.spanId,
@@ -219,7 +219,7 @@ export function createTelemetryPlugin(config: TelemetryPluginConfig) {
if (effectiveTopicId) {
try {
// 从 SpanManagerService 获取当前的 span
- logger.info('Attempting to find parent span', {
+ logger.debug('Attempting to find parent span', {
topicId: effectiveTopicId,
requestId: context.requestId,
modelName: modelName,
@@ -231,7 +231,7 @@ export function createTelemetryPlugin(config: TelemetryPluginConfig) {
if (parentSpan) {
// 直接使用父 span 的 SpanContext,避免手动拼装字段遗漏
parentSpanContext = parentSpan.spanContext()
- logger.info('Found active parent span for AI SDK', {
+ logger.debug('Found active parent span for AI SDK', {
parentSpanId: parentSpanContext.spanId,
parentTraceId: parentSpanContext.traceId,
topicId: effectiveTopicId,
@@ -303,7 +303,7 @@ export function createTelemetryPlugin(config: TelemetryPluginConfig) {
logger.debug('Updated active context with parent span')
})
- logger.info('Set parent context for AI SDK spans', {
+ logger.debug('Set parent context for AI SDK spans', {
parentSpanId: parentSpanContext?.spanId,
parentTraceId: parentSpanContext?.traceId,
hasActiveContext: !!activeContext,
@@ -314,7 +314,7 @@ export function createTelemetryPlugin(config: TelemetryPluginConfig) {
}
}
- logger.info('Injecting AI SDK telemetry config with adapter', {
+ logger.debug('Injecting AI SDK telemetry config with adapter', {
requestId: context.requestId,
topicId: effectiveTopicId,
modelId: context.modelId,
diff --git a/src/renderer/src/aiCore/prepareParams/messageConverter.ts b/src/renderer/src/aiCore/prepareParams/messageConverter.ts
index bfa303bcbc..72f387d9a4 100644
--- a/src/renderer/src/aiCore/prepareParams/messageConverter.ts
+++ b/src/renderer/src/aiCore/prepareParams/messageConverter.ts
@@ -4,7 +4,7 @@
*/
import { loggerService } from '@logger'
-import { isVisionModel } from '@renderer/config/models'
+import { isImageEnhancementModel, isVisionModel } from '@renderer/config/models'
import type { Message, Model } from '@renderer/types'
import type { FileMessageBlock, ImageMessageBlock, ThinkingMessageBlock } from '@renderer/types/newMessage'
import {
@@ -47,6 +47,41 @@ export async function convertMessageToSdkParam(
}
}
+async function convertImageBlockToImagePart(imageBlocks: ImageMessageBlock[]): Promise> {
+ const parts: Array = []
+ for (const imageBlock of imageBlocks) {
+ if (imageBlock.file) {
+ try {
+ const image = await window.api.file.base64Image(imageBlock.file.id + imageBlock.file.ext)
+ parts.push({
+ type: 'image',
+ image: image.base64,
+ mediaType: image.mime
+ })
+ } catch (error) {
+ logger.warn('Failed to load image:', error as Error)
+ }
+ } else if (imageBlock.url) {
+ const isBase64 = imageBlock.url.startsWith('data:')
+ if (isBase64) {
+ const base64 = imageBlock.url.match(/^data:[^;]*;base64,(.+)$/)![1]
+ const mimeMatch = imageBlock.url.match(/^data:([^;]+)/)
+ parts.push({
+ type: 'image',
+ image: base64,
+ mediaType: mimeMatch ? mimeMatch[1] : 'image/png'
+ })
+ } else {
+ parts.push({
+ type: 'image',
+ image: imageBlock.url
+ })
+ }
+ }
+ }
+ return parts
+}
+
/**
* 转换为用户模型消息
*/
@@ -64,25 +99,7 @@ async function convertMessageToUserModelMessage(
// 处理图片(仅在支持视觉的模型中)
if (isVisionModel) {
- for (const imageBlock of imageBlocks) {
- if (imageBlock.file) {
- try {
- const image = await window.api.file.base64Image(imageBlock.file.id + imageBlock.file.ext)
- parts.push({
- type: 'image',
- image: image.base64,
- mediaType: image.mime
- })
- } catch (error) {
- logger.warn('Failed to load image:', error as Error)
- }
- } else if (imageBlock.url) {
- parts.push({
- type: 'image',
- image: imageBlock.url
- })
- }
- }
+ parts.push(...(await convertImageBlockToImagePart(imageBlocks)))
}
// 处理文件
for (const fileBlock of fileBlocks) {
@@ -172,7 +189,27 @@ async function convertMessageToAssistantModelMessage(
}
/**
- * 转换 Cherry Studio 消息数组为 AI SDK 消息数组
+ * Converts an array of messages to SDK-compatible model messages.
+ *
+ * This function processes messages and transforms them into the format required by the SDK.
+ * It handles special cases for vision models and image enhancement models.
+ *
+ * @param messages - Array of messages to convert. Must contain at least 2 messages when using image enhancement models.
+ * @param model - The model configuration that determines conversion behavior
+ *
+ * @returns A promise that resolves to an array of SDK-compatible model messages
+ *
+ * @remarks
+ * For image enhancement models with 2+ messages:
+ * - Expects the second-to-last message (index length-2) to be an assistant message containing image blocks
+ * - Expects the last message (index length-1) to be a user message
+ * - Extracts images from the assistant message and appends them to the user message content
+ * - Returns only the last two processed messages [assistantSdkMessage, userSdkMessage]
+ *
+ * For other models:
+ * - Returns all converted messages in order
+ *
+ * The function automatically detects vision model capabilities and adjusts conversion accordingly.
*/
export async function convertMessagesToSdkMessages(messages: Message[], model: Model): Promise {
const sdkMessages: ModelMessage[] = []
@@ -182,6 +219,31 @@ export async function convertMessagesToSdkMessages(messages: Message[], model: M
const sdkMessage = await convertMessageToSdkParam(message, isVision, model)
sdkMessages.push(...(Array.isArray(sdkMessage) ? sdkMessage : [sdkMessage]))
}
+ // Special handling for image enhancement models
+ // Only keep the last two messages and merge images into the user message
+ // [system?, user, assistant, user]
+ if (isImageEnhancementModel(model) && messages.length >= 3) {
+ const needUpdatedMessages = messages.slice(-2)
+ const needUpdatedSdkMessages = sdkMessages.slice(-2)
+ const assistantMessage = needUpdatedMessages.filter((m) => m.role === 'assistant')[0]
+ const assistantSdkMessage = needUpdatedSdkMessages.filter((m) => m.role === 'assistant')[0]
+ const userSdkMessage = needUpdatedSdkMessages.filter((m) => m.role === 'user')[0]
+ const systemSdkMessages = sdkMessages.filter((m) => m.role === 'system')
+ const imageBlocks = findImageBlocks(assistantMessage)
+ const imageParts = await convertImageBlockToImagePart(imageBlocks)
+ const parts: Array = []
+ if (typeof userSdkMessage.content === 'string') {
+ parts.push({ type: 'text', text: userSdkMessage.content })
+ parts.push(...imageParts)
+ userSdkMessage.content = parts
+ } else {
+ userSdkMessage.content.push(...imageParts)
+ }
+ if (systemSdkMessages.length > 0) {
+ return [systemSdkMessages[0], assistantSdkMessage, userSdkMessage]
+ }
+ return [assistantSdkMessage, userSdkMessage]
+ }
return sdkMessages
}
diff --git a/src/renderer/src/aiCore/prepareParams/modelParameters.ts b/src/renderer/src/aiCore/prepareParams/modelParameters.ts
index 6f78ac2cc4..ed3f4fa210 100644
--- a/src/renderer/src/aiCore/prepareParams/modelParameters.ts
+++ b/src/renderer/src/aiCore/prepareParams/modelParameters.ts
@@ -4,6 +4,7 @@
*/
import {
+ isClaude45ReasoningModel,
isClaudeReasoningModel,
isNotSupportTemperatureAndTopP,
isSupportedFlexServiceTier
@@ -19,7 +20,10 @@ export function getTemperature(assistant: Assistant, model: Model): number | und
if (assistant.settings?.reasoning_effort && isClaudeReasoningModel(model)) {
return undefined
}
- if (isNotSupportTemperatureAndTopP(model)) {
+ if (
+ isNotSupportTemperatureAndTopP(model) ||
+ (isClaude45ReasoningModel(model) && assistant.settings?.enableTopP && !assistant.settings?.enableTemperature)
+ ) {
return undefined
}
const assistantSettings = getAssistantSettings(assistant)
@@ -33,7 +37,10 @@ export function getTopP(assistant: Assistant, model: Model): number | undefined
if (assistant.settings?.reasoning_effort && isClaudeReasoningModel(model)) {
return undefined
}
- if (isNotSupportTemperatureAndTopP(model)) {
+ if (
+ isNotSupportTemperatureAndTopP(model) ||
+ (isClaude45ReasoningModel(model) && assistant.settings?.enableTemperature)
+ ) {
return undefined
}
const assistantSettings = getAssistantSettings(assistant)
diff --git a/src/renderer/src/aiCore/provider/providerInitialization.ts b/src/renderer/src/aiCore/provider/providerInitialization.ts
index 9942ffa405..665f2bd05c 100644
--- a/src/renderer/src/aiCore/provider/providerInitialization.ts
+++ b/src/renderer/src/aiCore/provider/providerInitialization.ts
@@ -63,6 +63,14 @@ export const NEW_PROVIDER_CONFIGS: ProviderConfig[] = [
creatorFunctionName: 'createMistral',
supportsImageGeneration: false,
aliases: ['mistral']
+ },
+ {
+ id: 'huggingface',
+ name: 'HuggingFace',
+ import: () => import('@ai-sdk/huggingface'),
+ creatorFunctionName: 'createHuggingFace',
+ supportsImageGeneration: true,
+ aliases: ['hf', 'hugging-face']
}
] as const
diff --git a/src/renderer/src/aiCore/utils/image.ts b/src/renderer/src/aiCore/utils/image.ts
index 7691f9d4b1..37dbe76a2c 100644
--- a/src/renderer/src/aiCore/utils/image.ts
+++ b/src/renderer/src/aiCore/utils/image.ts
@@ -1,5 +1,16 @@
+import type { Model, Provider } from '@renderer/types'
+import { isSystemProvider, SystemProviderIds } from '@renderer/types'
+
export function buildGeminiGenerateImageParams(): Record {
return {
responseModalities: ['TEXT', 'IMAGE']
}
}
+
+export function isOpenRouterGeminiGenerateImageModel(model: Model, provider: Provider): boolean {
+ return (
+ model.id.includes('gemini-2.5-flash-image') &&
+ isSystemProvider(provider) &&
+ provider.id === SystemProviderIds.openrouter
+ )
+}
diff --git a/src/renderer/src/aiCore/utils/options.ts b/src/renderer/src/aiCore/utils/options.ts
index 451d2efa68..eaf4764c70 100644
--- a/src/renderer/src/aiCore/utils/options.ts
+++ b/src/renderer/src/aiCore/utils/options.ts
@@ -88,7 +88,9 @@ export function buildProviderOptions(
serviceTier: serviceTierSetting
}
break
-
+ case 'huggingface':
+ providerSpecificOptions = buildOpenAIProviderOptions(assistant, model, capabilities)
+ break
case 'anthropic':
providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities)
break
diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts
index 39cc71d4b9..0246ac31cb 100644
--- a/src/renderer/src/aiCore/utils/reasoning.ts
+++ b/src/renderer/src/aiCore/utils/reasoning.ts
@@ -10,6 +10,7 @@ import {
isGrok4FastReasoningModel,
isGrokReasoningModel,
isOpenAIDeepResearchModel,
+ isOpenAIModel,
isOpenAIReasoningModel,
isQwenAlwaysThinkModel,
isQwenReasoningModel,
@@ -33,6 +34,7 @@ import type { SettingsState } from '@renderer/store/settings'
import type { Assistant, Model } from '@renderer/types'
import { EFFORT_RATIO, isSystemProvider, SystemProviderIds } from '@renderer/types'
import type { ReasoningEffortOptionalParams } from '@renderer/types/sdk'
+import { toInteger } from 'lodash'
const logger = loggerService.withContext('reasoning')
@@ -66,7 +68,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
isGrokReasoningModel(model) ||
isOpenAIReasoningModel(model) ||
isQwenAlwaysThinkModel(model) ||
- model.id.includes('seed-oss')
+ model.id.includes('seed-oss') ||
+ model.id.includes('minimax-m2')
) {
return {}
}
@@ -95,7 +98,7 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
extra_body: {
google: {
thinking_config: {
- thinking_budget: 0
+ thinkingBudget: 0
}
}
}
@@ -113,9 +116,54 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
}
// reasoningEffort有效的情况
+
+ // OpenRouter models
+ if (model.provider === SystemProviderIds.openrouter) {
+ // Grok 4 Fast doesn't support effort levels, always use enabled: true
+ if (isGrok4FastReasoningModel(model)) {
+ return {
+ reasoning: {
+ enabled: true // Ignore effort level, just enable reasoning
+ }
+ }
+ }
+
+ // Other OpenRouter models that support effort levels
+ if (isSupportedReasoningEffortModel(model) || isSupportedThinkingTokenModel(model)) {
+ return {
+ reasoning: {
+ effort: reasoningEffort === 'auto' ? 'medium' : reasoningEffort
+ }
+ }
+ }
+ }
+
+ const effortRatio = EFFORT_RATIO[reasoningEffort]
+ const tokenLimit = findTokenLimit(model.id)
+ let budgetTokens: number | undefined
+ if (tokenLimit) {
+ budgetTokens = Math.floor((tokenLimit.max - tokenLimit.min) * effortRatio + tokenLimit.min)
+ }
+
+ // See https://docs.siliconflow.cn/cn/api-reference/chat-completions/chat-completions
+ if (model.provider === SystemProviderIds.silicon) {
+ if (
+ isDeepSeekHybridInferenceModel(model) ||
+ isSupportedThinkingTokenZhipuModel(model) ||
+ isSupportedThinkingTokenQwenModel(model) ||
+ isSupportedThinkingTokenHunyuanModel(model)
+ ) {
+ return {
+ enable_thinking: true,
+ // Hard-encoded maximum, only for silicon
+ thinking_budget: budgetTokens ? toInteger(Math.max(budgetTokens, 32768)) : undefined
+ }
+ }
+ return {}
+ }
+
// DeepSeek hybrid inference models, v3.1 and maybe more in the future
// 不同的 provider 有不同的思考控制方式,在这里统一解决
-
if (isDeepSeekHybridInferenceModel(model)) {
if (isSystemProvider(provider)) {
switch (provider.id) {
@@ -124,10 +172,6 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
enable_thinking: true,
incremental_output: true
}
- case SystemProviderIds.silicon:
- return {
- enable_thinking: true
- }
case SystemProviderIds.hunyuan:
case SystemProviderIds['tencent-cloud-ti']:
case SystemProviderIds.doubao:
@@ -152,54 +196,13 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
logger.warn(
`Skipping thinking options for provider ${provider.name} as DeepSeek v3.1 thinking control method is unknown`
)
+ case SystemProviderIds.silicon:
+ // specially handled before
}
}
}
- // OpenRouter models
- if (model.provider === SystemProviderIds.openrouter) {
- // Grok 4 Fast doesn't support effort levels, always use enabled: true
- if (isGrok4FastReasoningModel(model)) {
- return {
- reasoning: {
- enabled: true // Ignore effort level, just enable reasoning
- }
- }
- }
-
- // Other OpenRouter models that support effort levels
- if (isSupportedReasoningEffortModel(model) || isSupportedThinkingTokenModel(model)) {
- return {
- reasoning: {
- effort: reasoningEffort === 'auto' ? 'medium' : reasoningEffort
- }
- }
- }
- }
-
- // Doubao 思考模式支持
- if (isSupportedThinkingTokenDoubaoModel(model)) {
- if (isDoubaoSeedAfter251015(model)) {
- return { reasoningEffort }
- }
- // Comment below this line seems weird. reasoning is high instead of null/undefined. Who wrote this?
- // reasoningEffort 为空,默认开启 enabled
- if (reasoningEffort === 'high') {
- return { thinking: { type: 'enabled' } }
- }
- if (reasoningEffort === 'auto' && isDoubaoThinkingAutoModel(model)) {
- return { thinking: { type: 'auto' } }
- }
- // 其他情况不带 thinking 字段
- return {}
- }
-
- const effortRatio = EFFORT_RATIO[reasoningEffort]
- const budgetTokens = Math.floor(
- (findTokenLimit(model.id)?.max! - findTokenLimit(model.id)?.min!) * effortRatio + findTokenLimit(model.id)?.min!
- )
-
- // OpenRouter models, use thinking
+ // OpenRouter models, use reasoning
if (model.provider === SystemProviderIds.openrouter) {
if (isSupportedReasoningEffortModel(model) || isSupportedThinkingTokenModel(model)) {
return {
@@ -256,8 +259,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
extra_body: {
google: {
thinking_config: {
- thinking_budget: -1,
- include_thoughts: true
+ thinkingBudget: -1,
+ includeThoughts: true
}
}
}
@@ -267,8 +270,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
extra_body: {
google: {
thinking_config: {
- thinking_budget: budgetTokens,
- include_thoughts: true
+ thinkingBudget: budgetTokens,
+ includeThoughts: true
}
}
}
@@ -281,22 +284,26 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
return {
thinking: {
type: 'enabled',
- budget_tokens: Math.floor(
- Math.max(1024, Math.min(budgetTokens, (maxTokens || DEFAULT_MAX_TOKENS) * effortRatio))
- )
+ budget_tokens: budgetTokens
+ ? Math.floor(Math.max(1024, Math.min(budgetTokens, (maxTokens || DEFAULT_MAX_TOKENS) * effortRatio)))
+ : undefined
}
}
}
// Use thinking, doubao, zhipu, etc.
if (isSupportedThinkingTokenDoubaoModel(model)) {
- if (assistant.settings?.reasoning_effort === 'high') {
- return {
- thinking: {
- type: 'enabled'
- }
- }
+ if (isDoubaoSeedAfter251015(model)) {
+ return { reasoningEffort }
}
+ if (reasoningEffort === 'high') {
+ return { thinking: { type: 'enabled' } }
+ }
+ if (reasoningEffort === 'auto' && isDoubaoThinkingAutoModel(model)) {
+ return { thinking: { type: 'auto' } }
+ }
+ // 其他情况不带 thinking 字段
+ return {}
}
if (isSupportedThinkingTokenZhipuModel(model)) {
return { thinking: { type: 'enabled' } }
@@ -314,6 +321,20 @@ export function getOpenAIReasoningParams(assistant: Assistant, model: Model): Re
if (!isReasoningModel(model)) {
return {}
}
+
+ let reasoningEffort = assistant?.settings?.reasoning_effort
+
+ if (!reasoningEffort) {
+ return {}
+ }
+
+ // 非OpenAI模型,但是Provider类型是responses/azure openai的情况
+ if (!isOpenAIModel(model)) {
+ return {
+ reasoningEffort
+ }
+ }
+
const openAI = getStoreSetting('openAI') as SettingsState['openAI']
const summaryText = openAI?.summaryText || 'off'
@@ -325,16 +346,10 @@ export function getOpenAIReasoningParams(assistant: Assistant, model: Model): Re
reasoningSummary = summaryText
}
- let reasoningEffort = assistant?.settings?.reasoning_effort
-
if (isOpenAIDeepResearchModel(model)) {
reasoningEffort = 'medium'
}
- if (!reasoningEffort) {
- return {}
- }
-
// OpenAI 推理参数
if (isSupportedReasoningEffortOpenAIModel(model)) {
return {
diff --git a/src/renderer/src/aiCore/utils/websearch.ts b/src/renderer/src/aiCore/utils/websearch.ts
index 630de43d73..fde4ff534d 100644
--- a/src/renderer/src/aiCore/utils/websearch.ts
+++ b/src/renderer/src/aiCore/utils/websearch.ts
@@ -78,6 +78,7 @@ export function buildProviderBuiltinWebSearchConfig(
}
}
case 'xai': {
+ const excludeDomains = mapRegexToPatterns(webSearchConfig.excludeDomains)
return {
xai: {
maxSearchResults: webSearchConfig.maxResults,
@@ -85,7 +86,7 @@ export function buildProviderBuiltinWebSearchConfig(
sources: [
{
type: 'web',
- excludedWebsites: mapRegexToPatterns(webSearchConfig.excludeDomains)
+ excludedWebsites: excludeDomains.slice(0, Math.min(excludeDomains.length, 5))
},
{ type: 'news' },
{ type: 'x' }
diff --git a/src/renderer/src/assets/images/apps/huggingchat.svg b/src/renderer/src/assets/images/apps/huggingchat.svg
index 49765f6468..c79e09a8f5 100644
--- a/src/renderer/src/assets/images/apps/huggingchat.svg
+++ b/src/renderer/src/assets/images/apps/huggingchat.svg
@@ -1,14 +1,4 @@
-
@@ -466,7 +466,7 @@ export const AgentModal: React.FC
= ({ agent, isOpen: _isOpen, onClose: _
/>
- {t('common.close')}
+ {t('common.close')}
{isEditing(agent) ? t('common.confirm') : t('common.add')}
diff --git a/src/renderer/src/components/Popups/agent/SessionModal.tsx b/src/renderer/src/components/Popups/agent/SessionModal.tsx
index 368c943a1c..ae9aebebc7 100644
--- a/src/renderer/src/components/Popups/agent/SessionModal.tsx
+++ b/src/renderer/src/components/Popups/agent/SessionModal.tsx
@@ -305,7 +305,7 @@ export const SessionModal: React.FC = ({
- {t('common.close')}
+ {t('common.close')}
{isEditing(session) ? t('common.confirm') : t('common.add')}
diff --git a/src/renderer/src/components/Preview/ImageToolButton.tsx b/src/renderer/src/components/Preview/ImageToolButton.tsx
index f836d6ec35..18eafa82de 100644
--- a/src/renderer/src/components/Preview/ImageToolButton.tsx
+++ b/src/renderer/src/components/Preview/ImageToolButton.tsx
@@ -4,13 +4,15 @@ import { memo } from 'react'
interface ImageToolButtonProps {
tooltip: string
icon: React.ReactNode
- onPress: () => void
+ onClick: () => void
}
-const ImageToolButton = ({ tooltip, icon, onPress }: ImageToolButtonProps) => {
+const ImageToolButton = ({ tooltip, icon, onClick }: ImageToolButtonProps) => {
return (
-
+
+ {icon}
+
)
}
diff --git a/src/renderer/src/components/Preview/ImageToolbar.tsx b/src/renderer/src/components/Preview/ImageToolbar.tsx
index ffd4dff153..11d9695c25 100644
--- a/src/renderer/src/components/Preview/ImageToolbar.tsx
+++ b/src/renderer/src/components/Preview/ImageToolbar.tsx
@@ -36,9 +36,9 @@ const ImageToolbar = ({ pan, zoom, dialog, className }: ImageToolbarProps) => {
}
- onPress={() => pan(0, -panDistance)}
+ onClick={() => pan(0, -panDistance)}
/>
- } onPress={dialog} />
+ } onClick={dialog} />
{/* Left, Reset, Right */}
@@ -46,13 +46,13 @@ const ImageToolbar = ({ pan, zoom, dialog, className }: ImageToolbarProps) => {
}
- onPress={() => pan(-panDistance, 0)}
+ onClick={() => pan(-panDistance, 0)}
/>
- } onPress={handleReset} />
+ } onClick={handleReset} />
}
- onPress={() => pan(panDistance, 0)}
+ onClick={() => pan(panDistance, 0)}
/>
@@ -61,17 +61,17 @@ const ImageToolbar = ({ pan, zoom, dialog, className }: ImageToolbarProps) => {
}
- onPress={() => zoom(-zoomDelta)}
+ onClick={() => zoom(-zoomDelta)}
/>
}
- onPress={() => pan(0, panDistance)}
+ onClick={() => pan(0, panDistance)}
/>
}
- onPress={() => zoom(zoomDelta)}
+ onClick={() => zoom(zoomDelta)}
/>
diff --git a/src/renderer/src/components/Preview/__tests__/ImageToolButton.test.tsx b/src/renderer/src/components/Preview/__tests__/ImageToolButton.test.tsx
index 19e0c60c00..c4cd44d00c 100644
--- a/src/renderer/src/components/Preview/__tests__/ImageToolButton.test.tsx
+++ b/src/renderer/src/components/Preview/__tests__/ImageToolButton.test.tsx
@@ -30,7 +30,7 @@ describe('ImageToolButton', () => {
const defaultProps = {
tooltip: 'Test tooltip',
icon: Icon,
- onPress: vi.fn()
+ onClick: vi.fn()
}
it('should match snapshot', () => {
diff --git a/src/renderer/src/components/RichEditor/components/ImageUploader.tsx b/src/renderer/src/components/RichEditor/components/ImageUploader.tsx
index 4ff13843dd..d85db79687 100644
--- a/src/renderer/src/components/RichEditor/components/ImageUploader.tsx
+++ b/src/renderer/src/components/RichEditor/components/ImageUploader.tsx
@@ -175,10 +175,10 @@ export const ImageUploader: React.FC = ({ onImageSelect, vis
prefix={}
style={{ flex: 1 }}
/>
- setUrlInput('')} className="border border-gray-300 bg-white text-gray-700">
+ setUrlInput('')} className="border border-gray-300 bg-white text-gray-700">
{t('common.clear')}
-
+
{t('richEditor.imageUploader.embedImage')}
diff --git a/src/renderer/src/components/RichEditor/components/LinkEditor.tsx b/src/renderer/src/components/RichEditor/components/LinkEditor.tsx
index 12cd8aad61..cd2bff24b3 100644
--- a/src/renderer/src/components/RichEditor/components/LinkEditor.tsx
+++ b/src/renderer/src/components/RichEditor/components/LinkEditor.tsx
@@ -147,16 +147,16 @@ const LinkEditor: React.FC = ({
{showRemove && (
-
+
{t('richEditor.link.remove')}
)}
-
+
{t('common.cancel')}
-
+
{t('common.save')}
diff --git a/src/renderer/src/components/RichEditor/components/MathInputDialog.tsx b/src/renderer/src/components/RichEditor/components/MathInputDialog.tsx
index 241e1a5235..3eb4294293 100644
--- a/src/renderer/src/components/RichEditor/components/MathInputDialog.tsx
+++ b/src/renderer/src/components/RichEditor/components/MathInputDialog.tsx
@@ -149,10 +149,10 @@ const MathInputDialog: React.FC = ({
style={{ marginBottom: 12, fontFamily: 'monospace' }}
/>
-
+
{t('common.cancel')}
-
+
{t('common.confirm')}
diff --git a/src/renderer/src/components/RichEditor/extensions/code-block-shiki/CodeBlockNodeView.tsx b/src/renderer/src/components/RichEditor/extensions/code-block-shiki/CodeBlockNodeView.tsx
index b989c545cf..c8fd140ff7 100644
--- a/src/renderer/src/components/RichEditor/extensions/code-block-shiki/CodeBlockNodeView.tsx
+++ b/src/renderer/src/components/RichEditor/extensions/code-block-shiki/CodeBlockNodeView.tsx
@@ -67,14 +67,9 @@ const CodeBlockNodeView: FC = (props) => {
style={{ minWidth: 90 }}
/>
- }
- isIconOnly
- className="code-block-copy-btn"
- onPress={handleCopy}
- />
+
+
+
diff --git a/src/renderer/src/components/S3BackupManager.tsx b/src/renderer/src/components/S3BackupManager.tsx
index 84b5db8946..f4e50047de 100644
--- a/src/renderer/src/components/S3BackupManager.tsx
+++ b/src/renderer/src/components/S3BackupManager.tsx
@@ -3,7 +3,7 @@ import { Button, Tooltip } from '@cherrystudio/ui'
import { restoreFromS3 } from '@renderer/services/BackupService'
import type { S3Config } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
-import { Modal, Table } from 'antd'
+import { Modal, Space, Table } from 'antd'
import dayjs from 'dayjs'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -232,14 +232,10 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
width: 160,
render: (_: any, record: BackupFile) => (
<>
- handleRestore(record.fileName)} isDisabled={restoring || deleting}>
+ handleRestore(record.fileName)} disabled={restoring || deleting}>
{t('settings.data.s3.manager.restore')}
- handleDeleteSingle(record.fileName)}
- isDisabled={deleting || restoring}>
+ handleDeleteSingle(record.fileName)} disabled={deleting || restoring}>
{t('settings.data.s3.manager.delete.label')}
>
@@ -263,19 +259,19 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
centered
transitionName="animation-move-down"
footer={[
- } onPress={fetchBackupFiles} isDisabled={loading}>
+
+
{t('settings.data.s3.manager.refresh')}
,
}
- onPress={handleDeleteSelected}
- isDisabled={selectedRowKeys.length === 0 || deleting}
- isLoading={deleting}>
+ variant="destructive"
+ onClick={handleDeleteSelected}
+ disabled={selectedRowKeys.length === 0 || deleting}>
+
{t('settings.data.s3.manager.delete.selected', { count: selectedRowKeys.length })}
,
-
+
{t('settings.data.s3.manager.close')}
]}>
diff --git a/src/renderer/src/components/TranslateButton.tsx b/src/renderer/src/components/TranslateButton.tsx
index 7626df97f6..8ac342ca6e 100644
--- a/src/renderer/src/components/TranslateButton.tsx
+++ b/src/renderer/src/components/TranslateButton.tsx
@@ -68,13 +68,12 @@ const TranslateButton: FC = ({ text, onTranslated, disabled, style, isLoa
content={t('chat.input.translate', { target_language: getLanguageByLangcode(targetLanguage).label() })}
closeDelay={0}>
+ variant="ghost"
+ size="icon-sm"
+ className="rounded-full">
{isTranslating ? : }
diff --git a/src/renderer/src/components/UpdateDialog.tsx b/src/renderer/src/components/UpdateDialog.tsx
index ee87265c0d..9cb9b5b62b 100644
--- a/src/renderer/src/components/UpdateDialog.tsx
+++ b/src/renderer/src/components/UpdateDialog.tsx
@@ -77,13 +77,13 @@ const UpdateDialog: React.FC = ({ isOpen, onClose, releaseInf
-
+
{t('update.later')}
{
+ onClick={async () => {
await handleInstall()
onModalClose()
}}
diff --git a/src/renderer/src/components/WebdavBackupManager.tsx b/src/renderer/src/components/WebdavBackupManager.tsx
index eaf854c650..8143d244e4 100644
--- a/src/renderer/src/components/WebdavBackupManager.tsx
+++ b/src/renderer/src/components/WebdavBackupManager.tsx
@@ -239,14 +239,10 @@ export function WebdavBackupManager({
width: 160,
render: (_: any, record: BackupFile) => (
<>
- handleRestore(record.fileName)} isDisabled={restoring || deleting}>
+ handleRestore(record.fileName)} disabled={restoring || deleting}>
{t('settings.data.webdav.backup.manager.restore.text')}
- handleDeleteSingle(record.fileName)}
- isDisabled={deleting || restoring}>
+ handleDeleteSingle(record.fileName)} disabled={deleting || restoring}>
{t('settings.data.webdav.backup.manager.delete.text')}
>
@@ -270,19 +266,19 @@ export function WebdavBackupManager({
centered
transitionName="animation-move-down"
footer={[
- } onPress={fetchBackupFiles} isDisabled={loading}>
+
+
{t('settings.data.webdav.backup.manager.refresh')}
,
}
- onPress={handleDeleteSelected}
- isDisabled={selectedRowKeys.length === 0 || deleting}
- isLoading={deleting}>
+ variant="destructive"
+ onClick={handleDeleteSelected}
+ disabled={selectedRowKeys.length === 0 || deleting}>
+
{t('settings.data.webdav.backup.manager.delete.selected')} ({selectedRowKeys.length})
,
-
+
{t('common.close')}
]}>
diff --git a/src/renderer/src/config/minapps.ts b/src/renderer/src/config/minapps.ts
index 1e15d1b86f..815b3f4760 100644
--- a/src/renderer/src/config/minapps.ts
+++ b/src/renderer/src/config/minapps.ts
@@ -22,6 +22,7 @@ import GithubCopilotLogo from '@renderer/assets/images/apps/github-copilot.webp?
import GoogleAppLogo from '@renderer/assets/images/apps/google.svg?url'
import GrokAppLogo from '@renderer/assets/images/apps/grok.png?url'
import GrokXAppLogo from '@renderer/assets/images/apps/grok-x.png?url'
+import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg?url'
import KimiAppLogo from '@renderer/assets/images/apps/kimi.webp?url'
import LambdaChatLogo from '@renderer/assets/images/apps/lambdachat.webp?url'
import LeChatLogo from '@renderer/assets/images/apps/lechat.png?url'
@@ -471,6 +472,16 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
style: {
padding: 6
}
+ },
+ {
+ id: 'huggingchat',
+ name: 'HuggingChat',
+ url: 'https://huggingface.co/chat/',
+ logo: HuggingChatLogo,
+ bodered: true,
+ style: {
+ padding: 6
+ }
}
]
diff --git a/src/renderer/src/config/models/default.ts b/src/renderer/src/config/models/default.ts
index 38af10c63f..f2f5845b26 100644
--- a/src/renderer/src/config/models/default.ts
+++ b/src/renderer/src/config/models/default.ts
@@ -1837,5 +1837,6 @@ export const SYSTEM_MODELS: Record =
provider: 'longcat',
group: 'LongCat'
}
- ]
+ ],
+ huggingface: []
}
diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts
index 99abc95c59..3a4d97e592 100644
--- a/src/renderer/src/config/models/reasoning.ts
+++ b/src/renderer/src/config/models/reasoning.ts
@@ -361,6 +361,12 @@ export function isSupportedThinkingTokenDoubaoModel(model?: Model): boolean {
return DOUBAO_THINKING_MODEL_REGEX.test(modelId) || DOUBAO_THINKING_MODEL_REGEX.test(model.name)
}
+export function isClaude45ReasoningModel(model: Model): boolean {
+ const modelId = getLowerBaseModelName(model.id, '/')
+ const regex = /claude-(sonnet|opus|haiku)-4(-|.)5(?:-[\w-]+)?$/i
+ return regex.test(modelId)
+}
+
export function isClaudeReasoningModel(model?: Model): boolean {
if (!model) {
return false
@@ -455,6 +461,14 @@ export const isStepReasoningModel = (model?: Model): boolean => {
return modelId.includes('step-3') || modelId.includes('step-r1-v-mini')
}
+export const isMiniMaxReasoningModel = (model?: Model): boolean => {
+ if (!model) {
+ return false
+ }
+ const modelId = getLowerBaseModelName(model.id, '/')
+ return (['minimax-m1', 'minimax-m2'] as const).some((id) => modelId.includes(id))
+}
+
export function isReasoningModel(model?: Model): boolean {
if (!model || isEmbeddingModel(model) || isRerankModel(model) || isTextToImageModel(model)) {
return false
@@ -489,8 +503,8 @@ export function isReasoningModel(model?: Model): boolean {
isStepReasoningModel(model) ||
isDeepSeekHybridInferenceModel(model) ||
isLingReasoningModel(model) ||
+ isMiniMaxReasoningModel(model) ||
modelId.includes('magistral') ||
- modelId.includes('minimax-m1') ||
modelId.includes('pangu-pro-moe') ||
modelId.includes('seed-oss')
) {
diff --git a/src/renderer/src/config/models/tooluse.ts b/src/renderer/src/config/models/tooluse.ts
index 494d0e0901..76c441e9fc 100644
--- a/src/renderer/src/config/models/tooluse.ts
+++ b/src/renderer/src/config/models/tooluse.ts
@@ -28,8 +28,9 @@ export const FUNCTION_CALLING_MODELS = [
'doubao-seed-1[.-]6(?:-[\\w-]+)?',
'kimi-k2(?:-[\\w-]+)?',
'ling-\\w+(?:-[\\w-]+)?',
- 'ring-\\w+(?:-[\\w-]+)?'
-]
+ 'ring-\\w+(?:-[\\w-]+)?',
+ 'minimax-m2'
+] as const
const FUNCTION_CALLING_EXCLUDED_MODELS = [
'aqa(?:-[\\w-]+)?',
diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts
index 98ccf8dfb9..18b3480710 100644
--- a/src/renderer/src/config/models/vision.ts
+++ b/src/renderer/src/config/models/vision.ts
@@ -83,7 +83,7 @@ export const IMAGE_ENHANCEMENT_MODELS = [
'grok-2-image(?:-[\\w-]+)?',
'qwen-image-edit',
'gpt-image-1',
- 'gemini-2.5-flash-image',
+ 'gemini-2.5-flash-image(?:-[\\w-]+)?',
'gemini-2.0-flash-preview-image-generation'
]
diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts
index aba30d80c8..a29ecbfd34 100644
--- a/src/renderer/src/config/providers.ts
+++ b/src/renderer/src/config/providers.ts
@@ -22,6 +22,7 @@ import GoogleProviderLogo from '@renderer/assets/images/providers/google.png'
import GPUStackProviderLogo from '@renderer/assets/images/providers/gpustack.svg'
import GrokProviderLogo from '@renderer/assets/images/providers/grok.png'
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
+import HuggingfaceProviderLogo from '@renderer/assets/images/providers/huggingface.webp'
import HyperbolicProviderLogo from '@renderer/assets/images/providers/hyperbolic.png'
import InfiniProviderLogo from '@renderer/assets/images/providers/infini.png'
import IntelOvmsLogo from '@renderer/assets/images/providers/intel.png'
@@ -646,6 +647,16 @@ export const SYSTEM_PROVIDERS_CONFIG: Record =
models: SYSTEM_MODELS.longcat,
isSystem: true,
enabled: false
+ },
+ huggingface: {
+ id: 'huggingface',
+ name: 'Hugging Face',
+ type: 'openai-response',
+ apiKey: '',
+ apiHost: 'https://router.huggingface.co/v1/',
+ models: [],
+ isSystem: true,
+ enabled: false
}
} as const
@@ -710,7 +721,8 @@ export const PROVIDER_LOGO_MAP: AtLeast = {
'aws-bedrock': AwsProviderLogo,
poe: 'poe', // use svg icon component
aionly: AiOnlyProviderLogo,
- longcat: LongCatProviderLogo
+ longcat: LongCatProviderLogo,
+ huggingface: HuggingfaceProviderLogo
} as const
export function getProviderLogo(providerId: string) {
@@ -1337,6 +1349,17 @@ export const PROVIDER_URLS: Record = {
docs: 'https://longcat.chat/platform/docs/zh/',
models: 'https://longcat.chat/platform/docs/zh/APIDocs.html'
}
+ },
+ huggingface: {
+ api: {
+ url: 'https://router.huggingface.co/v1/'
+ },
+ websites: {
+ official: 'https://huggingface.co/',
+ apiKey: 'https://huggingface.co/settings/tokens',
+ docs: 'https://huggingface.co/docs',
+ models: 'https://huggingface.co/models'
+ }
}
}
diff --git a/src/renderer/src/handler/NavigationHandler.tsx b/src/renderer/src/handler/NavigationHandler.tsx
index 0bdef5c992..5e1ef56113 100644
--- a/src/renderer/src/handler/NavigationHandler.tsx
+++ b/src/renderer/src/handler/NavigationHandler.tsx
@@ -1,4 +1,6 @@
import { useAppSelector } from '@renderer/store'
+import { IpcChannel } from '@shared/IpcChannel'
+import { useEffect } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useLocation, useNavigate } from 'react-router-dom'
@@ -25,6 +27,19 @@ const NavigationHandler: React.FC = () => {
}
)
+ // Listen for navigate to About page event from macOS menu
+ useEffect(() => {
+ const handleNavigateToAbout = () => {
+ navigate('/settings/about')
+ }
+
+ const removeListener = window.electron.ipcRenderer.on(IpcChannel.Windows_NavigateToAbout, handleNavigateToAbout)
+
+ return () => {
+ removeListener()
+ }
+ }, [navigate])
+
return null
}
diff --git a/src/renderer/src/hooks/useAssistantPresets.ts b/src/renderer/src/hooks/useAssistantPresets.ts
index c8571070f0..a92bc99897 100644
--- a/src/renderer/src/hooks/useAssistantPresets.ts
+++ b/src/renderer/src/hooks/useAssistantPresets.ts
@@ -1,3 +1,4 @@
+import { loggerService } from '@logger'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import {
addAssistantPreset,
@@ -8,8 +9,22 @@ import {
} from '@renderer/store/assistants'
import type { AssistantPreset, AssistantSettings } from '@renderer/types'
+const logger = loggerService.withContext('useAssistantPresets')
+
+function ensurePresetsArray(storedPresets: unknown): AssistantPreset[] {
+ if (Array.isArray(storedPresets)) {
+ return storedPresets
+ }
+ logger.warn('Unexpected data type from state.assistants.presets, falling back to empty list.', {
+ type: typeof storedPresets,
+ value: storedPresets
+ })
+ return []
+}
+
export function useAssistantPresets() {
- const presets = useAppSelector((state) => state.assistants.presets)
+ const storedPresets = useAppSelector((state) => state.assistants.presets)
+ const presets = ensurePresetsArray(storedPresets)
const dispatch = useAppDispatch()
return {
@@ -21,14 +36,23 @@ export function useAssistantPresets() {
}
export function useAssistantPreset(id: string) {
- // FIXME: undefined is not handled
- const preset = useAppSelector((state) => state.assistants.presets.find((a) => a.id === id) as AssistantPreset)
+ const storedPresets = useAppSelector((state) => state.assistants.presets)
+ const presets = ensurePresetsArray(storedPresets)
+ const preset = presets.find((a) => a.id === id)
const dispatch = useAppDispatch()
+ if (!preset) {
+ logger.warn(`Assistant preset with id ${id} not found in state.`)
+ }
+
return {
- preset,
+ preset: preset,
updateAssistantPreset: (preset: AssistantPreset) => dispatch(updateAssistantPreset(preset)),
updateAssistantPresetSettings: (settings: Partial) => {
+ if (!preset) {
+ logger.warn(`Failed to update assistant preset settings because preset with id ${id} is missing.`)
+ return
+ }
dispatch(updateAssistantPresetSettings({ assistantId: preset.id, settings }))
}
}
diff --git a/src/renderer/src/hooks/useInPlaceEdit.ts b/src/renderer/src/hooks/useInPlaceEdit.ts
index d912abd57e..675de75c7c 100644
--- a/src/renderer/src/hooks/useInPlaceEdit.ts
+++ b/src/renderer/src/hooks/useInPlaceEdit.ts
@@ -88,7 +88,7 @@ export function useInPlaceEdit(options: UseInPlaceEditOptions): UseInPlaceEditRe
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
- if (e.key === 'Enter') {
+ if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
e.preventDefault()
saveEdit()
} else if (e.key === 'Escape') {
diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts
index 4eb310d80a..3736437fc8 100644
--- a/src/renderer/src/i18n/label.ts
+++ b/src/renderer/src/i18n/label.ts
@@ -83,7 +83,9 @@ const providerKeyMap = {
zhinao: 'provider.zhinao',
zhipu: 'provider.zhipu',
poe: 'provider.poe',
- aionly: 'provider.aionly'
+ aionly: 'provider.aionly',
+ longcat: 'provider.longcat',
+ huggingface: 'provider.huggingface'
} as const
/**
@@ -158,9 +160,21 @@ export const getThemeModeLabel = (key: string): string => {
return getLabel(themeModeKeyMap, key)
}
+// const sidebarIconKeyMap = {
+// assistants: t('assistants.title'),
+// store: t('assistants.presets.title'),
+// paintings: t('paintings.title'),
+// translate: t('translate.title'),
+// minapp: t('minapp.title'),
+// knowledge: t('knowledge.title'),
+// files: t('files.title'),
+// code_tools: t('code.title'),
+// notes: t('notes.title')
+// } as const
+
const sidebarIconKeyMap = {
assistants: 'assistants.title',
- agents: 'agents.title',
+ store: 'assistants.presets.title',
paintings: 'paintings.title',
translate: 'translate.title',
minapp: 'minapp.title',
diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json
index e49eb7fa72..a5a93e4356 100644
--- a/src/renderer/src/i18n/locales/en-us.json
+++ b/src/renderer/src/i18n/locales/en-us.json
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "About",
"add": "Add",
"add_success": "Added successfully",
"advanced_settings": "Advanced Settings",
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
+ "huggingface": "Hugging Face",
"hunyuan": "Tencent Hunyuan",
"hyperbolic": "Hyperbolic",
"infini": "Infini",
"jina": "Jina",
"lanyun": "LANYUN",
"lmstudio": "LM Studio",
+ "longcat": "LongCat AI",
"minimax": "MiniMax",
"mistral": "Mistral",
"modelscope": "ModelScope",
@@ -4230,7 +4233,7 @@
"system": "System Proxy",
"title": "Proxy Mode"
},
- "tip": "[to be translated]:支持模糊匹配(*.test.com,192.168.0.0/16)"
+ "tip": "Supports wildcard matching (*.test.com, 192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "Click the tray icon to start",
diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json
index e63264127e..44f051be07 100644
--- a/src/renderer/src/i18n/locales/zh-cn.json
+++ b/src/renderer/src/i18n/locales/zh-cn.json
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "关于",
"add": "添加",
"add_success": "添加成功",
"advanced_settings": "高级设置",
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
+ "huggingface": "Hugging Face",
"hunyuan": "腾讯混元",
"hyperbolic": "Hyperbolic",
"infini": "无问芯穹",
"jina": "Jina",
"lanyun": "蓝耘科技",
"lmstudio": "LM Studio",
+ "longcat": "龙猫",
"minimax": "MiniMax",
"mistral": "Mistral",
"modelscope": "ModelScope 魔搭",
@@ -2677,11 +2680,11 @@
"go_to_settings": "去设置",
"open_accessibility_settings": "打开辅助功能设置"
},
- "description": [
- "划词助手需「辅助功能权限」才能正常工作。",
- "请点击「去设置」,并在稍后弹出的权限请求弹窗中点击 「打开系统设置」 按钮,然后在之后的应用列表中找到 「Cherry Studio」,并打开权限开关。",
- "完成设置后,请再次开启划词助手。"
- ],
+ "description": {
+ "0": "划词助手需「辅助功能权限」才能正常工作。",
+ "1": "请点击「去设置」,并在稍后弹出的权限请求弹窗中点击 「打开系统设置」 按钮,然后在之后的应用列表中找到 「Cherry Studio」,并打开权限开关。",
+ "2": "完成设置后,请再次开启划词助手。"
+ },
"title": "辅助功能权限"
},
"title": "启用"
diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json
index 074b935b34..d933db01d5 100644
--- a/src/renderer/src/i18n/locales/zh-tw.json
+++ b/src/renderer/src/i18n/locales/zh-tw.json
@@ -538,7 +538,7 @@
"context": "清除上下文 {{Command}}"
},
"new_topic": "新話題 {{Command}}",
- "paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
+ "paste_text_file_confirm": "貼到輸入框?",
"pause": "暫停",
"placeholder": "在此輸入您的訊息,按 {{key}} 傳送 - @ 選擇模型,/ 包含工具",
"placeholder_without_triggers": "在此輸入您的訊息,按 {{key}} 傳送",
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "關於",
"add": "新增",
"add_success": "新增成功",
"advanced_settings": "進階設定",
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
+ "huggingface": "Hugging Face",
"hunyuan": "騰訊混元",
"hyperbolic": "Hyperbolic",
"infini": "無問芯穹",
"jina": "Jina",
"lanyun": "藍耘",
"lmstudio": "LM Studio",
+ "longcat": "龍貓",
"minimax": "MiniMax",
"mistral": "Mistral",
"modelscope": "ModelScope 魔搭",
@@ -4230,7 +4233,7 @@
"system": "系統代理伺服器",
"title": "代理伺服器模式"
},
- "tip": "[to be translated]:支持模糊匹配(*.test.com,192.168.0.0/16)"
+ "tip": "支援模糊匹配(*.test.com,192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "點選工具列圖示啟動",
diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json
index e4c07b1b07..3a44387d5d 100644
--- a/src/renderer/src/i18n/translate/de-de.json
+++ b/src/renderer/src/i18n/translate/de-de.json
@@ -22,7 +22,8 @@
},
"get": {
"error": {
- "failed": "Agent abrufen fehlgeschlagen"
+ "failed": "Agent abrufen fehlgeschlagen",
+ "null_id": "Agent ID ist leer."
}
},
"list": {
@@ -30,6 +31,11 @@
"failed": "Agent-Liste abrufen fehlgeschlagen"
}
},
+ "server": {
+ "error": {
+ "not_running": "API server is enabled but not running properly."
+ }
+ },
"session": {
"accessible_paths": {
"add": "Verzeichnis hinzufügen",
@@ -68,7 +74,8 @@
},
"get": {
"error": {
- "failed": "Sitzung abrufen fehlgeschlagen"
+ "failed": "Sitzung abrufen fehlgeschlagen",
+ "null_id": "Sitzung ID ist leer."
}
},
"label_one": "Sitzung",
@@ -237,6 +244,7 @@
"messages": {
"apiKeyCopied": "API-Schlüssel in die Zwischenablage kopiert",
"apiKeyRegenerated": "API-Schlüssel wurde neu generiert",
+ "notEnabled": "API server is not enabled.",
"operationFailed": "API-Server-Operation fehlgeschlagen:",
"restartError": "API-Server-Neustart fehlgeschlagen:",
"restartFailed": "API-Server-Neustart fehlgeschlagen:",
@@ -530,6 +538,7 @@
"context": "Kontext löschen {{Command}}"
},
"new_topic": "Neues Thema {{Command}}",
+ "paste_text_file_confirm": "In Eingabefeld einfügen?",
"pause": "Pause",
"placeholder": "Geben Sie hier eine Nachricht ein, drücken Sie {{key}} zum Senden - @ für Modellauswahl, / für Tools",
"placeholder_without_triggers": "Geben Sie hier eine Nachricht ein, drücken Sie {{key}} zum Senden",
@@ -943,6 +952,7 @@
}
},
"common": {
+ "about": "About",
"add": "Hinzufügen",
"add_success": "Erfolgreich hinzugefügt",
"advanced_settings": "Erweiterte Einstellungen",
@@ -1795,6 +1805,7 @@
"title": "Mini-Apps"
},
"minapps": {
+ "ant-ling": "Ant Ling",
"baichuan": "Baixiaoying",
"baidu-ai-search": "Baidu AI Suche",
"chatglm": "ChatGLM",
@@ -1951,6 +1962,14 @@
"rename": "Umbenennen",
"rename_changed": "Aus Sicherheitsgründen wurde der Dateiname von {{original}} zu {{final}} geändert",
"save": "In Notizen speichern",
+ "search": {
+ "both": "Name + Inhalt",
+ "content": "Inhalt",
+ "found_results": "{{count}} Ergebnisse gefunden (Name: {{nameCount}}, Inhalt: {{contentCount}})",
+ "more_matches": " Treffer",
+ "searching": "Searching...",
+ "show_less": "Weniger anzeigen"
+ },
"settings": {
"data": {
"apply": "Anwenden",
@@ -2035,6 +2054,7 @@
"provider": {
"cannot_remove_builtin": "Eingebauter Anbieter kann nicht entfernt werden",
"existing": "Anbieter existiert bereits",
+ "get_providers": "Failed to obtain available providers",
"not_found": "OCR-Anbieter nicht gefunden",
"update_failed": "Konfiguration aktualisieren fehlgeschlagen"
},
@@ -2098,6 +2118,8 @@
"install_code_103": "OVMS Runtime herunterladen fehlgeschlagen",
"install_code_104": "OVMS Runtime entpacken fehlgeschlagen",
"install_code_105": "OVMS Runtime bereinigen fehlgeschlagen",
+ "install_code_106": "Failed to create run.bat",
+ "install_code_110": "Failed to clean up old OVMS runtime",
"run": "OVMS ausführen fehlgeschlagen:",
"stop": "OVMS stoppen fehlgeschlagen:"
},
@@ -2301,40 +2323,42 @@
"provider": {
"302ai": "302.AI",
"aihubmix": "AiHubMix",
- "aionly": "唯一AI (AiOnly)",
+ "aionly": "Einzige KI (AiOnly)",
"alayanew": "Alaya NeW",
"anthropic": "Anthropic",
"aws-bedrock": "AWS Bedrock",
"azure-openai": "Azure OpenAI",
- "baichuan": "百川",
- "baidu-cloud": "百度云千帆",
+ "baichuan": "Baichuan",
+ "baidu-cloud": "Baidu Cloud Qianfan",
"burncloud": "BurnCloud",
"cephalon": "Cephalon",
"cherryin": "CherryIN",
"copilot": "GitHub Copilot",
- "dashscope": "阿里云百炼",
- "deepseek": "深度求索",
+ "dashscope": "Alibaba Cloud Bailian",
+ "deepseek": "DeepSeek",
"dmxapi": "DMXAPI",
- "doubao": "火山引擎",
+ "doubao": "Volcano Engine",
"fireworks": "Fireworks",
"gemini": "Gemini",
- "gitee-ai": "模力方舟",
+ "gitee-ai": "Modellkraft Arche",
"github": "GitHub Models",
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
- "hunyuan": "腾讯混元",
+ "huggingface": "Hugging Face",
+ "hunyuan": "Tencent Hunyuan",
"hyperbolic": "Hyperbolic",
- "infini": "无问芯穹",
+ "infini": "Infini-AI",
"jina": "Jina",
- "lanyun": "蓝耘科技",
+ "lanyun": "Lanyun Technologie",
"lmstudio": "LM Studio",
+ "longcat": "Meißner Riesenhamster",
"minimax": "MiniMax",
"mistral": "Mistral",
- "modelscope": "ModelScope 魔搭",
- "moonshot": "月之暗面",
+ "modelscope": "ModelScope",
+ "moonshot": "Moonshot AI",
"new-api": "New API",
- "nvidia": "英伟达",
+ "nvidia": "NVIDIA",
"o3": "O3",
"ocoolai": "ocoolAI",
"ollama": "Ollama",
@@ -2342,22 +2366,22 @@
"openrouter": "OpenRouter",
"ovms": "Intel OVMS",
"perplexity": "Perplexity",
- "ph8": "PH8 大模型开放平台",
+ "ph8": "PH8 Großmodell-Plattform",
"poe": "Poe",
- "ppio": "PPIO 派欧云",
- "qiniu": "七牛云 AI 推理",
+ "ppio": "PPIO Cloud",
+ "qiniu": "Qiniu Cloud KI-Inferenz",
"qwenlm": "QwenLM",
- "silicon": "硅基流动",
- "stepfun": "阶跃星辰",
- "tencent-cloud-ti": "腾讯云 TI",
+ "silicon": "SiliconFlow",
+ "stepfun": "StepFun",
+ "tencent-cloud-ti": "Tencent Cloud TI",
"together": "Together",
"tokenflux": "TokenFlux",
"vertexai": "Vertex AI",
"voyageai": "Voyage AI",
- "xirang": "天翼云息壤",
- "yi": "零一万物",
- "zhinao": "360 智脑",
- "zhipu": "智谱开放平台"
+ "xirang": "China Telecom Cloud Xirang",
+ "yi": "01.AI",
+ "zhinao": "360 Zhinao",
+ "zhipu": "Zhipu AI"
},
"restore": {
"confirm": {
@@ -2656,11 +2680,11 @@
"go_to_settings": "Zu Einstellungen",
"open_accessibility_settings": "Bedienungshilfen-Einstellungen öffnen"
},
- "description": [
- "Der Textauswahl-Assistent benötigt Bedienungshilfen-Berechtigungen, um ordnungsgemäß zu funktionieren.",
- "Klicken Sie auf Zu Einstellungen und anschließend im Berechtigungsdialog auf Systemeinstellungen öffnen. Suchen Sie danach in der App-Liste Cherry Studio und aktivieren Sie den Schalter.",
- "Nach Abschluss der Einrichtung Textauswahl-Assistent erneut aktivieren."
- ],
+ "description": {
+ "0": "Der Textauswahl-Assistent benötigt Bedienungshilfen-Berechtigungen, um ordnungsgemäß zu funktionieren.",
+ "1": "Klicken Sie auf Zu Einstellungen und anschließend im Berechtigungsdialog auf Systemeinstellungen öffnen. Suchen Sie danach in der App-Liste Cherry Studio und aktivieren Sie den Schalter.",
+ "2": "Nach Abschluss der Einrichtung Textauswahl-Assistent erneut aktivieren."
+ },
"title": "Bedienungshilfen-Berechtigung"
},
"title": "Aktivieren"
@@ -3568,6 +3592,7 @@
"builtinServers": "Integrierter Server",
"builtinServersDescriptions": {
"brave_search": "MCP-Server-Implementierung mit Brave-Search-API, die sowohl Web- als auch lokale Suchfunktionen bietet. BRAVE_API_KEY-Umgebungsvariable muss konfiguriert werden",
+ "didi_mcp": "An integrated Didi MCP server implementation that provides ride-hailing services including map search, price estimation, order management, and driver tracking. Only available in mainland China. Requires the DIDI_API_KEY environment variable to be configured.",
"dify_knowledge": "MCP-Server-Implementierung von Dify, die einen einfachen API-Zugriff auf Dify bietet. Dify Key muss konfiguriert werden",
"fetch": "MCP-Server zum Abrufen von Webseiteninhalten",
"filesystem": "MCP-Server für Dateisystemoperationen (Node.js), der den Zugriff auf bestimmte Verzeichnisse ermöglicht",
@@ -4207,7 +4232,8 @@
"none": "Keinen Proxy verwenden",
"system": "System-Proxy",
"title": "Proxy-Modus"
- }
+ },
+ "tip": "Unterstützt Fuzzy-Matching (*.test.com, 192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "Klicken auf Tray-Symbol zum Starten",
diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json
index 569820776c..59b25aea2d 100644
--- a/src/renderer/src/i18n/translate/el-gr.json
+++ b/src/renderer/src/i18n/translate/el-gr.json
@@ -538,7 +538,7 @@
"context": "Καθαρισμός ενδιάμεσων {{Command}}"
},
"new_topic": "Νέο θέμα {{Command}}",
- "paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
+ "paste_text_file_confirm": "Επικόλληση στο πεδίο εισαγωγής;",
"pause": "Παύση",
"placeholder": "Εισάγετε μήνυμα εδώ...",
"placeholder_without_triggers": "Γράψτε το μήνυμά σας εδώ, πατήστε {{key}} για αποστολή",
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "σχετικά με",
"add": "Προσθέστε",
"add_success": "Η προσθήκη ήταν επιτυχής",
"advanced_settings": "Προχωρημένες ρυθμίσεις",
@@ -1962,12 +1963,12 @@
"rename_changed": "Λόγω πολιτικής ασφάλειας, το όνομα του αρχείου έχει αλλάξει από {{original}} σε {{final}}",
"save": "αποθήκευση στις σημειώσεις",
"search": {
- "both": "[to be translated]:名称+内容",
- "content": "[to be translated]:内容",
- "found_results": "[to be translated]:找到 {{count}} 个结果 (名称: {{nameCount}}, 内容: {{contentCount}})",
- "more_matches": "[to be translated]:个匹配",
- "searching": "[to be translated]:搜索中...",
- "show_less": "[to be translated]:收起"
+ "both": "Όνομα + Περιεχόμενο",
+ "content": "περιεχόμενο",
+ "found_results": "Βρέθηκαν {{count}} αποτελέσματα (όνομα: {{nameCount}}, περιεχόμενο: {{contentCount}})",
+ "more_matches": "Ταιριάζει",
+ "searching": "Αναζήτηση...",
+ "show_less": "Κλείσιμο"
},
"settings": {
"data": {
@@ -2117,8 +2118,8 @@
"install_code_103": "Η λήψη του OVMS runtime απέτυχε",
"install_code_104": "Η αποσυμπίεση του OVMS runtime απέτυχε",
"install_code_105": "Ο καθαρισμός του OVMS runtime απέτυχε",
- "install_code_106": "[to be translated]:创建 run.bat 失败",
- "install_code_110": "[to be translated]:清理旧 OVMS runtime 失败",
+ "install_code_106": "Η δημιουργία του run.bat απέτυχε",
+ "install_code_110": "Η διαγραφή του παλιού χρόνου εκτέλεσης OVMS απέτυχε",
"run": "Η εκτέλεση του OVMS απέτυχε:",
"stop": "Η διακοπή του OVMS απέτυχε:"
},
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
+ "huggingface": "Hugging Face",
"hunyuan": "Tencent Hunyuan",
"hyperbolic": "Υπερβολικός",
"infini": "Χωρίς Ερώτημα Xin Qiong",
"jina": "Jina",
"lanyun": "Λανιούν Τεχνολογία",
"lmstudio": "LM Studio",
+ "longcat": "Τσίρο",
"minimax": "MiniMax",
"mistral": "Mistral",
"modelscope": "ModelScope Magpie",
@@ -4230,7 +4233,7 @@
"system": "συστηματική προξενική",
"title": "κλίμακα προξενικής"
},
- "tip": "[to be translated]:支持模糊匹配(*.test.com,192.168.0.0/16)"
+ "tip": "Υποστήριξη ασαφούς αντιστοίχισης (*.test.com, 192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "Επιλέξτε την εικόνα στο πίνακα για να ενεργοποιήσετε",
diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json
index 96fdbf5127..70defe51da 100644
--- a/src/renderer/src/i18n/translate/es-es.json
+++ b/src/renderer/src/i18n/translate/es-es.json
@@ -538,7 +538,7 @@
"context": "Limpiar contexto {{Command}}"
},
"new_topic": "Nuevo tema {{Command}}",
- "paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
+ "paste_text_file_confirm": "¿Pegar en el cuadro de entrada?",
"pause": "Pausar",
"placeholder": "Escribe aquí tu mensaje...",
"placeholder_without_triggers": "Escribe tu mensaje aquí, presiona {{key}} para enviar",
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "sobre",
"add": "Agregar",
"add_success": "Añadido con éxito",
"advanced_settings": "Configuración avanzada",
@@ -1962,12 +1963,12 @@
"rename_changed": "Debido a políticas de seguridad, el nombre del archivo ha cambiado de {{original}} a {{final}}",
"save": "Guardar en notas",
"search": {
- "both": "[to be translated]:名称+内容",
- "content": "[to be translated]:内容",
- "found_results": "[to be translated]:找到 {{count}} 个结果 (名称: {{nameCount}}, 内容: {{contentCount}})",
- "more_matches": "[to be translated]:个匹配",
- "searching": "[to be translated]:搜索中...",
- "show_less": "[to be translated]:收起"
+ "both": "Nombre + Contenido",
+ "content": "contenido",
+ "found_results": "Se encontraron {{count}} resultados (nombre: {{nameCount}}, contenido: {{contentCount}})",
+ "more_matches": "Una coincidencia",
+ "searching": "Buscando...",
+ "show_less": "Recoger"
},
"settings": {
"data": {
@@ -2117,8 +2118,8 @@
"install_code_103": "Error al descargar el tiempo de ejecución de OVMS",
"install_code_104": "Error al descomprimir el tiempo de ejecución de OVMS",
"install_code_105": "Error al limpiar el tiempo de ejecución de OVMS",
- "install_code_106": "[to be translated]:创建 run.bat 失败",
- "install_code_110": "[to be translated]:清理旧 OVMS runtime 失败",
+ "install_code_106": "Error al crear run.bat",
+ "install_code_110": "Error al limpiar el antiguo runtime de OVMS",
"run": "Error al ejecutar OVMS:",
"stop": "Error al detener OVMS:"
},
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
+ "huggingface": "Hugging Face",
"hunyuan": "Tencent Hùnyuán",
"hyperbolic": "Hiperbólico",
"infini": "Infini",
"jina": "Jina",
"lanyun": "Tecnología Lanyun",
"lmstudio": "Estudio LM",
+ "longcat": "Totoro",
"minimax": "Minimax",
"mistral": "Mistral",
"modelscope": "ModelScope Módulo",
@@ -4230,7 +4233,7 @@
"system": "Proxy del sistema",
"title": "Modo de proxy"
},
- "tip": "[to be translated]:支持模糊匹配(*.test.com,192.168.0.0/16)"
+ "tip": "Admite coincidencia parcial (*.test.com, 192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "Haz clic en el icono de la bandeja para iniciar",
diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json
index add50fb202..305378447e 100644
--- a/src/renderer/src/i18n/translate/fr-fr.json
+++ b/src/renderer/src/i18n/translate/fr-fr.json
@@ -538,7 +538,7 @@
"context": "Effacer le contexte {{Command}}"
},
"new_topic": "Nouveau sujet {{Command}}",
- "paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
+ "paste_text_file_confirm": "Coller dans la zone de saisie ?",
"pause": "Pause",
"placeholder": "Entrez votre message ici...",
"placeholder_without_triggers": "Tapez votre message ici, appuyez sur {{key}} pour envoyer",
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "À propos",
"add": "Ajouter",
"add_success": "Ajout réussi",
"advanced_settings": "Paramètres avancés",
@@ -1962,12 +1963,12 @@
"rename_changed": "En raison de la politique de sécurité, le nom du fichier a été changé de {{original}} à {{final}}",
"save": "sauvegarder dans les notes",
"search": {
- "both": "[to be translated]:名称+内容",
- "content": "[to be translated]:内容",
- "found_results": "[to be translated]:找到 {{count}} 个结果 (名称: {{nameCount}}, 内容: {{contentCount}})",
- "more_matches": "[to be translated]:个匹配",
- "searching": "[to be translated]:搜索中...",
- "show_less": "[to be translated]:收起"
+ "both": "Nom + Contenu",
+ "content": "contenu",
+ "found_results": "{{count}} résultat(s) trouvé(s) (nom : {{nameCount}}, contenu : {{contentCount}})",
+ "more_matches": "Correspondance",
+ "searching": "Recherche en cours...",
+ "show_less": "Replier"
},
"settings": {
"data": {
@@ -2117,8 +2118,8 @@
"install_code_103": "Échec du téléchargement du runtime OVMS",
"install_code_104": "Échec de la décompression du runtime OVMS",
"install_code_105": "Échec du nettoyage du runtime OVMS",
- "install_code_106": "[to be translated]:创建 run.bat 失败",
- "install_code_110": "[to be translated]:清理旧 OVMS runtime 失败",
+ "install_code_106": "Échec de la création de run.bat",
+ "install_code_110": "Échec du nettoyage de l'ancien runtime OVMS",
"run": "Échec de l'exécution d'OVMS :",
"stop": "Échec de l'arrêt d'OVMS :"
},
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
+ "huggingface": "Hugging Face",
"hunyuan": "Tencent HunYuan",
"hyperbolic": "Hyperbolique",
"infini": "Sans Frontières Céleste",
"jina": "Jina",
"lanyun": "Technologie Lan Yun",
"lmstudio": "Studio LM",
+ "longcat": "Mon voisin Totoro",
"minimax": "MiniMax",
"mistral": "Mistral",
"modelscope": "ModelScope MoDa",
@@ -4230,7 +4233,7 @@
"system": "Proxy système",
"title": "Mode de proxy"
},
- "tip": "[to be translated]:支持模糊匹配(*.test.com,192.168.0.0/16)"
+ "tip": "Prise en charge de la correspondance floue (*.test.com, 192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "Cliquez sur l'icône dans la barre d'état système pour démarrer",
diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json
index 154a69edf5..6e66ace09f 100644
--- a/src/renderer/src/i18n/translate/ja-jp.json
+++ b/src/renderer/src/i18n/translate/ja-jp.json
@@ -538,7 +538,7 @@
"context": "コンテキストをクリア {{Command}}"
},
"new_topic": "新しいトピック {{Command}}",
- "paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
+ "paste_text_file_confirm": "入力欄に貼り付けますか?",
"pause": "一時停止",
"placeholder": "ここにメッセージを入力し、{{key}} を押して送信...",
"placeholder_without_triggers": "ここにメッセージを入力し、{{key}} を押して送信...",
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "について",
"add": "追加",
"add_success": "追加成功",
"advanced_settings": "詳細設定",
@@ -1962,12 +1963,12 @@
"rename_changed": "セキュリティポリシーにより、ファイル名は{{original}}から{{final}}に変更されました",
"save": "メモに保存する",
"search": {
- "both": "[to be translated]:名称+内容",
- "content": "[to be translated]:内容",
- "found_results": "[to be translated]:找到 {{count}} 个结果 (名称: {{nameCount}}, 内容: {{contentCount}})",
- "more_matches": "[to be translated]:个匹配",
- "searching": "[to be translated]:搜索中...",
- "show_less": "[to be translated]:收起"
+ "both": "名称+内容",
+ "content": "内容",
+ "found_results": "{{count}} 件の結果が見つかりました(名称: {{nameCount}}、内容: {{contentCount}})",
+ "more_matches": "一致",
+ "searching": "検索中...",
+ "show_less": "閉じる"
},
"settings": {
"data": {
@@ -2117,8 +2118,8 @@
"install_code_103": "OVMSランタイムのダウンロードに失敗しました",
"install_code_104": "OVMSランタイムの解凍に失敗しました",
"install_code_105": "OVMSランタイムのクリーンアップに失敗しました",
- "install_code_106": "[to be translated]:创建 run.bat 失败",
- "install_code_110": "[to be translated]:清理旧 OVMS runtime 失败",
+ "install_code_106": "run.bat の作成に失敗しました",
+ "install_code_110": "古いOVMSランタイムのクリーンアップに失敗しました",
"run": "OVMSの実行に失敗しました:",
"stop": "OVMSの停止に失敗しました:"
},
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
+ "huggingface": "ハギングフェイス",
"hunyuan": "腾讯混元",
"hyperbolic": "Hyperbolic",
"infini": "Infini",
"jina": "Jina",
"lanyun": "LANYUN",
"lmstudio": "LM Studio",
+ "longcat": "トトロ",
"minimax": "MiniMax",
"mistral": "Mistral",
"modelscope": "ModelScope",
@@ -4230,7 +4233,7 @@
"system": "システムプロキシ",
"title": "プロキシモード"
},
- "tip": "[to be translated]:支持模糊匹配(*.test.com,192.168.0.0/16)"
+ "tip": "ワイルドカード一致をサポート (*.test.com, 192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "トレイアイコンをクリックして起動",
diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json
index 75faafe889..4a6dc5b2b6 100644
--- a/src/renderer/src/i18n/translate/pt-pt.json
+++ b/src/renderer/src/i18n/translate/pt-pt.json
@@ -538,7 +538,7 @@
"context": "Limpar contexto {{Command}}"
},
"new_topic": "Novo tópico {{Command}}",
- "paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
+ "paste_text_file_confirm": "Colar na caixa de entrada?",
"pause": "Pausar",
"placeholder": "Digite sua mensagem aqui...",
"placeholder_without_triggers": "Escreve a tua mensagem aqui, pressiona {{key}} para enviar",
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "sobre",
"add": "Adicionar",
"add_success": "Adicionado com sucesso",
"advanced_settings": "Configurações Avançadas",
@@ -1962,12 +1963,12 @@
"rename_changed": "Devido às políticas de segurança, o nome do arquivo foi alterado de {{original}} para {{final}}",
"save": "salvar em notas",
"search": {
- "both": "[to be translated]:名称+内容",
- "content": "[to be translated]:内容",
- "found_results": "[to be translated]:找到 {{count}} 个结果 (名称: {{nameCount}}, 内容: {{contentCount}})",
- "more_matches": "[to be translated]:个匹配",
- "searching": "[to be translated]:搜索中...",
- "show_less": "[to be translated]:收起"
+ "both": "Nome + Conteúdo",
+ "content": "conteúdo",
+ "found_results": "Encontrados {{count}} resultados (nome: {{nameCount}}, conteúdo: {{contentCount}})",
+ "more_matches": "uma correspondência",
+ "searching": "Pesquisando...",
+ "show_less": "Recolher"
},
"settings": {
"data": {
@@ -2117,8 +2118,8 @@
"install_code_103": "Falha ao baixar o tempo de execução do OVMS",
"install_code_104": "Falha ao descompactar o tempo de execução do OVMS",
"install_code_105": "Falha ao limpar o tempo de execução do OVMS",
- "install_code_106": "[to be translated]:创建 run.bat 失败",
- "install_code_110": "[to be translated]:清理旧 OVMS runtime 失败",
+ "install_code_106": "Falha ao criar run.bat",
+ "install_code_110": "Falha ao limpar o antigo runtime OVMS",
"run": "Falha ao executar o OVMS:",
"stop": "Falha ao parar o OVMS:"
},
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Compreender",
"groq": "Groq",
+ "huggingface": "Hugging Face",
"hunyuan": "Tencent Hún Yuán",
"hyperbolic": "Hiperbólico",
"infini": "Infinito",
"jina": "Jina",
"lanyun": "Lanyun Tecnologia",
"lmstudio": "Estúdio LM",
+ "longcat": "Totoro",
"minimax": "Minimax",
"mistral": "Mistral",
"modelscope": "ModelScope MôDá",
@@ -4230,7 +4233,7 @@
"system": "Proxy do Sistema",
"title": "Modo de Proxy"
},
- "tip": "[to be translated]:支持模糊匹配(*.test.com,192.168.0.0/16)"
+ "tip": "suporte a correspondência fuzzy (*.test.com, 192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "Clique no ícone da bandeja para iniciar",
diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json
index 4b56b7b0da..477fcb0a28 100644
--- a/src/renderer/src/i18n/translate/ru-ru.json
+++ b/src/renderer/src/i18n/translate/ru-ru.json
@@ -538,7 +538,7 @@
"context": "Очистить контекст {{Command}}"
},
"new_topic": "Новый топик {{Command}}",
- "paste_text_file_confirm": "[to be translated]:粘贴到输入框?",
+ "paste_text_file_confirm": "Вставить в поле ввода?",
"pause": "Остановить",
"placeholder": "Введите ваше сообщение здесь, нажмите {{key}} для отправки...",
"placeholder_without_triggers": "Напишите сообщение здесь, нажмите {{key}} для отправки",
@@ -952,6 +952,7 @@
}
},
"common": {
+ "about": "о",
"add": "Добавить",
"add_success": "Успешно добавлено",
"advanced_settings": "Дополнительные настройки",
@@ -1962,12 +1963,12 @@
"rename_changed": "В связи с политикой безопасности имя файла было изменено с {{Original}} на {{final}}",
"save": "Сохранить в заметки",
"search": {
- "both": "[to be translated]:名称+内容",
- "content": "[to be translated]:内容",
- "found_results": "[to be translated]:找到 {{count}} 个结果 (名称: {{nameCount}}, 内容: {{contentCount}})",
- "more_matches": "[to be translated]:个匹配",
- "searching": "[to be translated]:搜索中...",
- "show_less": "[to be translated]:收起"
+ "both": "Название+содержание",
+ "content": "содержание",
+ "found_results": "Найдено {{count}} результатов (название: {{nameCount}}, содержание: {{contentCount}})",
+ "more_matches": "совпадение",
+ "searching": "Идет поиск...",
+ "show_less": "Свернуть"
},
"settings": {
"data": {
@@ -2117,8 +2118,8 @@
"install_code_103": "Ошибка загрузки среды выполнения OVMS",
"install_code_104": "Ошибка распаковки среды выполнения OVMS",
"install_code_105": "Ошибка очистки среды выполнения OVMS",
- "install_code_106": "[to be translated]:创建 run.bat 失败",
- "install_code_110": "[to be translated]:清理旧 OVMS runtime 失败",
+ "install_code_106": "Не удалось создать run.bat",
+ "install_code_110": "Ошибка очистки старой среды выполнения OVMS",
"run": "Ошибка запуска OVMS:",
"stop": "Ошибка остановки OVMS:"
},
@@ -2344,12 +2345,14 @@
"gpustack": "GPUStack",
"grok": "Grok",
"groq": "Groq",
+ "huggingface": "Hugging Face",
"hunyuan": "Tencent Hunyuan",
"hyperbolic": "Hyperbolic",
"infini": "Infini",
"jina": "Jina",
"lanyun": "LANYUN",
"lmstudio": "LM Studio",
+ "longcat": "Тоторо",
"minimax": "MiniMax",
"mistral": "Mistral",
"modelscope": "ModelScope",
@@ -4230,7 +4233,7 @@
"system": "Системный прокси",
"title": "Режим прокси"
},
- "tip": "[to be translated]:支持模糊匹配(*.test.com,192.168.0.0/16)"
+ "tip": "Поддержка нечёткого соответствия (*.test.com, 192.168.0.0/16)"
},
"quickAssistant": {
"click_tray_to_show": "Нажмите на иконку трея для запуска",
diff --git a/src/renderer/src/pages/code/CodeToolsPage.tsx b/src/renderer/src/pages/code/CodeToolsPage.tsx
index 99d6d11708..ea98b3acb3 100644
--- a/src/renderer/src/pages/code/CodeToolsPage.tsx
+++ b/src/renderer/src/pages/code/CodeToolsPage.tsx
@@ -337,13 +337,8 @@ const CodeToolsPage: FC = () => {
alignItems: 'center'
}}>
{t('code.bun_required_message')}
- }
- onPress={handleInstallBun}
- isLoading={isInstallingBun}
- isDisabled={isInstallingBun}>
+
+
{isInstallingBun ? t('code.installing_bun') : t('code.install_bun')}