Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2

This commit is contained in:
fullex 2025-10-31 11:12:49 +08:00
commit 23f7b39753
22 changed files with 181 additions and 129 deletions

View File

@ -77,7 +77,7 @@ Please review the following critical information before submitting your Pull Req
Our core team is currently focused on significant architectural updates that involve these data structures. To ensure stability and focus during this period, contributions of this nature will be temporarily managed internally. Our core team is currently focused on significant architectural updates that involve these data structures. To ensure stability and focus during this period, contributions of this nature will be temporarily managed internally.
* **PRs that require changes to Redux state shape or IndexedDB schemas will be closed.** * **PRs that require changes to Redux state shape or IndexedDB schemas will be closed.**
* **This restriction is temporary and will be lifted with the release of `v2.0.0`.** You can track the progress of `v2.0.0` and its related discussions on issue [#10162](https://github.com/YOUR_ORG/YOUR_REPO/issues/10162) (please replace with your actual repo link). * **This restriction is temporary and will be lifted with the release of `v2.0.0`.** You can track the progress of `v2.0.0` and its related discussions on issue [#10162](https://github.com/CherryHQ/cherry-studio/pull/10162).
We highly encourage contributions for: We highly encourage contributions for:
* Bug fixes 🐞 * Bug fixes 🐞

View File

@ -81,7 +81,7 @@ git commit --signoff -m "Your commit message"
我们的核心团队目前正专注于涉及这些数据结构的关键架构更新和基础工作。为确保在此期间的稳定性与专注,此类贡献将暂时由内部进行管理。 我们的核心团队目前正专注于涉及这些数据结构的关键架构更新和基础工作。为确保在此期间的稳定性与专注,此类贡献将暂时由内部进行管理。
* **需要更改 Redux 状态结构或 IndexedDB schema 的 PR 将会被关闭。** * **需要更改 Redux 状态结构或 IndexedDB schema 的 PR 将会被关闭。**
* **此限制是临时性的,并将在 `v2.0.0` 版本发布后解除。** 您可以通过 Issue [#10162](https://github.com/YOUR_ORG/YOUR_REPO/issues/10162) (请替换为您的实际仓库链接) 跟踪 `v2.0.0` 的进展及相关讨论。 * **此限制是临时性的,并将在 `v2.0.0` 版本发布后解除。** 您可以通过 Issue [#10162](https://github.com/CherryHQ/cherry-studio/pull/10162) 跟踪 `v2.0.0` 的进展及相关讨论。
我们非常鼓励以下类型的贡献: 我们非常鼓励以下类型的贡献:
* 错误修复 🐞 * 错误修复 🐞

View File

@ -334,13 +334,13 @@
--cs-ring: hsla(84, 81%, 44%, 0.4); --cs-ring: hsla(84, 81%, 44%, 0.4);
/* UI Element Colors */ /* UI Element Colors */
--cs-secondary: hsla(0, 0%, 0%, 0.05); /* Secondary Button Background */ --cs-secondary: hsla(0, 0%, 0%, 0.05); /* Secondary Button Background */
--cs-secondary-hover: hsla(0, 0%, 0%, 0.85); --cs-secondary-hover: hsla(0, 0%, 0%, 0.85);
--cs-secondary-active: hsla(0, 0%, 0%, 0.7); --cs-secondary-active: hsla(0, 0%, 0%, 0.7);
--cs-muted: hsla(0, 0%, 0%, 0.05); /* Muted/Subtle Background */ --cs-muted: hsla(0, 0%, 0%, 0.05); /* Muted/Subtle Background */
--cs-accent: hsla(0, 0%, 0%, 0.05); /* Accent Background */ --cs-accent: hsla(0, 0%, 0%, 0.05); /* Accent Background */
--cs-ghost-hover: hsla(0, 0%, 0%, 0.05); /* Ghost Button Hover */ --cs-ghost-hover: hsla(0, 0%, 0%, 0.05); /* Ghost Button Hover */
--cs-ghost-active: hsla(0, 0%, 0%, 0.1); /* Ghost Button Active */ --cs-ghost-active: hsla(0, 0%, 0%, 0.1); /* Ghost Button Active */
/* Sidebar */ /* Sidebar */
--cs-sidebar: var(--cs-white); --cs-sidebar: var(--cs-white);
@ -390,43 +390,43 @@
/* Spacing & Sizing (合并) */ /* Spacing & Sizing (合并) */
/* ==================== */ /* ==================== */
--cs-size-5xs: 0.25rem; /* 4px */ --cs-size-5xs: 0.25rem; /* 4px */
--cs-size-4xs: 0.5rem; /* 8px */ --cs-size-4xs: 0.5rem; /* 8px */
--cs-size-3xs: 0.75rem; /* 12px */ --cs-size-3xs: 0.75rem; /* 12px */
--cs-size-2xs: 1rem; /* 16px */ --cs-size-2xs: 1rem; /* 16px */
--cs-size-xs: 1.5rem; /* 24px */ --cs-size-xs: 1.5rem; /* 24px */
--cs-size-sm: 2rem; /* 32px */ --cs-size-sm: 2rem; /* 32px */
--cs-size-md: 2.5rem; /* 40px */ --cs-size-md: 2.5rem; /* 40px */
--cs-size-lg: 3rem; /* 48px */ --cs-size-lg: 3rem; /* 48px */
--cs-size-xl: 3.5rem; /* 56px */ --cs-size-xl: 3.5rem; /* 56px */
--cs-size-2xl: 4rem; /* 64px */ --cs-size-2xl: 4rem; /* 64px */
--cs-size-3xl: 4.5rem; /* 72px */ --cs-size-3xl: 4.5rem; /* 72px */
--cs-size-4xl: 5rem; /* 80px */ --cs-size-4xl: 5rem; /* 80px */
--cs-size-5xl: 5.5rem; /* 88px */ --cs-size-5xl: 5.5rem; /* 88px */
--cs-size-6xl: 6rem; /* 96px */ --cs-size-6xl: 6rem; /* 96px */
--cs-size-7xl: 6.5rem; /* 104px */ --cs-size-7xl: 6.5rem; /* 104px */
--cs-size-8xl: 7rem; /* 112px */ --cs-size-8xl: 7rem; /* 112px */
/* ==================== */ /* ==================== */
/* Border Radius */ /* Border Radius */
/* ==================== */ /* ==================== */
--cs-radius-4xs: 0.25rem; /* 4px */ --cs-radius-4xs: 0.25rem; /* 4px */
--cs-radius-3xs: 0.5rem; /* 8px */ --cs-radius-3xs: 0.5rem; /* 8px */
--cs-radius-2xs: 0.75rem; /* 12px */ --cs-radius-2xs: 0.75rem; /* 12px */
--cs-radius-xs: 1rem; /* 16px */ --cs-radius-xs: 1rem; /* 16px */
--cs-radius-sm: 1.5rem; /* 24px */ --cs-radius-sm: 1.5rem; /* 24px */
--cs-radius-md: 2rem; /* 32px */ --cs-radius-md: 2rem; /* 32px */
--cs-radius-lg: 2.5rem; /* 40px */ --cs-radius-lg: 2.5rem; /* 40px */
--cs-radius-xl: 3rem; /* 48px */ --cs-radius-xl: 3rem; /* 48px */
--cs-radius-2xl: 3.5rem; /* 56px */ --cs-radius-2xl: 3.5rem; /* 56px */
--cs-radius-3xl: 4rem; /* 64px */ --cs-radius-3xl: 4rem; /* 64px */
--cs-radius-round: 999px; /* 保持 px因为是特殊值 */ --cs-radius-round: 999px; /* 保持 px因为是特殊值 */
/* Border Width */ /* Border Width */
--cs-border-width-sm: 1px; /* 1px */ --cs-border-width-sm: 1px; /* 1px */
--cs-border-width-md: 2px; /* 2px */ --cs-border-width-md: 2px; /* 2px */
--cs-border-width-lg: 3px; /* 3px */ --cs-border-width-lg: 3px; /* 3px */
/* ==================== */ /* ==================== */
/* Typography */ /* Typography */
@ -442,43 +442,43 @@
--cs-font-weight-bold: 700; --cs-font-weight-bold: 700;
/* Font Sizes - Body */ /* Font Sizes - Body */
--cs-font-size-body-xs: 0.75rem; /* 12px */ --cs-font-size-body-xs: 0.75rem; /* 12px */
--cs-font-size-body-sm: 0.875rem; /* 14px */ --cs-font-size-body-sm: 0.875rem; /* 14px */
--cs-font-size-body-md: 1rem; /* 16px */ --cs-font-size-body-md: 1rem; /* 16px */
--cs-font-size-body-lg: 1.125rem; /* 18px */ --cs-font-size-body-lg: 1.125rem; /* 18px */
/* Font Sizes - Heading */ /* Font Sizes - Heading */
--cs-font-size-heading-xs: 1.25rem; /* 20px */ --cs-font-size-heading-xs: 1.25rem; /* 20px */
--cs-font-size-heading-sm: 1.5rem; /* 24px */ --cs-font-size-heading-sm: 1.5rem; /* 24px */
--cs-font-size-heading-md: 2rem; /* 32px */ --cs-font-size-heading-md: 2rem; /* 32px */
--cs-font-size-heading-lg: 2.5rem; /* 40px */ --cs-font-size-heading-lg: 2.5rem; /* 40px */
--cs-font-size-heading-xl: 3rem; /* 48px */ --cs-font-size-heading-xl: 3rem; /* 48px */
--cs-font-size-heading-2xl: 3.75rem; /* 60px */ --cs-font-size-heading-2xl: 3.75rem; /* 60px */
/* Line Heights - Body */ /* Line Heights - Body */
--cs-line-height-body-xs: 1.25rem; /* 20px */ --cs-line-height-body-xs: 1.25rem; /* 20px */
--cs-line-height-body-sm: 1.5rem; /* 24px */ --cs-line-height-body-sm: 1.5rem; /* 24px */
--cs-line-height-body-md: 1.5rem; /* 24px */ --cs-line-height-body-md: 1.5rem; /* 24px */
--cs-line-height-body-lg: 1.75rem; /* 28px */ --cs-line-height-body-lg: 1.75rem; /* 28px */
/* Line Heights - Heading */ /* Line Heights - Heading */
--cs-line-height-heading-xs: 2rem; /* 32px */ --cs-line-height-heading-xs: 2rem; /* 32px */
--cs-line-height-heading-sm: 2.5rem; /* 40px */ --cs-line-height-heading-sm: 2.5rem; /* 40px */
--cs-line-height-heading-md: 3rem; /* 48px */ --cs-line-height-heading-md: 3rem; /* 48px */
--cs-line-height-heading-lg: 3.75rem; /* 60px */ --cs-line-height-heading-lg: 3.75rem; /* 60px */
--cs-line-height-heading-xl: 5rem; /* 80px */ --cs-line-height-heading-xl: 5rem; /* 80px */
/* Paragraph Spacing */ /* Paragraph Spacing */
--cs-paragraph-spacing-body-xs: 0.75rem; /* 12px */ --cs-paragraph-spacing-body-xs: 0.75rem; /* 12px */
--cs-paragraph-spacing-body-sm: 0.875rem; /* 14px */ --cs-paragraph-spacing-body-sm: 0.875rem; /* 14px */
--cs-paragraph-spacing-body-md: 1rem; /* 16px */ --cs-paragraph-spacing-body-md: 1rem; /* 16px */
--cs-paragraph-spacing-body-lg: 1.125rem; /* 18px */ --cs-paragraph-spacing-body-lg: 1.125rem; /* 18px */
--cs-paragraph-spacing-heading-xs: 1.25rem; /* 20px */ --cs-paragraph-spacing-heading-xs: 1.25rem; /* 20px */
--cs-paragraph-spacing-heading-sm: 1.5rem; /* 24px */ --cs-paragraph-spacing-heading-sm: 1.5rem; /* 24px */
--cs-paragraph-spacing-heading-md: 2rem; /* 32px */ --cs-paragraph-spacing-heading-md: 2rem; /* 32px */
--cs-paragraph-spacing-heading-lg: 2.5rem; /* 40px */ --cs-paragraph-spacing-heading-lg: 2.5rem; /* 40px */
--cs-paragraph-spacing-heading-xl: 3rem; /* 48px */ --cs-paragraph-spacing-heading-xl: 3rem; /* 48px */
--cs-paragraph-spacing-heading-2xl: 3.75rem; /* 60px */ --cs-paragraph-spacing-heading-2xl: 3.75rem; /* 60px */
} }
/* ==================== */ /* ==================== */
@ -506,13 +506,13 @@
--cs-ring: hsla(84, 81%, 44%, 0.4); --cs-ring: hsla(84, 81%, 44%, 0.4);
/* UI Element Colors - Dark Mode */ /* UI Element Colors - Dark Mode */
--cs-secondary: hsla(0, 0%, 100%, 0.1); /* Secondary Button Background */ --cs-secondary: hsla(0, 0%, 100%, 0.1); /* Secondary Button Background */
--cs-secondary-hover: hsla(0, 0%, 100%, 0.2); --cs-secondary-hover: hsla(0, 0%, 100%, 0.2);
--cs-secondary-active: hsla(0, 0%, 100%, 0.25); --cs-secondary-active: hsla(0, 0%, 100%, 0.25);
--cs-muted: hsla(0, 0%, 100%, 0.1); /* Muted/Subtle Background */ --cs-muted: hsla(0, 0%, 100%, 0.1); /* Muted/Subtle Background */
--cs-accent: hsla(0, 0%, 100%, 0.1); /* Accent Background */ --cs-accent: hsla(0, 0%, 100%, 0.1); /* Accent Background */
--cs-ghost-hover: hsla(0, 0%, 100%, 0.1); /* Ghost Button Hover */ --cs-ghost-hover: hsla(0, 0%, 100%, 0.1); /* Ghost Button Hover */
--cs-ghost-active: hsla(0, 0%, 100%, 0.15); /* Ghost Button Active */ --cs-ghost-active: hsla(0, 0%, 100%, 0.15); /* Ghost Button Active */
/* Sidebar */ /* Sidebar */
--cs-sidebar: var(--cs-black); --cs-sidebar: var(--cs-black);
@ -558,4 +558,3 @@
--cs-ghost-button-background-hover: var(--cs-ghost-hover); --cs-ghost-button-background-hover: var(--cs-ghost-hover);
--cs-ghost-button-background-active: var(--cs-ghost-active); --cs-ghost-button-background-active: var(--cs-ghost-active);
} }

View File

@ -8,7 +8,7 @@
@import 'tailwindcss'; @import 'tailwindcss';
@import 'tw-animate-css'; @import 'tw-animate-css';
@import './theme.css'; /* 已包含 design-tokens.css 和所有 cs-* 工具类 */ @import './theme.css'; /* 已包含 design-tokens.css 和所有 cs-* 工具类 */
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));

View File

@ -239,4 +239,3 @@
--border-width-cs-md: var(--cs-border-width-md); --border-width-cs-md: var(--cs-border-width-md);
--border-width-cs-lg: var(--cs-border-width-lg); --border-width-cs-lg: var(--cs-border-width-lg);
} }

View File

@ -1,7 +1,8 @@
import '@renderer/databases' import '@renderer/databases'
import { Spinner } from '@heroui/react'
import type { FC } from 'react' import type { FC } from 'react'
import { useMemo } from 'react' import { lazy, Suspense, useMemo } from 'react'
import { HashRouter, Route, Routes } from 'react-router-dom' import { HashRouter, Route, Routes } from 'react-router-dom'
import Sidebar from './components/app/Sidebar' import Sidebar from './components/app/Sidebar'
@ -9,18 +10,25 @@ import { ErrorBoundary } from './components/ErrorBoundary'
import TabsContainer from './components/Tab/TabContainer' import TabsContainer from './components/Tab/TabContainer'
import NavigationHandler from './handler/NavigationHandler' import NavigationHandler from './handler/NavigationHandler'
import { useNavbarPosition } from './hooks/useNavbar' import { useNavbarPosition } from './hooks/useNavbar'
import CodeToolsPage from './pages/code/CodeToolsPage'
import FilesPage from './pages/files/FilesPage' const HomePage = lazy(() => import('./pages/home/HomePage'))
import HomePage from './pages/home/HomePage' const AssistantPresetsPage = lazy(() => import('./pages/store/assistants/presets/AssistantPresetsPage'))
import KnowledgePage from './pages/knowledge/KnowledgePage' const PaintingsRoutePage = lazy(() => import('./pages/paintings/PaintingsRoutePage'))
import LaunchpadPage from './pages/launchpad/LaunchpadPage' const TranslatePage = lazy(() => import('./pages/translate/TranslatePage'))
import MinAppPage from './pages/minapps/MinAppPage' const FilesPage = lazy(() => import('./pages/files/FilesPage'))
import MinAppsPage from './pages/minapps/MinAppsPage' const NotesPage = lazy(() => import('./pages/notes/NotesPage'))
import NotesPage from './pages/notes/NotesPage' const KnowledgePage = lazy(() => import('./pages/knowledge/KnowledgePage'))
import PaintingsRoutePage from './pages/paintings/PaintingsRoutePage' const MinAppPage = lazy(() => import('./pages/minapps/MinAppPage'))
import SettingsPage from './pages/settings/SettingsPage' const MinAppsPage = lazy(() => import('./pages/minapps/MinAppsPage'))
import AssistantPresetsPage from './pages/store/assistants/presets/AssistantPresetsPage' const CodeToolsPage = lazy(() => import('./pages/code/CodeToolsPage'))
import TranslatePage from './pages/translate/TranslatePage' const SettingsPage = lazy(() => import('./pages/settings/SettingsPage'))
const LaunchpadPage = lazy(() => import('./pages/launchpad/LaunchpadPage'))
const RouterFallback: FC = () => (
<div className="flex h-full w-full items-center justify-center">
<Spinner color="primary" size="lg" label="Loading" />
</div>
)
const Router: FC = () => { const Router: FC = () => {
const { navbarPosition } = useNavbarPosition() const { navbarPosition } = useNavbarPosition()
@ -28,20 +36,22 @@ const Router: FC = () => {
const routes = useMemo(() => { const routes = useMemo(() => {
return ( return (
<ErrorBoundary> <ErrorBoundary>
<Routes> <Suspense fallback={<RouterFallback />}>
<Route path="/" element={<HomePage />} /> <Routes>
<Route path="/store" element={<AssistantPresetsPage />} /> <Route path="/" element={<HomePage />} />
<Route path="/paintings/*" element={<PaintingsRoutePage />} /> <Route path="/store" element={<AssistantPresetsPage />} />
<Route path="/translate" element={<TranslatePage />} /> <Route path="/paintings/*" element={<PaintingsRoutePage />} />
<Route path="/files" element={<FilesPage />} /> <Route path="/translate" element={<TranslatePage />} />
<Route path="/notes" element={<NotesPage />} /> <Route path="/files" element={<FilesPage />} />
<Route path="/knowledge" element={<KnowledgePage />} /> <Route path="/notes" element={<NotesPage />} />
<Route path="/apps/:appId" element={<MinAppPage />} /> <Route path="/knowledge" element={<KnowledgePage />} />
<Route path="/apps" element={<MinAppsPage />} /> <Route path="/apps/:appId" element={<MinAppPage />} />
<Route path="/code" element={<CodeToolsPage />} /> <Route path="/apps" element={<MinAppsPage />} />
<Route path="/settings/*" element={<SettingsPage />} /> <Route path="/code" element={<CodeToolsPage />} />
<Route path="/launchpad" element={<LaunchpadPage />} /> <Route path="/settings/*" element={<SettingsPage />} />
</Routes> <Route path="/launchpad" element={<LaunchpadPage />} />
</Routes>
</Suspense>
</ErrorBoundary> </ErrorBoundary>
) )
}, []) }, [])

View File

@ -20,6 +20,7 @@ export interface EditableNumberProps {
suffix?: string suffix?: string
prefix?: string prefix?: string
align?: 'start' | 'center' | 'end' align?: 'start' | 'center' | 'end'
formatter?: (value: number | null) => string | number
} }
const EditableNumber: FC<EditableNumberProps> = ({ const EditableNumber: FC<EditableNumberProps> = ({
@ -36,7 +37,8 @@ const EditableNumber: FC<EditableNumberProps> = ({
style, style,
className, className,
size = 'middle', size = 'middle',
align = 'end' align = 'end',
formatter
}) => { }) => {
const [isEditing, setIsEditing] = useState(false) const [isEditing, setIsEditing] = useState(false)
const [inputValue, setInputValue] = useState(value) const [inputValue, setInputValue] = useState(value)
@ -90,7 +92,7 @@ const EditableNumber: FC<EditableNumberProps> = ({
changeOnBlur={changeOnBlur} changeOnBlur={changeOnBlur}
/> />
<DisplayText style={style} className={className} $align={align} $isEditing={isEditing}> <DisplayText style={style} className={className} $align={align} $isEditing={isEditing}>
{value ?? placeholder} {formatter ? formatter(value ?? null) : (value ?? placeholder)}
</DisplayText> </DisplayText>
</Container> </Container>
) )

View File

@ -838,7 +838,7 @@
"label": "Context", "label": "Context",
"tip": "The number of previous messages to keep in the context." "tip": "The number of previous messages to keep in the context."
}, },
"max": "Max", "max": "Unlimited",
"max_tokens": { "max_tokens": {
"confirm": "Set max tokens", "confirm": "Set max tokens",
"confirm_content": "Set the maximum number of tokens the model can generate. Need to consider the context limit of the model, otherwise an error will be reported", "confirm_content": "Set the maximum number of tokens the model can generate. Need to consider the context limit of the model, otherwise an error will be reported",
@ -1051,6 +1051,7 @@
"copied": "Copied", "copied": "Copied",
"copy": "Copy", "copy": "Copy",
"copy_failed": "Copy failed", "copy_failed": "Copy failed",
"current": "Current",
"cut": "Cut", "cut": "Cut",
"default": "Default", "default": "Default",
"delete": "Delete", "delete": "Delete",

View File

@ -1051,6 +1051,7 @@
"copied": "已复制", "copied": "已复制",
"copy": "复制", "copy": "复制",
"copy_failed": "复制失败", "copy_failed": "复制失败",
"current": "当前",
"cut": "剪切", "cut": "剪切",
"default": "默认", "default": "默认",
"delete": "删除", "delete": "删除",

View File

@ -838,7 +838,7 @@
"label": "上下文", "label": "上下文",
"tip": "在上下文中保留的前幾則訊息" "tip": "在上下文中保留的前幾則訊息"
}, },
"max": "最大", "max": "不限",
"max_tokens": { "max_tokens": {
"confirm": "設置最大 Token 數", "confirm": "設置最大 Token 數",
"confirm_content": "設置單次交互所用的最大 Token 數,會影響返回結果的長度。要根據模型上下文限制來設定,否則會發生錯誤", "confirm_content": "設置單次交互所用的最大 Token 數,會影響返回結果的長度。要根據模型上下文限制來設定,否則會發生錯誤",
@ -1051,6 +1051,7 @@
"copied": "已複製", "copied": "已複製",
"copy": "複製", "copy": "複製",
"copy_failed": "複製失敗", "copy_failed": "複製失敗",
"current": "当前",
"cut": "剪下", "cut": "剪下",
"default": "預設", "default": "預設",
"delete": "刪除", "delete": "刪除",

View File

@ -1051,6 +1051,7 @@
"copied": "Kopiert", "copied": "Kopiert",
"copy": "Kopieren", "copy": "Kopieren",
"copy_failed": "Kopieren fehlgeschlagen", "copy_failed": "Kopieren fehlgeschlagen",
"current": "Aktuell",
"cut": "Ausschneiden", "cut": "Ausschneiden",
"default": "Standard", "default": "Standard",
"delete": "Löschen", "delete": "Löschen",

View File

@ -838,7 +838,7 @@
"label": "Πλήθος ενδιάμεσων", "label": "Πλήθος ενδιάμεσων",
"tip": "Πλήθος των μηνυμάτων που θα παραμείνουν στα ενδιάμεσα, όσο μεγαλύτερο είναι το αριθμός, τόσο μεγαλύτερο είναι το μήκος του ενδιάμεσου και τόσο περισσότερα tokens χρησιμοποιούνται. Συνομιλία συνήθως συνιστάται μεταξύ 5-10" "tip": "Πλήθος των μηνυμάτων που θα παραμείνουν στα ενδιάμεσα, όσο μεγαλύτερο είναι το αριθμός, τόσο μεγαλύτερο είναι το μήκος του ενδιάμεσου και τόσο περισσότερα tokens χρησιμοποιούνται. Συνομιλία συνήθως συνιστάται μεταξύ 5-10"
}, },
"max": "Όχι ορισμένο", "max": "άπειρος",
"max_tokens": { "max_tokens": {
"confirm": "Ενεργοποίηση περιορισμού μήκους μηνύματος", "confirm": "Ενεργοποίηση περιορισμού μήκους μηνύματος",
"confirm_content": "Μετά την ενεργοποίηση του περιορισμού μήκους μηνύματος, ο μέγιστος αριθμός των tokens που χρησιμοποιούνται κάθε φορά, θα επηρεάζει το μήκος της απάντησης. Πρέπει να το ρυθμίζετε βάσει των περιορισμών του πλαισίου του μοντέλου, διαφορετικά θα σφάλλεται.", "confirm_content": "Μετά την ενεργοποίηση του περιορισμού μήκους μηνύματος, ο μέγιστος αριθμός των tokens που χρησιμοποιούνται κάθε φορά, θα επηρεάζει το μήκος της απάντησης. Πρέπει να το ρυθμίζετε βάσει των περιορισμών του πλαισίου του μοντέλου, διαφορετικά θα σφάλλεται.",
@ -1051,6 +1051,7 @@
"copied": "Αντιγράφηκε", "copied": "Αντιγράφηκε",
"copy": "Αντιγραφή", "copy": "Αντιγραφή",
"copy_failed": "Αποτυχία αντιγραφής", "copy_failed": "Αποτυχία αντιγραφής",
"current": "Τρέχων",
"cut": "Κοπή", "cut": "Κοπή",
"default": "Προεπιλογή", "default": "Προεπιλογή",
"delete": "Διαγραφή", "delete": "Διαγραφή",

View File

@ -1051,6 +1051,7 @@
"copied": "Copiado", "copied": "Copiado",
"copy": "Copiar", "copy": "Copiar",
"copy_failed": "Error al copiar", "copy_failed": "Error al copiar",
"current": "Actual",
"cut": "Cortar", "cut": "Cortar",
"default": "Predeterminado", "default": "Predeterminado",
"delete": "Eliminar", "delete": "Eliminar",

View File

@ -1051,6 +1051,7 @@
"copied": "Copié", "copied": "Copié",
"copy": "Copier", "copy": "Copier",
"copy_failed": "Échec de la copie", "copy_failed": "Échec de la copie",
"current": "Actuel",
"cut": "Couper", "cut": "Couper",
"default": "Défaut", "default": "Défaut",
"delete": "Supprimer", "delete": "Supprimer",

View File

@ -838,7 +838,7 @@
"label": "コンテキスト", "label": "コンテキスト",
"tip": "コンテキストに保持する以前のメッセージの数" "tip": "コンテキストに保持する以前のメッセージの数"
}, },
"max": "最大", "max": "制限なし",
"max_tokens": { "max_tokens": {
"confirm": "最大トークン数", "confirm": "最大トークン数",
"confirm_content": "最大トークン数を設定すると、モデルが生成できる最大トークン数が制限されます。これにより、返される結果の長さに影響が出る可能性があります。モデルのコンテキスト制限に基づいて設定する必要があります。そうしないとエラーが発生します", "confirm_content": "最大トークン数を設定すると、モデルが生成できる最大トークン数が制限されます。これにより、返される結果の長さに影響が出る可能性があります。モデルのコンテキスト制限に基づいて設定する必要があります。そうしないとエラーが発生します",
@ -1051,6 +1051,7 @@
"copied": "コピーされました", "copied": "コピーされました",
"copy": "コピー", "copy": "コピー",
"copy_failed": "コピーに失敗しました", "copy_failed": "コピーに失敗しました",
"current": "現在",
"cut": "切り取り", "cut": "切り取り",
"default": "デフォルト", "default": "デフォルト",
"delete": "削除", "delete": "削除",

View File

@ -1051,6 +1051,7 @@
"copied": "Copiado", "copied": "Copiado",
"copy": "Copiar", "copy": "Copiar",
"copy_failed": "Falha ao copiar", "copy_failed": "Falha ao copiar",
"current": "Atual",
"cut": "Cortar", "cut": "Cortar",
"default": "Padrão", "default": "Padrão",
"delete": "Excluir", "delete": "Excluir",

View File

@ -838,7 +838,7 @@
"label": "Контекст", "label": "Контекст",
"tip": "Количество предыдущих сообщений, которые нужно сохранить в контексте." "tip": "Количество предыдущих сообщений, которые нужно сохранить в контексте."
}, },
"max": "Максимум", "max": "без ограничений",
"max_tokens": { "max_tokens": {
"confirm": "Максимальное количество токенов", "confirm": "Максимальное количество токенов",
"confirm_content": "Установить максимальное количество токенов, влияет на длину результата. Нужно учитывать контекст модели, иначе будет ошибка", "confirm_content": "Установить максимальное количество токенов, влияет на длину результата. Нужно учитывать контекст модели, иначе будет ошибка",
@ -1051,6 +1051,7 @@
"copied": "Скопировано", "copied": "Скопировано",
"copy": "Копировать", "copy": "Копировать",
"copy_failed": "Не удалось скопировать", "copy_failed": "Не удалось скопировать",
"current": "Текущий",
"cut": "Вырезать", "cut": "Вырезать",
"default": "По умолчанию", "default": "По умолчанию",
"delete": "Удалить", "delete": "Удалить",

View File

@ -6,7 +6,7 @@ import type { GlobToolInput as GlobToolInputType, GlobToolOutput as GlobToolOutp
export function GlobTool({ input, output }: { input: GlobToolInputType; output?: GlobToolOutputType }) { export function GlobTool({ input, output }: { input: GlobToolInputType; output?: GlobToolOutputType }) {
// 如果有输出,计算文件数量 // 如果有输出,计算文件数量
const fileCount = output ? output.split('\n').filter((line) => line.trim()).length : 0 const lineCount = output ? output.split('\n').filter((line) => line.trim()).length : 0
return ( return (
<AccordionItem <AccordionItem
@ -17,7 +17,7 @@ export function GlobTool({ input, output }: { input: GlobToolInputType; output?:
icon={<FolderSearch className="h-4 w-4" />} icon={<FolderSearch className="h-4 w-4" />}
label="Glob" label="Glob"
params={input.pattern} params={input.pattern}
stats={output ? `${fileCount} found` : undefined} stats={output ? `${lineCount} of output` : undefined}
/> />
}> }>
<div>{output}</div> <div>{output}</div>

View File

@ -8,20 +8,31 @@ import type { ReadToolInput as ReadToolInputType, ReadToolOutput as ReadToolOutp
import { AgentToolsType } from './types' import { AgentToolsType } from './types'
export function ReadTool({ input, output }: { input: ReadToolInputType; output?: ReadToolOutputType }) { export function ReadTool({ input, output }: { input: ReadToolInputType; output?: ReadToolOutputType }) {
// 移除 system-reminder 标签及其内容的辅助函数
const removeSystemReminderTags = (text: string): string => {
// 使用正则表达式匹配 <system-reminder> 标签及其内容,包括换行符
return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, '')
}
// 将 output 统一转换为字符串 // 将 output 统一转换为字符串
const outputString = useMemo(() => { const outputString = useMemo(() => {
if (!output) return null if (!output) return null
let processedOutput: string
// 如果是 TextOutput[] 类型,提取所有 text 内容 // 如果是 TextOutput[] 类型,提取所有 text 内容
if (Array.isArray(output)) { if (Array.isArray(output)) {
return output processedOutput = output
.filter((item): item is TextOutput => item.type === 'text') .filter((item): item is TextOutput => item.type === 'text')
.map((item) => item.text) .map((item) => removeSystemReminderTags(item.text))
.join('') .join('')
} else {
// 如果是字符串,直接使用
processedOutput = output
} }
// 如果是字符串,直接返回 // 移除 system-reminder 标签及其内容
return output return removeSystemReminderTags(processedOutput)
}, [output]) }, [output])
// 如果有输出,计算统计信息 // 如果有输出,计算统计信息

View File

@ -2,11 +2,7 @@ import { AccordionItem, Card, CardBody, Chip } from '@heroui/react'
import { CheckCircle, Circle, Clock, ListTodo } from 'lucide-react' import { CheckCircle, Circle, Clock, ListTodo } from 'lucide-react'
import { ToolTitle } from './GenericTools' import { ToolTitle } from './GenericTools'
import type { import type { TodoItem, TodoWriteToolInput as TodoWriteToolInputType } from './types'
TodoItem,
TodoWriteToolInput as TodoWriteToolInputType,
TodoWriteToolOutput as TodoWriteToolOutputType
} from './types'
import { AgentToolsType } from './types' import { AgentToolsType } from './types'
const getStatusConfig = (status: TodoItem['status']) => { const getStatusConfig = (status: TodoItem['status']) => {
@ -34,7 +30,7 @@ const getStatusConfig = (status: TodoItem['status']) => {
} }
} }
export function TodoWriteTool({ input, output }: { input: TodoWriteToolInputType; output?: TodoWriteToolOutputType }) { export function TodoWriteTool({ input }: { input: TodoWriteToolInputType }) {
const doneCount = input.todos.filter((todo) => todo.status === 'completed').length const doneCount = input.todos.filter((todo) => todo.status === 'completed').length
return ( return (
<AccordionItem <AccordionItem
@ -72,7 +68,6 @@ export function TodoWriteTool({ input, output }: { input: TodoWriteToolInputType
) )
})} })}
</div> </div>
{output}
</AccordionItem> </AccordionItem>
) )
} }

View File

@ -2,7 +2,12 @@ import { Button, DescriptionSwitch, HelpTooltip, RowFlex, Selector, type Selecto
import { useMultiplePreferences, usePreference } from '@data/hooks/usePreference' import { useMultiplePreferences, usePreference } from '@data/hooks/usePreference'
import EditableNumber from '@renderer/components/EditableNumber' import EditableNumber from '@renderer/components/EditableNumber'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant' import {
DEFAULT_CONTEXTCOUNT,
DEFAULT_MAX_TOKENS,
DEFAULT_TEMPERATURE,
MAX_CONTEXT_COUNT
} from '@renderer/config/constant'
import { isOpenAIModel } from '@renderer/config/models' import { isOpenAIModel } from '@renderer/config/models'
import { UNKNOWN } from '@renderer/config/translate' import { UNKNOWN } from '@renderer/config/translate'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
@ -214,9 +219,6 @@ const SettingsTab: FC<Props> = (props) => {
setStreamOutput(assistant?.settings?.streamOutput ?? true) setStreamOutput(assistant?.settings?.streamOutput ?? true)
}, [assistant]) }, [assistant])
const assistantContextCount = assistant?.settings?.contextCount || 20
const maxContextCount = assistantContextCount > 20 ? assistantContextCount : 20
const model = assistant.model || getDefaultModel() const model = assistant.model || getDefaultModel()
const isOpenAI = isOpenAIModel(model) const isOpenAI = isOpenAIModel(model)
@ -269,21 +271,44 @@ const SettingsTab: FC<Props> = (props) => {
) : ( ) : (
<SettingDivider /> <SettingDivider />
)} )}
<Row align="middle"> <Row align="middle" gutter={10} justify="space-between">
<SettingRowTitleSmall> <SettingRowTitleSmall>
{t('chat.settings.context_count.label')} {t('chat.settings.context_count.label')}
<HelpTooltip title={t('chat.settings.context_count.tip')} /> <HelpTooltip title={t('chat.settings.context_count.tip')} />
</SettingRowTitleSmall> </SettingRowTitleSmall>
<Col span={8}>
<EditableNumber
min={0}
max={20}
step={1}
value={contextCount}
changeOnBlur
onChange={(value) => {
if (value !== null && value >= 0 && value <= 20) {
setContextCount(value)
onContextCountChange(value)
}
}}
formatter={(value) => (value === MAX_CONTEXT_COUNT ? t('chat.settings.max') : (value ?? ''))}
style={{ width: '100%' }}
/>
</Col>
</Row> </Row>
<Row align="middle" gutter={10}> <Row align="middle" gutter={10}>
<Col span={23}> <Col span={24}>
<Slider <Slider
min={0} min={0}
max={maxContextCount} max={20}
onChange={setContextCount} onChange={setContextCount}
onChangeComplete={onContextCountChange} onChangeComplete={onContextCountChange}
value={typeof contextCount === 'number' ? contextCount : 0} value={Math.min(contextCount, 20)}
tooltip={{ open: false }}
step={1} step={1}
marks={{
0: '0',
10: '10',
20: '20'
}}
/> />
</Col> </Col>
</Row> </Row>

View File

@ -360,6 +360,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
setTimeoutTimer('contextCount_onChange', () => updateAssistantSettings({ contextCount: value }), 500) setTimeoutTimer('contextCount_onChange', () => updateAssistantSettings({ contextCount: value }), 500)
} }
}} }}
formatter={(value) => (value === MAX_CONTEXT_COUNT ? t('chat.settings.max') : (value ?? ''))}
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
</Col> </Col>
@ -374,7 +375,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
value={typeof contextCount === 'number' ? contextCount : 0} value={typeof contextCount === 'number' ? contextCount : 0}
marks={{ 0: '0', 25: '25', 50: '50', 75: '75', 100: t('chat.settings.max') }} marks={{ 0: '0', 25: '25', 50: '50', 75: '75', 100: t('chat.settings.max') }}
step={1} step={1}
tooltip={{ formatter: formatSliderTooltip }} tooltip={{ formatter: formatSliderTooltip, open: false }}
/> />
</Col> </Col>
</Row> </Row>