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

This commit is contained in:
fullex 2025-09-16 11:35:18 +08:00
commit 182ac3bc98
41 changed files with 684 additions and 667 deletions

View File

@ -3,7 +3,10 @@
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"lokalise.i18n-ally",
"bradlc.vscode-tailwindcss",
"vitest.explorer",
"oxc.oxc-vscode",
"biomejs.biome"
"biomejs.biome",
"typescriptteam.native-preview"
]
}

View File

@ -48,5 +48,6 @@
"search.exclude": {
"**/dist/**": true,
".yarn/releases/**": true
}
},
"typescript.experimental.useTsgo": true
}

View File

@ -5,3 +5,5 @@ httpTimeout: 300000
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.9.1.cjs
npmRegistryServer: https://registry.npmjs.org
npmPublishRegistry: https://registry.npmjs.org

View File

@ -9,6 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- **Prerequisites**: Node.js v22.x.x or higher, Yarn 4.9.1
- **Setup Yarn**: `corepack enable && corepack prepare yarn@4.9.1 --activate`
- **Install Dependencies**: `yarn install`
- **Add New Dependencies**: `yarn add -D` for renderer-specific dependencies, `yarn add` for others.
### Development

View File

@ -48,8 +48,8 @@
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
"analyze:main": "VISUALIZER_MAIN=true yarn build",
"typecheck": "concurrently -n \"node,web\" -c \"cyan,magenta\" \"npm run typecheck:node\" \"npm run typecheck:web\"",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsgo --noEmit -p tsconfig.web.json --composite false",
"check:i18n": "tsx scripts/check-i18n.ts",
"sync:i18n": "tsx scripts/sync-i18n.ts",
"update:i18n": "dotenv -e .env -- tsx scripts/update-i18n.ts",
@ -70,7 +70,10 @@
"format:check": "biome format && biome lint",
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
"claude": "dotenv -e .env -- claude",
"migrations:generate": "drizzle-kit generate --config ./migrations/sqlite-drizzle.config.ts"
"migrations:generate": "drizzle-kit generate --config ./migrations/sqlite-drizzle.config.ts",
"release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public",
"release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public",
"release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core npm publish --access public"
},
"dependencies": {
"@libsql/client": "0.14.0",
@ -95,10 +98,10 @@
"@agentic/exa": "^7.3.3",
"@agentic/searxng": "^7.3.3",
"@agentic/tavily": "^7.3.3",
"@ai-sdk/amazon-bedrock": "^3.0.0",
"@ai-sdk/google-vertex": "^3.0.25",
"@ai-sdk/mistral": "^2.0.0",
"@ai-sdk/perplexity": "^2.0.8",
"@ai-sdk/amazon-bedrock": "^3.0.21",
"@ai-sdk/google-vertex": "^3.0.27",
"@ai-sdk/mistral": "^2.0.14",
"@ai-sdk/perplexity": "^2.0.9",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@anthropic-ai/sdk": "^0.41.0",
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
@ -106,7 +109,7 @@
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
"@aws-sdk/client-s3": "^3.840.0",
"@biomejs/biome": "2.2.4",
"@cherrystudio/ai-core": "workspace:*",
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.16",
"@cherrystudio/embedjs": "^0.1.31",
"@cherrystudio/embedjs-libsql": "^0.1.31",
"@cherrystudio/embedjs-loader-csv": "^0.1.31",
@ -202,6 +205,7 @@
"@types/tinycolor2": "^1",
"@types/turndown": "^5.0.5",
"@types/word-extractor": "^1",
"@typescript/native-preview": "latest",
"@uiw/codemirror-extensions-langs": "^4.25.1",
"@uiw/codemirror-themes-all": "^4.25.1",
"@uiw/react-codemirror": "^4.25.1",
@ -213,7 +217,7 @@
"@viz-js/lang-dot": "^1.0.5",
"@viz-js/viz": "^3.14.0",
"@xyflow/react": "^12.4.4",
"ai": "^5.0.38",
"ai": "^5.0.44",
"antd": "patch:antd@npm%3A5.27.0#~/.yarn/patches/antd-npm-5.27.0-aa91c36546.patch",
"archiver": "^7.0.1",
"async-mutex": "^0.5.0",
@ -235,6 +239,7 @@
"diff": "^8.0.2",
"docx": "^9.0.2",
"dompurify": "^3.2.6",
"dotenv": "^17.2.2",
"dotenv-cli": "^7.4.2",
"drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.2",

View File

@ -1,6 +1,6 @@
{
"name": "@cherrystudio/ai-core",
"version": "1.0.0-alpha.14",
"version": "1.0.0-alpha.16",
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK",
"main": "dist/index.js",
"module": "dist/index.mjs",
@ -13,7 +13,15 @@
"test": "vitest run",
"test:watch": "vitest"
},
"keywords": ["ai", "sdk", "openai", "anthropic", "google", "cherry-studio", "vercel-ai-sdk"],
"keywords": [
"ai",
"sdk",
"openai",
"anthropic",
"google",
"cherry-studio",
"vercel-ai-sdk"
],
"author": "Cherry Studio",
"license": "MIT",
"repository": {
@ -28,15 +36,15 @@
"ai": "^5.0.26"
},
"dependencies": {
"@ai-sdk/anthropic": "^2.0.5",
"@ai-sdk/azure": "^2.0.16",
"@ai-sdk/deepseek": "^1.0.9",
"@ai-sdk/google": "^2.0.13",
"@ai-sdk/openai": "^2.0.26",
"@ai-sdk/openai-compatible": "^1.0.9",
"@ai-sdk/anthropic": "^2.0.17",
"@ai-sdk/azure": "^2.0.30",
"@ai-sdk/deepseek": "^1.0.17",
"@ai-sdk/google": "^2.0.14",
"@ai-sdk/openai": "^2.0.30",
"@ai-sdk/openai-compatible": "^1.0.17",
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/provider-utils": "^3.0.4",
"@ai-sdk/xai": "^2.0.9",
"@ai-sdk/provider-utils": "^3.0.9",
"@ai-sdk/xai": "^2.0.18",
"zod": "^4.1.5"
},
"devDependencies": {
@ -48,7 +56,9 @@
"engines": {
"node": ">=18.0.0"
},
"files": ["dist"],
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/index.d.ts",

View File

@ -14,11 +14,10 @@ import { SpanEntity, TokenUsage } from '@mcp-trace/trace-core'
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH } from '@shared/config/constant'
import { UpgradeChannel } from '@shared/data/preference/preferenceTypes'
import { IpcChannel } from '@shared/IpcChannel'
import { FileMetadata, OcrProvider, Provider, Shortcut, SupportedOcrFile } from '@types'
import { FileMetadata, Notification, OcrProvider, Provider, Shortcut, SupportedOcrFile } from '@types'
import checkDiskSpace from 'check-disk-space'
import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron'
import fontList from 'font-list'
import { Notification } from 'src/renderer/src/types/notification'
import { apiServerService } from './services/ApiServerService'
import appService from './services/AppService'

View File

@ -1,5 +1,5 @@
import { Notification } from '@types'
import { Notification as ElectronNotification } from 'electron'
import { Notification } from 'src/renderer/src/types/notification'
import { windowService } from './WindowService'

View File

@ -11,6 +11,7 @@ import type {
} from '@shared/data/preference/preferenceTypes'
import { UpgradeChannel } from '@shared/data/preference/preferenceTypes'
import { IpcChannel } from '@shared/IpcChannel'
import type { Notification } from '@types'
import {
AddMemoryOptions,
AssistantMessage,
@ -33,7 +34,6 @@ import {
WebDavConfig
} from '@types'
import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron'
import { Notification } from 'src/renderer/src/types/notification'
import { CreateDirectoryOptions } from 'webdav'
export function tracedInvoke(channel: string, spanContext: SpanContext | undefined, ...args: any[]) {

View File

@ -18,7 +18,7 @@ import {
import { getAssistantSettings, getDefaultModel } from '@renderer/services/AssistantService'
import { type Assistant, type MCPTool, type Provider } from '@renderer/types'
import type { StreamTextParams } from '@renderer/types/aiCoreTypes'
import type { ModelMessage } from 'ai'
import type { ModelMessage, Tool } from 'ai'
import { stepCountIs } from 'ai'
import { getAiSdkProviderId } from '../provider/factory'
@ -29,6 +29,8 @@ import { getTemperature, getTopP } from './modelParameters'
const logger = loggerService.withContext('parameterBuilder')
type ProviderDefinedTool = Extract<Tool<any, any>, { type: 'provider-defined' }>
/**
* AI SDK
*
@ -113,9 +115,9 @@ export async function buildStreamTextParams(
tools = {}
}
if (aiSdkProviderId === 'google-vertex') {
tools.google_search = vertex.tools.googleSearch({})
tools.google_search = vertex.tools.googleSearch({}) as ProviderDefinedTool
} else if (aiSdkProviderId === 'google-vertex-anthropic') {
tools.web_search = vertexAnthropic.tools.webSearch_20250305({})
tools.web_search = vertexAnthropic.tools.webSearch_20250305({}) as ProviderDefinedTool
}
}
@ -124,7 +126,7 @@ export async function buildStreamTextParams(
if (!tools) {
tools = {}
}
tools.url_context = vertex.tools.urlContext({})
tools.url_context = vertex.tools.urlContext({}) as ProviderDefinedTool
}
// 构建基础参数

View File

@ -9,7 +9,7 @@ import { JSONSchema7 } from 'json-schema'
const logger = loggerService.withContext('MCP-utils')
// Setup tools configuration based on provided parameters
export function setupToolsConfig(mcpTools?: MCPTool[]): Record<string, Tool> | undefined {
export function setupToolsConfig(mcpTools?: MCPTool[]): Record<string, Tool<any, any>> | undefined {
let tools: ToolSet = {}
if (!mcpTools?.length) {

View File

@ -121,6 +121,7 @@
border-radius: 5px;
word-break: keep-all;
white-space: pre;
text-wrap: wrap;
}
.markdown code {

View File

@ -53,6 +53,7 @@
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
--icon: #00000099;
}
.dark {
@ -87,6 +88,7 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
--icon: #ffffff99;
}
@theme inline {
@ -128,6 +130,7 @@
--color-sidebar-ring: var(--sidebar-ring);
--animate-marquee: marquee var(--duration) infinite linear;
--animate-marquee-vertical: marquee-vertical var(--duration) linear infinite;
--color-icon: var(--icon);
@keyframes marquee {
from {
transform: translateX(0);

View File

@ -0,0 +1,30 @@
import { cn } from '@heroui/react'
import { Button, ButtonProps } from 'antd'
import React, { memo } from 'react'
interface ActionIconButtonProps extends ButtonProps {
children: React.ReactNode
active?: boolean
}
/**
* A simple action button rendered as an icon
*/
const ActionIconButton: React.FC<ActionIconButtonProps> = ({ children, active = false, className, ...props }) => {
return (
<Button
type="text"
shape="circle"
className={cn(
'flex h-[30px] w-[30px] cursor-pointer flex-row items-center justify-center border-none p-0 text-base transition-all duration-300 ease-in-out [&_.anticon]:text-icon [&_.icon-a-addchat]:mb-[-2px] [&_.icon-a-addchat]:text-lg [&_.icon]:text-icon [&_.iconfont]:text-icon [&_.lucide]:text-icon',
active &&
'[&_.anticon]:text-primary! [&_.icon]:text-primary! [&_.iconfont]:text-primary! [&_.lucide]:text-primary!',
className
)}
{...props}>
{children}
</Button>
)
}
export default memo(ActionIconButton)

View File

@ -0,0 +1 @@
export { default as ActionIconButton } from './ActionIconButton'

View File

@ -1,4 +1,4 @@
import { ToolbarButton } from '@renderer/pages/home/Inputbar/Inputbar'
import { ActionIconButton } from '@renderer/components/Buttons'
import NarrowLayout from '@renderer/pages/home/Messages/NarrowLayout'
import { Tooltip } from 'antd'
import { debounce } from 'lodash'
@ -364,23 +364,23 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
<ToolBar>
{showUserToggle && (
<Tooltip title={t('button.includes_user_questions')} mouseEnterDelay={0.8} placement="bottom">
<ToolbarButton type="text" onClick={userOutlinedButtonOnClick}>
<ActionIconButton onClick={userOutlinedButtonOnClick}>
<User size={18} style={{ color: includeUser ? 'var(--color-link)' : 'var(--color-icon)' }} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
)}
<Tooltip title={t('button.case_sensitive')} mouseEnterDelay={0.8} placement="bottom">
<ToolbarButton type="text" onClick={caseSensitiveButtonOnClick}>
<ActionIconButton onClick={caseSensitiveButtonOnClick}>
<CaseSensitive
size={18}
style={{ color: isCaseSensitive ? 'var(--color-link)' : 'var(--color-icon)' }}
/>
</ToolbarButton>
</ActionIconButton>
</Tooltip>
<Tooltip title={t('button.whole_word')} mouseEnterDelay={0.8} placement="bottom">
<ToolbarButton type="text" onClick={wholeWordButtonOnClick}>
<ActionIconButton onClick={wholeWordButtonOnClick}>
<WholeWord size={18} style={{ color: isWholeWord ? 'var(--color-link)' : 'var(--color-icon)' }} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
</ToolBar>
</InputWrapper>
@ -397,15 +397,15 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
)}
</SearchResults>
<ToolBar>
<ToolbarButton type="text" onClick={prevButtonOnClick} disabled={allRanges.length === 0}>
<ActionIconButton onClick={prevButtonOnClick} disabled={allRanges.length === 0}>
<ChevronUp size={18} />
</ToolbarButton>
<ToolbarButton type="text" onClick={nextButtonOnClick} disabled={allRanges.length === 0}>
</ActionIconButton>
<ActionIconButton onClick={nextButtonOnClick} disabled={allRanges.length === 0}>
<ChevronDown size={18} />
</ToolbarButton>
<ToolbarButton type="text" onClick={closeButtonOnClick}>
</ActionIconButton>
<ActionIconButton onClick={closeButtonOnClick}>
<X size={18} />
</ToolbarButton>
</ActionIconButton>
</ToolBar>
</SearchBarContainer>
</NarrowLayout>

View File

@ -1,5 +1,18 @@
import React from 'react'
export enum QuickPanelReservedSymbol {
Root = '/',
File = 'file',
KnowledgeBase = '#',
MentionModels = '@',
QuickPhrases = 'quick-phrases',
Thinking = 'thinking',
WebSearch = '?',
Mcp = 'mcp',
McpPrompt = 'mcp-prompt',
McpResource = 'mcp-resource'
}
export type QuickPanelCloseAction = 'enter' | 'click' | 'esc' | 'outsideclick' | 'enter_empty' | string | undefined
export type QuickPanelTriggerInfo = {
type: 'input' | 'button'

View File

@ -172,7 +172,7 @@ export function useAssistant(id: string) {
(model: Model) => assistant && dispatch(setModel({ assistantId: assistant?.id, model })),
[assistant, dispatch]
),
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
updateAssistant: useCallback((assistant: Partial<Assistant>) => dispatch(updateAssistant(assistant)), [dispatch]),
updateAssistantSettings
}
}

View File

@ -362,8 +362,9 @@
"translate": "Translate to {{target_language}}",
"translating": "Translating...",
"upload": {
"attachment": "Upload attachment",
"document": "Upload document file (model does not support images)",
"label": "Upload image or document file",
"image_or_document": "Upload image or document file",
"upload_from_local": "Upload local file..."
},
"url_context": "URL Context",

View File

@ -363,8 +363,9 @@
"translate": "翻译成 {{target_language}}",
"translating": "翻译中...",
"upload": {
"attachment": "上传附件",
"document": "上传文档(模型不支持图片)",
"label": "上传图片或文档",
"image_or_document": "上传图片或文档",
"upload_from_local": "上传本地文件..."
},
"url_context": "网页上下文",

View File

@ -362,8 +362,9 @@
"translate": "翻譯成 {{target_language}}",
"translating": "翻譯中...",
"upload": {
"attachment": "上傳附件",
"document": "上傳文件(模型不支援圖片)",
"label": "上傳圖片或文件",
"image_or_document": "上傳圖片或文件",
"upload_from_local": "上傳本地文件..."
},
"url_context": "網頁上下文",

View File

@ -1,12 +1,17 @@
import { FileType } from '@renderer/types'
import { filterSupportedFiles } from '@renderer/utils/file'
import { ActionIconButton } from '@renderer/components/Buttons'
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
import { FileType, KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { filterSupportedFiles, formatFileSize } from '@renderer/utils/file'
import { Tooltip } from 'antd'
import { Paperclip } from 'lucide-react'
import { FC, useCallback, useImperativeHandle, useState } from 'react'
import dayjs from 'dayjs'
import { FileSearch, FileText, Paperclip, Upload } from 'lucide-react'
import { Dispatch, FC, SetStateAction, useCallback, useImperativeHandle, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
export interface AttachmentButtonRef {
openQuickPanel: () => void
openFileSelectDialog: () => void
}
interface Props {
@ -14,24 +19,17 @@ interface Props {
couldAddImageFile: boolean
extensions: string[]
files: FileType[]
setFiles: (files: FileType[]) => void
ToolbarButton: any
setFiles: Dispatch<SetStateAction<FileType[]>>
disabled?: boolean
}
const AttachmentButton: FC<Props> = ({
ref,
couldAddImageFile,
extensions,
files,
setFiles,
ToolbarButton,
disabled
}) => {
const AttachmentButton: FC<Props> = ({ ref, couldAddImageFile, extensions, files, setFiles, disabled }) => {
const { t } = useTranslation()
const quickPanel = useQuickPanel()
const { bases: knowledgeBases } = useKnowledgeBases()
const [selecting, setSelecting] = useState<boolean>(false)
const onSelectFile = useCallback(async () => {
const openFileSelectDialog = useCallback(async () => {
if (selecting) {
return
}
@ -70,23 +68,88 @@ const AttachmentButton: FC<Props> = ({
}
}, [extensions, files, selecting, setFiles, t])
const openKnowledgeFileList = useCallback(
(base: KnowledgeBase) => {
quickPanel.open({
title: base.name,
list: base.items
.filter((file): file is KnowledgeItem => ['file'].includes(file.type))
.map((file) => {
const fileContent = file.content as FileType
return {
label: fileContent.origin_name || fileContent.name,
description:
formatFileSize(fileContent.size) + ' · ' + dayjs(fileContent.created_at).format('YYYY-MM-DD HH:mm'),
icon: <FileText />,
isSelected: files.some((f) => f.path === fileContent.path),
action: async ({ item }) => {
item.isSelected = !item.isSelected
if (fileContent.path) {
setFiles((prevFiles) => {
const fileExists = prevFiles.some((f) => f.path === fileContent.path)
if (fileExists) {
return prevFiles.filter((f) => f.path !== fileContent.path)
} else {
return fileContent ? [...prevFiles, fileContent] : prevFiles
}
})
}
}
}
}),
symbol: QuickPanelReservedSymbol.File,
multiple: true
})
},
[files, quickPanel, setFiles]
)
const items = useMemo(() => {
return [
{
label: t('chat.input.upload.upload_from_local'),
description: '',
icon: <Upload />,
action: () => openFileSelectDialog()
},
...knowledgeBases.map((base) => {
const length = base.items?.filter(
(item): item is KnowledgeItem => ['file', 'note'].includes(item.type) && typeof item.content !== 'string'
).length
return {
label: base.name,
description: `${length} ${t('files.count')}`,
icon: <FileSearch />,
disabled: length === 0,
isMenu: true,
action: () => openKnowledgeFileList(base)
}
})
]
}, [knowledgeBases, openFileSelectDialog, openKnowledgeFileList, t])
const openQuickPanel = useCallback(() => {
onSelectFile()
}, [onSelectFile])
quickPanel.open({
title: t('chat.input.upload.attachment'),
list: items,
symbol: QuickPanelReservedSymbol.File
})
}, [items, quickPanel, t])
useImperativeHandle(ref, () => ({
openQuickPanel
openQuickPanel,
openFileSelectDialog
}))
return (
<Tooltip
placement="top"
title={couldAddImageFile ? t('chat.input.upload.label') : t('chat.input.upload.document')}
title={couldAddImageFile ? t('chat.input.upload.image_or_document') : t('chat.input.upload.document')}
mouseLeaveDelay={0}
arrow>
<ToolbarButton type="text" onClick={onSelectFile} disabled={disabled}>
<Paperclip size={18} style={{ color: files.length ? 'var(--color-primary)' : 'var(--color-icon)' }} />
</ToolbarButton>
<ActionIconButton onClick={openFileSelectDialog} active={files.length > 0} disabled={disabled}>
<Paperclip size={18} />
</ActionIconButton>
</Tooltip>
)
}

View File

@ -1,3 +1,4 @@
import { ActionIconButton } from '@renderer/components/Buttons'
import { isGenerateImageModel } from '@renderer/config/models'
import { Assistant, Model } from '@renderer/types'
import { Tooltip } from 'antd'
@ -8,11 +9,10 @@ import { useTranslation } from 'react-i18next'
interface Props {
assistant: Assistant
model: Model
ToolbarButton: any
onEnableGenerateImage: () => void
}
const GenerateImageButton: FC<Props> = ({ model, ToolbarButton, assistant, onEnableGenerateImage }) => {
const GenerateImageButton: FC<Props> = ({ model, assistant, onEnableGenerateImage }) => {
const { t } = useTranslation()
return (
@ -23,9 +23,12 @@ const GenerateImageButton: FC<Props> = ({ model, ToolbarButton, assistant, onEna
}
mouseLeaveDelay={0}
arrow>
<ToolbarButton type="text" disabled={!isGenerateImageModel(model)} onClick={onEnableGenerateImage}>
<Image size={18} color={assistant.enableGenerateImage ? 'var(--color-primary)' : 'var(--color-icon)'} />
</ToolbarButton>
<ActionIconButton
onClick={onEnableGenerateImage}
active={assistant.enableGenerateImage}
disabled={!isGenerateImageModel(model)}>
<Image size={18} />
</ActionIconButton>
</Tooltip>
)
}

View File

@ -2,25 +2,23 @@ import { HolderOutlined } from '@ant-design/icons'
import { useCache } from '@data/hooks/useCache'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { QuickPanelView, useQuickPanel } from '@renderer/components/QuickPanel'
import { ActionIconButton } from '@renderer/components/Buttons'
import { QuickPanelReservedSymbol, QuickPanelView, useQuickPanel } from '@renderer/components/QuickPanel'
import TranslateButton from '@renderer/components/TranslateButton'
import {
isAutoEnableImageGenerationModel,
isGenerateImageModel,
isGenerateImageModels,
isMandatoryWebSearchModel,
isSupportedReasoningEffortModel,
isSupportedThinkingTokenModel,
isVisionModel,
isVisionModels,
isWebSearchModel
} from '@renderer/config/models'
import db from '@renderer/databases'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
import { modelGenerating } from '@renderer/hooks/useModel'
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
import { useTimer } from '@renderer/hooks/useTimer'
import useTranslate from '@renderer/hooks/useTranslate'
@ -28,7 +26,6 @@ import { getDefaultTopic } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import FileManager from '@renderer/services/FileManager'
import { checkRateLimit, getUserMessage } from '@renderer/services/MessagesService'
import { getModelUniqId } from '@renderer/services/ModelService'
import PasteService from '@renderer/services/PasteService'
import { spanManagerService } from '@renderer/services/SpanManagerService'
import { estimateTextTokens as estimateTxtTokens, estimateUserPromptUsage } from '@renderer/services/TokenService'
@ -36,9 +33,9 @@ import { translateText } from '@renderer/services/TranslateService'
import WebSearchService from '@renderer/services/WebSearchService'
import { useAppDispatch } from '@renderer/store'
import { sendMessage as _sendMessage } from '@renderer/store/thunk/messageThunk'
import { Assistant, FileType, FileTypes, KnowledgeBase, KnowledgeItem, Model, Topic } from '@renderer/types'
import { Assistant, FileType, KnowledgeBase, Model, Topic } from '@renderer/types'
import type { MessageInputBaseParams } from '@renderer/types/newMessage'
import { classNames, delay, filterSupportedFiles, formatFileSize } from '@renderer/utils'
import { classNames, delay, filterSupportedFiles } from '@renderer/utils'
import { formatQuotedText } from '@renderer/utils/formats'
import {
getFilesFromDropEvent,
@ -46,14 +43,12 @@ import {
getTextFromDropEvent,
isSendMessageKeyPressed
} from '@renderer/utils/input'
import { isPromptToolUse, isSupportedToolUse } from '@renderer/utils/mcp-tools'
import { documentExts, imageExts, textExts } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel'
import { Button, Tooltip } from 'antd'
import { Tooltip } from 'antd'
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
import dayjs from 'dayjs'
import { debounce, isEmpty } from 'lodash'
import { CirclePause, FileSearch, FileText, Upload } from 'lucide-react'
import { CirclePause } from 'lucide-react'
import React, { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -114,7 +109,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const [textareaHeight, setTextareaHeight] = useState<number>()
const startDragY = useRef<number>(0)
const startHeight = useRef<number>(0)
const { bases: knowledgeBases } = useKnowledgeBases()
const [isMultiSelectMode] = useCache('chat.multi_select_mode')
const isVisionAssistant = useMemo(() => isVisionModel(model), [model])
const isGenerateImageAssistant = useMemo(() => isGenerateImageModel(model), [model])
@ -134,11 +128,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
[mentionedModels, isGenerateImageAssistant]
)
// 仅允许在不含图片文件时mention非视觉模型
const couldMentionNotVisionModel = useMemo(() => {
return !files.some((file) => file.type === FileTypes.IMAGE)
}, [files])
// 允许在支持视觉或生成图片时添加图片文件
const couldAddImageFile = useMemo(() => {
return isVisionSupported || isGenerateImageSupported
@ -185,8 +174,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const inputTokenCount = showInputEstimatedTokens ? tokenCount : 0
const newTopicShortcut = useShortcutDisplay('new_topic')
const cleanTopicShortcut = useShortcutDisplay('clear_topic')
const inputEmpty = isEmpty(text.trim()) && files.length === 0
_text = text
@ -279,72 +266,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
}
}, [isTranslating, text, getLanguageByLangcode, targetLanguage, setTimeoutTimer, resizeTextArea])
const openKnowledgeFileList = useCallback(
(base: KnowledgeBase) => {
quickPanel.open({
title: base.name,
list: base.items
.filter((file): file is KnowledgeItem => ['file'].includes(file.type))
.map((file) => {
const fileContent = file.content as FileType
return {
label: fileContent.origin_name || fileContent.name,
description:
formatFileSize(fileContent.size) + ' · ' + dayjs(fileContent.created_at).format('YYYY-MM-DD HH:mm'),
icon: <FileText />,
isSelected: files.some((f) => f.path === fileContent.path),
action: async ({ item }) => {
item.isSelected = !item.isSelected
if (fileContent.path) {
setFiles((prevFiles) => {
const fileExists = prevFiles.some((f) => f.path === fileContent.path)
if (fileExists) {
return prevFiles.filter((f) => f.path !== fileContent.path)
} else {
return fileContent ? [...prevFiles, fileContent] : prevFiles
}
})
}
}
}
}),
symbol: 'file',
multiple: true
})
},
[files, quickPanel]
)
const openSelectFileMenu = useCallback(() => {
quickPanel.open({
title: t('chat.input.upload.label'),
list: [
{
label: t('chat.input.upload.upload_from_local'),
description: '',
icon: <Upload />,
action: () => {
inputbarToolsRef.current?.openAttachmentQuickPanel()
}
},
...knowledgeBases.map((base) => {
const length = base.items?.filter(
(item): item is KnowledgeItem => ['file', 'note'].includes(item.type) && typeof item.content !== 'string'
).length
return {
label: base.name,
description: `${length} ${t('files.count')}`,
icon: <FileSearch />,
disabled: length === 0,
isMenu: true,
action: () => openKnowledgeFileList(base)
}
})
],
symbol: 'file'
})
}, [knowledgeBases, openKnowledgeFileList, quickPanel, t, inputbarToolsRef])
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
// 按下Tab键自动选中${xxx}
if (event.key === 'Tab' && inputFocus) {
@ -512,35 +433,31 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const lastSymbol = newText[cursorPosition - 1]
// 触发符号为 '/':若当前未打开或符号不同,则切换/打开
if (enableQuickPanelTriggers && lastSymbol === '/') {
if (quickPanel.isVisible && quickPanel.symbol !== '/') {
if (enableQuickPanelTriggers && lastSymbol === QuickPanelReservedSymbol.Root) {
if (quickPanel.isVisible && quickPanel.symbol !== QuickPanelReservedSymbol.Root) {
quickPanel.close('switch-symbol')
}
if (!quickPanel.isVisible || quickPanel.symbol !== '/') {
if (!quickPanel.isVisible || quickPanel.symbol !== QuickPanelReservedSymbol.Root) {
const quickPanelMenu =
inputbarToolsRef.current?.getQuickPanelMenu({
t,
files,
couldAddImageFile,
text: newText,
openSelectFileMenu,
translate
}) || []
quickPanel.open({
title: t('settings.quickPanel.title'),
list: quickPanelMenu,
symbol: '/'
symbol: QuickPanelReservedSymbol.Root
})
}
}
// 触发符号为 '@':若当前未打开或符号不同,则切换/打开
if (enableQuickPanelTriggers && lastSymbol === '@') {
if (quickPanel.isVisible && quickPanel.symbol !== '@') {
if (enableQuickPanelTriggers && lastSymbol === QuickPanelReservedSymbol.MentionModels) {
if (quickPanel.isVisible && quickPanel.symbol !== QuickPanelReservedSymbol.MentionModels) {
quickPanel.close('switch-symbol')
}
if (!quickPanel.isVisible || quickPanel.symbol !== '@') {
if (!quickPanel.isVisible || quickPanel.symbol !== QuickPanelReservedSymbol.MentionModels) {
inputbarToolsRef.current?.openMentionModelsPanel({
type: 'input',
position: cursorPosition - 1,
@ -549,7 +466,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
}
}
},
[enableQuickPanelTriggers, quickPanel, t, files, couldAddImageFile, openSelectFileMenu, translate]
[enableQuickPanelTriggers, quickPanel, t, translate]
)
const onPaste = useCallback(
@ -765,11 +682,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
setSelectedKnowledgeBases(showKnowledgeIcon ? (assistant.knowledge_bases ?? []) : [])
}, [assistant.id, assistant.knowledge_bases, showKnowledgeIcon])
const handleKnowledgeBaseSelect = (bases?: KnowledgeBase[]) => {
updateAssistant({ ...assistant, knowledge_bases: bases })
setSelectedKnowledgeBases(bases ?? [])
}
const handleRemoveModel = (model: Model) => {
setMentionedModels(mentionedModels.filter((m) => m.id !== model.id))
}
@ -783,10 +695,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
setSelectedKnowledgeBases(newKnowledgeBases ?? [])
}
const onEnableGenerateImage = () => {
updateAssistant({ ...assistant, enableGenerateImage: !assistant.enableGenerateImage })
}
useEffect(() => {
if (!isWebSearchModel(model) && assistant.enableWebSearch) {
updateAssistant({ ...assistant, enableWebSearch: false })
@ -806,24 +714,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
}
}, [assistant, model, updateAssistant])
const onMentionModel = useCallback(
(model: Model) => {
// 我想应该没有模型是只支持视觉而不支持文本的?
if (isVisionModel(model) || couldMentionNotVisionModel) {
setMentionedModels((prev) => {
const modelId = getModelUniqId(model)
const exists = prev.some((m) => getModelUniqId(m) === modelId)
return exists ? prev.filter((m) => getModelUniqId(m) !== modelId) : [...prev, model]
})
} else {
logger.error('Cannot add non-vision model when images are uploaded')
}
},
[couldMentionNotVisionModel]
)
const onClearMentionModels = useCallback(() => setMentionedModels([]), [setMentionedModels])
const onToggleExpanded = () => {
const currentlyExpanded = expanded || !!textareaHeight
const shouldExpand = !currentlyExpanded
@ -848,8 +738,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
}
const isExpanded = expanded || !!textareaHeight
const showThinkingButton = isSupportedThinkingTokenModel(model) || isSupportedReasoningEffortModel(model)
const showMcpTools = isSupportedToolUse(assistant) || isPromptToolUse(assistant)
if (isMultiSelectMode) {
return null
@ -921,47 +809,38 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
<Toolbar>
<InputbarTools
ref={inputbarToolsRef}
assistant={assistant}
assistantId={assistant.id}
model={model}
files={files}
extensions={supportedExts}
setFiles={setFiles}
showThinkingButton={showThinkingButton}
showKnowledgeIcon={showKnowledgeIcon && showMcpTools}
showMcpTools={showMcpTools}
selectedKnowledgeBases={selectedKnowledgeBases}
handleKnowledgeBaseSelect={handleKnowledgeBaseSelect}
setText={setText}
resizeTextArea={resizeTextArea}
mentionModels={mentionedModels}
onMentionModel={onMentionModel}
onClearMentionModels={onClearMentionModels}
couldMentionNotVisionModel={couldMentionNotVisionModel}
selectedKnowledgeBases={selectedKnowledgeBases}
setSelectedKnowledgeBases={setSelectedKnowledgeBases}
mentionedModels={mentionedModels}
setMentionedModels={setMentionedModels}
couldAddImageFile={couldAddImageFile}
onEnableGenerateImage={onEnableGenerateImage}
isExpanded={isExpanded}
onToggleExpanded={onToggleExpanded}
addNewTopic={addNewTopic}
clearTopic={clearTopic}
onNewContext={onNewContext}
newTopicShortcut={newTopicShortcut}
cleanTopicShortcut={cleanTopicShortcut}
/>
<ToolbarMenu>
<TokenCount
estimateTokenCount={estimateTokenCount}
inputTokenCount={inputTokenCount}
contextCount={contextCount}
ToolbarButton={ToolbarButton}
onClick={onNewContext}
/>
<TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} />
<SendMessageButton sendMessage={sendMessage} disabled={inputEmpty} />
{loading && (
<Tooltip placement="top" title={t('chat.input.pause')} mouseLeaveDelay={0} arrow>
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2 }}>
<ActionIconButton onClick={onPause} style={{ marginRight: -2 }}>
<CirclePause size={20} color="var(--color-error)" />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
)}
</ToolbarMenu>
@ -1076,45 +955,4 @@ const ToolbarMenu = styled.div`
gap: 6px;
`
export const ToolbarButton = styled(Button)`
width: 30px;
height: 30px;
font-size: 16px;
border-radius: 50%;
transition: all 0.3s ease;
color: var(--color-icon);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 0;
&.anticon,
&.iconfont {
transition: all 0.3s ease;
color: var(--color-icon);
}
.icon-a-addchat {
font-size: 18px;
margin-bottom: -2px;
}
&:hover {
background-color: var(--color-background-soft);
.anticon,
.iconfont {
color: var(--color-text-1);
}
}
&.active {
background-color: var(--color-primary) !important;
.anticon,
.iconfont,
.chevron-icon {
color: var(--color-white-soft);
}
&:hover {
background-color: var(--color-primary);
}
}
`
export default Inputbar

View File

@ -1,12 +1,26 @@
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
import { loggerService } from '@logger'
import { ActionIconButton } from '@renderer/components/Buttons'
import { QuickPanelListItem } from '@renderer/components/QuickPanel'
import { isGeminiModel, isGenerateImageModel, isMandatoryWebSearchModel } from '@renderer/config/models'
import {
isGeminiModel,
isGenerateImageModel,
isMandatoryWebSearchModel,
isSupportedReasoningEffortModel,
isSupportedThinkingTokenModel,
isVisionModel
} from '@renderer/config/models'
import { isSupportUrlContextProvider } from '@renderer/config/providers'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useShortcutDisplay } from '@renderer/hooks/useShortcuts'
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
import { getProviderByModel } from '@renderer/services/AssistantService'
import { getModelUniqId } from '@renderer/services/ModelService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setIsCollapsed, setToolOrder } from '@renderer/store/inputTools'
import { Assistant, FileType, KnowledgeBase, Model } from '@renderer/types'
import { FileType, FileTypes, KnowledgeBase, Model } from '@renderer/types'
import { classNames } from '@renderer/utils'
import { isPromptToolUse, isSupportedToolUse } from '@renderer/utils/mcp-tools'
import { Divider, Dropdown, Tooltip } from 'antd'
import { ItemType } from 'antd/es/menu/interface'
import {
@ -32,7 +46,6 @@ import styled from 'styled-components'
import AttachmentButton, { AttachmentButtonRef } from './AttachmentButton'
import GenerateImageButton from './GenerateImageButton'
import { ToolbarButton } from './Inputbar'
import KnowledgeBaseButton, { KnowledgeBaseButtonRef } from './KnowledgeBaseButton'
import MCPToolsButton, { MCPToolsButtonRef } from './MCPToolsButton'
import MentionModelsButton, { MentionModelsButtonRef } from './MentionModelsButton'
@ -42,47 +55,33 @@ import ThinkingButton, { ThinkingButtonRef } from './ThinkingButton'
import UrlContextButton, { UrlContextButtonRef } from './UrlContextbutton'
import WebSearchButton, { WebSearchButtonRef } from './WebSearchButton'
const logger = loggerService.withContext('InputbarTools')
export interface InputbarToolsRef {
getQuickPanelMenu: (params: {
t: (key: string, options?: any) => string
files: FileType[]
couldAddImageFile: boolean
text: string
openSelectFileMenu: () => void
translate: () => void
}) => QuickPanelListItem[]
getQuickPanelMenu: (params: { text: string; translate: () => void }) => QuickPanelListItem[]
openMentionModelsPanel: (triggerInfo?: { type: 'input' | 'button'; position?: number; originalText?: string }) => void
openAttachmentQuickPanel: () => void
}
export interface InputbarToolsProps {
assistant: Assistant
assistantId: string
model: Model
files: FileType[]
setFiles: (files: FileType[]) => void
setFiles: Dispatch<SetStateAction<FileType[]>>
extensions: string[]
showThinkingButton: boolean
showKnowledgeIcon: boolean
showMcpTools: boolean
selectedKnowledgeBases: KnowledgeBase[]
handleKnowledgeBaseSelect: (bases?: KnowledgeBase[]) => void
setText: Dispatch<SetStateAction<string>>
resizeTextArea: () => void
mentionModels: Model[]
onMentionModel: (model: Model) => void
onClearMentionModels: () => void
couldMentionNotVisionModel: boolean
selectedKnowledgeBases: KnowledgeBase[]
setSelectedKnowledgeBases: Dispatch<SetStateAction<KnowledgeBase[]>>
mentionedModels: Model[]
setMentionedModels: Dispatch<SetStateAction<Model[]>>
couldAddImageFile: boolean
onEnableGenerateImage: () => void
isExpanded: boolean
onToggleExpanded: () => void
addNewTopic: () => void
clearTopic: () => void
onNewContext: () => void
newTopicShortcut: string
cleanTopicShortcut: string
}
interface ToolButtonConfig {
@ -100,34 +99,27 @@ const DraggablePortal = ({ children, isDragging }) => {
const InputbarTools = ({
ref,
assistant,
assistantId,
model,
files,
setFiles,
showThinkingButton,
showKnowledgeIcon,
showMcpTools,
selectedKnowledgeBases,
handleKnowledgeBaseSelect,
setText,
resizeTextArea,
mentionModels,
onMentionModel,
onClearMentionModels,
couldMentionNotVisionModel,
selectedKnowledgeBases,
setSelectedKnowledgeBases,
mentionedModels,
setMentionedModels,
couldAddImageFile,
onEnableGenerateImage,
isExpanded: isExpended,
onToggleExpanded: onToggleExpended,
addNewTopic,
clearTopic,
onNewContext,
newTopicShortcut,
cleanTopicShortcut,
extensions
}: InputbarToolsProps & { ref?: React.RefObject<InputbarToolsRef | null> }) => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const { assistant, updateAssistant } = useAssistant(assistantId)
const quickPhrasesButtonRef = useRef<QuickPhrasesButtonRef>(null)
const mentionModelsButtonRef = useRef<MentionModelsButtonRef>(null)
@ -143,6 +135,54 @@ const InputbarTools = ({
const [targetTool, setTargetTool] = useState<ToolButtonConfig | null>(null)
const showThinkingButton = useMemo(
() => isSupportedThinkingTokenModel(model) || isSupportedReasoningEffortModel(model),
[model]
)
const showMcpServerButton = useMemo(() => isSupportedToolUse(assistant) || isPromptToolUse(assistant), [assistant])
const knowledgeSidebarEnabled = useSidebarIconShow('knowledge')
const showKnowledgeBaseButton = knowledgeSidebarEnabled && showMcpServerButton
const handleKnowledgeBaseSelect = useCallback(
(bases?: KnowledgeBase[]) => {
updateAssistant({ knowledge_bases: bases })
setSelectedKnowledgeBases(bases ?? [])
},
[setSelectedKnowledgeBases, updateAssistant]
)
// 仅允许在不含图片文件时mention非视觉模型
const couldMentionNotVisionModel = useMemo(() => {
return !files.some((file) => file.type === FileTypes.IMAGE)
}, [files])
const onMentionModel = useCallback(
(model: Model) => {
// 我想应该没有模型是只支持视觉而不支持文本的?
if (isVisionModel(model) || couldMentionNotVisionModel) {
setMentionedModels((prev) => {
const modelId = getModelUniqId(model)
const exists = prev.some((m) => getModelUniqId(m) === modelId)
return exists ? prev.filter((m) => getModelUniqId(m) !== modelId) : [...prev, model]
})
} else {
logger.error('Cannot add non-vision model when images are uploaded')
}
},
[couldMentionNotVisionModel, setMentionedModels]
)
const onClearMentionModels = useCallback(() => setMentionedModels([]), [setMentionedModels])
const onEnableGenerateImage = useCallback(() => {
updateAssistant({ enableGenerateImage: !assistant.enableGenerateImage })
}, [assistant.enableGenerateImage, updateAssistant])
const newTopicShortcut = useShortcutDisplay('new_topic')
const clearTopicShortcut = useShortcutDisplay('clear_topic')
const toggleToolVisibility = useCallback(
(toolKey: string, isVisible: boolean | undefined) => {
const newToolOrder = {
@ -164,15 +204,8 @@ const InputbarTools = ({
[dispatch, toolOrder.hidden, toolOrder.visible]
)
const getQuickPanelMenuImpl = (params: {
t: (key: string, options?: any) => string
files: FileType[]
couldAddImageFile: boolean
text: string
openSelectFileMenu: () => void
translate: () => void
}): QuickPanelListItem[] => {
const { t, files, couldAddImageFile, text, openSelectFileMenu, translate } = params
const getQuickPanelMenuImpl = (params: { text: string; translate: () => void }): QuickPanelListItem[] => {
const { text, translate } = params
return [
{
@ -249,11 +282,13 @@ const InputbarTools = ({
}
},
{
label: couldAddImageFile ? t('chat.input.upload.label') : t('chat.input.upload.document'),
label: couldAddImageFile ? t('chat.input.upload.attachment') : t('chat.input.upload.document'),
description: '',
icon: <Paperclip />,
isMenu: true,
action: openSelectFileMenu
action: () => {
attachmentButtonRef.current?.openQuickPanel()
}
},
{
label: t('translate.title'),
@ -313,15 +348,15 @@ const InputbarTools = ({
title={t('chat.input.new_topic', { Command: newTopicShortcut })}
mouseLeaveDelay={0}
arrow>
<ToolbarButton type="text" onClick={addNewTopic}>
<ActionIconButton onClick={addNewTopic}>
<MessageSquareDiff size={19} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
)
},
{
key: 'attachment',
label: t('chat.input.upload.label'),
label: t('chat.input.upload.image_or_document'),
component: (
<AttachmentButton
ref={attachmentButtonRef}
@ -329,28 +364,25 @@ const InputbarTools = ({
extensions={extensions}
files={files}
setFiles={setFiles}
ToolbarButton={ToolbarButton}
/>
)
},
{
key: 'thinking',
label: t('chat.input.thinking.label'),
component: (
<ThinkingButton ref={thinkingButtonRef} model={model} assistant={assistant} ToolbarButton={ToolbarButton} />
),
component: <ThinkingButton ref={thinkingButtonRef} model={model} assistantId={assistant.id} />,
condition: showThinkingButton
},
{
key: 'web_search',
label: t('chat.input.web_search.label'),
component: <WebSearchButton ref={webSearchButtonRef} assistant={assistant} ToolbarButton={ToolbarButton} />,
component: <WebSearchButton ref={webSearchButtonRef} assistantId={assistant.id} />,
condition: !isMandatoryWebSearchModel(model)
},
{
key: 'url_context',
label: t('chat.input.url_context'),
component: <UrlContextButton ref={urlContextButtonRef} assistant={assistant} ToolbarButton={ToolbarButton} />,
component: <UrlContextButton ref={urlContextButtonRef} assistantId={assistant.id} />,
condition: isGeminiModel(model) && isSupportUrlContextProvider(getProviderByModel(model))
},
{
@ -361,36 +393,29 @@ const InputbarTools = ({
ref={knowledgeBaseButtonRef}
selectedBases={selectedKnowledgeBases}
onSelect={handleKnowledgeBaseSelect}
ToolbarButton={ToolbarButton}
disabled={files.length > 0}
/>
),
condition: showKnowledgeIcon
condition: showKnowledgeBaseButton
},
{
key: 'mcp_tools',
label: t('settings.mcp.title'),
component: (
<MCPToolsButton
assistant={assistant}
assistantId={assistant.id}
ref={mcpToolsButtonRef}
ToolbarButton={ToolbarButton}
setInputValue={setText}
resizeTextArea={resizeTextArea}
/>
),
condition: showMcpTools
condition: showMcpServerButton
},
{
key: 'generate_image',
label: t('chat.input.generate_image'),
component: (
<GenerateImageButton
model={model}
assistant={assistant}
onEnableGenerateImage={onEnableGenerateImage}
ToolbarButton={ToolbarButton}
/>
<GenerateImageButton model={model} assistant={assistant} onEnableGenerateImage={onEnableGenerateImage} />
),
condition: isGenerateImageModel(model)
},
@ -400,10 +425,9 @@ const InputbarTools = ({
component: (
<MentionModelsButton
ref={mentionModelsButtonRef}
mentionedModels={mentionModels}
mentionedModels={mentionedModels}
onMentionModel={onMentionModel}
onClearMentionModels={onClearMentionModels}
ToolbarButton={ToolbarButton}
couldMentionNotVisionModel={couldMentionNotVisionModel}
files={files}
setText={setText}
@ -418,8 +442,7 @@ const InputbarTools = ({
ref={quickPhrasesButtonRef}
setInputValue={setText}
resizeTextArea={resizeTextArea}
ToolbarButton={ToolbarButton}
assistantObj={assistant}
assistantId={assistant.id}
/>
)
},
@ -429,12 +452,12 @@ const InputbarTools = ({
component: (
<Tooltip
placement="top"
title={t('chat.input.clear.label', { Command: cleanTopicShortcut })}
title={t('chat.input.clear.label', { Command: clearTopicShortcut })}
mouseLeaveDelay={0}
arrow>
<ToolbarButton type="text" onClick={clearTopic}>
<ActionIconButton onClick={clearTopic}>
<PaintbrushVertical size={18} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
)
},
@ -447,22 +470,22 @@ const InputbarTools = ({
title={isExpended ? t('chat.input.collapse') : t('chat.input.expand')}
mouseLeaveDelay={0}
arrow>
<ToolbarButton type="text" onClick={onToggleExpended}>
<ActionIconButton onClick={onToggleExpended}>
{isExpended ? <Minimize size={18} /> : <Maximize size={18} />}
</ToolbarButton>
</ActionIconButton>
</Tooltip>
)
},
{
key: 'new_context',
label: t('chat.input.new.context', { Command: '' }),
component: <NewContextButton onNewContext={onNewContext} ToolbarButton={ToolbarButton} />
component: <NewContextButton onNewContext={onNewContext} />
}
]
}, [
addNewTopic,
assistant,
cleanTopicShortcut,
clearTopicShortcut,
clearTopic,
couldAddImageFile,
couldMentionNotVisionModel,
@ -470,7 +493,7 @@ const InputbarTools = ({
files,
handleKnowledgeBaseSelect,
isExpended,
mentionModels,
mentionedModels,
model,
newTopicShortcut,
onClearMentionModels,
@ -482,8 +505,8 @@ const InputbarTools = ({
selectedKnowledgeBases,
setFiles,
setText,
showKnowledgeIcon,
showMcpTools,
showKnowledgeBaseButton,
showMcpServerButton,
showThinkingButton,
t
])
@ -628,14 +651,14 @@ const InputbarTools = ({
placement="top"
title={isCollapse ? t('chat.input.tools.expand') : t('chat.input.tools.collapse')}
arrow>
<ToolbarButton type="text" onClick={() => dispatch(setIsCollapsed(!isCollapse))}>
<ActionIconButton onClick={() => dispatch(setIsCollapsed(!isCollapse))}>
<CircleChevronRight
size={18}
style={{
transform: isCollapse ? 'scaleX(1)' : 'scaleX(-1)'
}}
/>
</ToolbarButton>
</ActionIconButton>
</Tooltip>
)}
</ToolsContainer>

View File

@ -1,4 +1,5 @@
import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel'
import { ActionIconButton } from '@renderer/components/Buttons'
import { QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { useAppSelector } from '@renderer/store'
import { KnowledgeBase } from '@renderer/types'
import { Tooltip } from 'antd'
@ -16,10 +17,9 @@ interface Props {
selectedBases?: KnowledgeBase[]
onSelect: (bases: KnowledgeBase[]) => void
disabled?: boolean
ToolbarButton: any
}
const KnowledgeBaseButton: FC<Props> = ({ ref, selectedBases, onSelect, disabled, ToolbarButton }) => {
const KnowledgeBaseButton: FC<Props> = ({ ref, selectedBases, onSelect, disabled }) => {
const { t } = useTranslation()
const navigate = useNavigate()
const quickPanel = useQuickPanel()
@ -77,7 +77,7 @@ const KnowledgeBaseButton: FC<Props> = ({ ref, selectedBases, onSelect, disabled
quickPanel.open({
title: t('chat.input.knowledge_base'),
list: baseItems,
symbol: '#',
symbol: QuickPanelReservedSymbol.KnowledgeBase,
multiple: true,
afterAction({ item }) {
item.isSelected = !item.isSelected
@ -86,7 +86,7 @@ const KnowledgeBaseButton: FC<Props> = ({ ref, selectedBases, onSelect, disabled
}, [baseItems, quickPanel, t])
const handleOpenQuickPanel = useCallback(() => {
if (quickPanel.isVisible && quickPanel.symbol === '#') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.KnowledgeBase) {
quickPanel.close()
} else {
openQuickPanel()
@ -95,7 +95,7 @@ const KnowledgeBaseButton: FC<Props> = ({ ref, selectedBases, onSelect, disabled
// 监听 selectedBases 变化,动态更新已打开的 QuickPanel 列表状态
useEffect(() => {
if (quickPanel.isVisible && quickPanel.symbol === '#') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.KnowledgeBase) {
// 直接使用重新计算的 baseItems因为它已经包含了最新的 isSelected 状态
quickPanel.updateList(baseItems)
}
@ -107,12 +107,12 @@ const KnowledgeBaseButton: FC<Props> = ({ ref, selectedBases, onSelect, disabled
return (
<Tooltip placement="top" title={t('chat.input.knowledge_base')} mouseLeaveDelay={0} arrow>
<ToolbarButton type="text" onClick={handleOpenQuickPanel} disabled={disabled}>
<FileSearch
size={18}
color={selectedBases && selectedBases.length > 0 ? 'var(--color-primary)' : 'var(--color-icon)'}
/>
</ToolbarButton>
<ActionIconButton
onClick={handleOpenQuickPanel}
active={selectedBases && selectedBases.length > 0}
disabled={disabled}>
<FileSearch size={18} />
</ActionIconButton>
</Tooltip>
)
}

View File

@ -1,4 +1,5 @@
import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel'
import { ActionIconButton } from '@renderer/components/Buttons'
import { QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { isGeminiModel } from '@renderer/config/models'
import { isGeminiWebSearchProvider, isSupportUrlContextProvider } from '@renderer/config/providers'
import { useAssistant } from '@renderer/hooks/useAssistant'
@ -6,7 +7,7 @@ import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { useTimer } from '@renderer/hooks/useTimer'
import { getProviderByModel } from '@renderer/services/AssistantService'
import { EventEmitter } from '@renderer/services/EventService'
import { Assistant, MCPPrompt, MCPResource, MCPServer } from '@renderer/types'
import { MCPPrompt, MCPResource, MCPServer } from '@renderer/types'
import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Form, Input, Tooltip } from 'antd'
import { CircleX, Hammer, Plus } from 'lucide-react'
@ -21,11 +22,10 @@ export interface MCPToolsButtonRef {
}
interface Props {
assistant: Assistant
assistantId: string
ref?: React.RefObject<MCPToolsButtonRef | null>
setInputValue: React.Dispatch<React.SetStateAction<string>>
resizeTextArea: () => void
ToolbarButton: any
}
// 添加类型定义
@ -113,14 +113,14 @@ const extractPromptContent = (response: any): string | null => {
return null
}
const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, ToolbarButton, ...props }) => {
const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, assistantId }) => {
const { activedMcpServers } = useMCPServers()
const { t } = useTranslation()
const quickPanel = useQuickPanel()
const navigate = useNavigate()
const [form] = Form.useForm()
const { updateAssistant, assistant } = useAssistant(props.assistant.id)
const { assistant, updateAssistant } = useAssistant(assistantId)
const model = assistant.model
const { setTimeoutTimer } = useTimer()
@ -228,7 +228,7 @@ const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, Toolbar
quickPanel.open({
title: t('settings.mcp.title'),
list: menuItems,
symbol: 'mcp',
symbol: QuickPanelReservedSymbol.Mcp,
multiple: true,
afterAction({ item }) {
item.isSelected = !item.isSelected
@ -377,7 +377,7 @@ const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, Toolbar
quickPanel.open({
title: t('settings.mcp.title'),
list: prompts,
symbol: 'mcp-prompt',
symbol: QuickPanelReservedSymbol.McpPrompt,
multiple: true
})
}, [promptList, quickPanel, t])
@ -465,13 +465,13 @@ const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, Toolbar
quickPanel.open({
title: t('settings.mcp.title'),
list: resourcesList,
symbol: 'mcp-resource',
symbol: QuickPanelReservedSymbol.McpResource,
multiple: true
})
}, [resourcesList, quickPanel, t])
const handleOpenQuickPanel = useCallback(() => {
if (quickPanel.isVisible && quickPanel.symbol === 'mcp') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.Mcp) {
quickPanel.close()
} else {
openQuickPanel()
@ -486,12 +486,9 @@ const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, Toolbar
return (
<Tooltip placement="top" title={t('settings.mcp.title')} mouseLeaveDelay={0} arrow>
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
<Hammer
size={18}
color={assistant.mcpServers && assistant.mcpServers.length > 0 ? 'var(--color-primary)' : 'var(--color-icon)'}
/>
</ToolbarButton>
<ActionIconButton onClick={handleOpenQuickPanel} active={assistant.mcpServers && assistant.mcpServers.length > 0}>
<Hammer size={18} />
</ActionIconButton>
</Tooltip>
)
}

View File

@ -1,6 +1,6 @@
import { ActionIconButton } from '@renderer/components/Buttons'
import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel'
import { useQuickPanel } from '@renderer/components/QuickPanel'
import { QuickPanelListItem } from '@renderer/components/QuickPanel/types'
import { type QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { getModelLogo, isEmbeddingModel, isRerankModel, isVisionModel } from '@renderer/config/models'
import db from '@renderer/databases'
import { useProviders } from '@renderer/hooks/useProvider'
@ -27,7 +27,6 @@ interface Props {
onClearMentionModels: () => void
couldMentionNotVisionModel: boolean
files: FileType[]
ToolbarButton: any
setText: React.Dispatch<React.SetStateAction<string>>
}
@ -38,7 +37,6 @@ const MentionModelsButton: FC<Props> = ({
onClearMentionModels,
couldMentionNotVisionModel,
files,
ToolbarButton,
setText
}) => {
const { providers } = useProviders()
@ -242,7 +240,7 @@ const MentionModelsButton: FC<Props> = ({
quickPanel.open({
title: t('agents.edit.model.select.title'),
list: modelItems,
symbol: '@',
symbol: QuickPanelReservedSymbol.MentionModels,
multiple: true,
triggerInfo: triggerInfo || { type: 'button' },
afterAction({ item }) {
@ -274,7 +272,7 @@ const MentionModelsButton: FC<Props> = ({
)
const handleOpenQuickPanel = useCallback(() => {
if (quickPanel.isVisible && quickPanel.symbol === '@') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.MentionModels) {
quickPanel.close()
} else {
openQuickPanel({ type: 'button' })
@ -286,7 +284,7 @@ const MentionModelsButton: FC<Props> = ({
useEffect(() => {
// 检查files是否变化
if (filesRef.current !== files) {
if (quickPanel.isVisible && quickPanel.symbol === '@') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.MentionModels) {
quickPanel.close()
}
filesRef.current = files
@ -295,7 +293,7 @@ const MentionModelsButton: FC<Props> = ({
// 监听 mentionedModels 变化,动态更新已打开的 QuickPanel 列表状态
useEffect(() => {
if (quickPanel.isVisible && quickPanel.symbol === '@') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.MentionModels) {
// 直接使用重新计算的 modelItems因为它已经包含了最新的 isSelected 状态
quickPanel.updateList(modelItems)
}
@ -307,9 +305,9 @@ const MentionModelsButton: FC<Props> = ({
return (
<Tooltip placement="top" title={t('agents.edit.model.select.title')} mouseLeaveDelay={0} arrow>
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
<AtSign size={18} color={mentionedModels.length > 0 ? 'var(--color-primary)' : 'var(--color-icon)'} />
</ToolbarButton>
<ActionIconButton onClick={handleOpenQuickPanel} active={mentionedModels.length > 0}>
<AtSign size={18} />
</ActionIconButton>
</Tooltip>
)
}

View File

@ -1,15 +1,14 @@
import { ActionIconButton } from '@renderer/components/Buttons'
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
import { Tooltip } from 'antd'
import { Eraser } from 'lucide-react'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
interface Props {
onNewContext: () => void
ToolbarButton: any
}
const NewContextButton: FC<Props> = ({ onNewContext, ToolbarButton }) => {
const NewContextButton: FC<Props> = ({ onNewContext }) => {
const newContextShortcut = useShortcutDisplay('toggle_new_context')
const { t } = useTranslation()
@ -21,9 +20,9 @@ const NewContextButton: FC<Props> = ({ onNewContext, ToolbarButton }) => {
title={t('chat.input.new.context', { Command: newContextShortcut })}
mouseLeaveDelay={0}
arrow>
<ToolbarButton type="text" onClick={onNewContext}>
<ActionIconButton onClick={onNewContext}>
<Eraser size={18} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
)
}

View File

@ -1,11 +1,14 @@
import { ActionIconButton } from '@renderer/components/Buttons'
import {
type QuickPanelListItem,
type QuickPanelOpenOptions,
QuickPanelReservedSymbol
} from '@renderer/components/QuickPanel'
import { useQuickPanel } from '@renderer/components/QuickPanel'
import { QuickPanelListItem, QuickPanelOpenOptions } from '@renderer/components/QuickPanel/types'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useTimer } from '@renderer/hooks/useTimer'
import QuickPhraseService from '@renderer/services/QuickPhraseService'
import { useAppSelector } from '@renderer/store'
import { QuickPhrase } from '@renderer/types'
import { Assistant } from '@renderer/types'
import { Input, Modal, Radio, Space, Tooltip } from 'antd'
import { BotMessageSquare, Plus, Zap } from 'lucide-react'
import { memo, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react'
@ -20,21 +23,16 @@ interface Props {
ref?: React.RefObject<QuickPhrasesButtonRef | null>
setInputValue: React.Dispatch<React.SetStateAction<string>>
resizeTextArea: () => void
ToolbarButton: any
assistantObj: Assistant
assistantId: string
}
const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, ToolbarButton, assistantObj }: Props) => {
const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, assistantId }: Props) => {
const [quickPhrasesList, setQuickPhrasesList] = useState<QuickPhrase[]>([])
const [isModalOpen, setIsModalOpen] = useState(false)
const [formData, setFormData] = useState({ title: '', content: '', location: 'global' })
const { t } = useTranslation()
const quickPanel = useQuickPanel()
const activeAssistantId = useAppSelector(
(state) =>
state.assistants.assistants.find((a) => a.id === assistantObj.id)?.id || state.assistants.defaultAssistant.id
)
const { assistant, updateAssistant } = useAssistant(activeAssistantId)
const { assistant, updateAssistant } = useAssistant(assistantId)
const { setTimeoutTimer } = useTimer()
const loadQuickListPhrases = useCallback(
@ -135,7 +133,7 @@ const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, ToolbarButton,
() => ({
title: t('settings.quickPhrase.title'),
list: phraseItems,
symbol: 'quick-phrases'
symbol: QuickPanelReservedSymbol.QuickPhrases
}),
[phraseItems, t]
)
@ -145,7 +143,7 @@ const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, ToolbarButton,
}, [quickPanel, quickPanelOpenOptions])
const handleOpenQuickPanel = useCallback(() => {
if (quickPanel.isVisible && quickPanel.symbol === 'quick-phrases') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.QuickPhrases) {
quickPanel.close()
} else {
openQuickPanel()
@ -159,9 +157,9 @@ const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, ToolbarButton,
return (
<>
<Tooltip placement="top" title={t('settings.quickPhrase.title')} mouseLeaveDelay={0} arrow>
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
<ActionIconButton onClick={handleOpenQuickPanel}>
<Zap size={18} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
<Modal

View File

@ -1,3 +1,4 @@
import { ActionIconButton } from '@renderer/components/Buttons'
import {
MdiLightbulbAutoOutline,
MdiLightbulbOffOutline,
@ -6,11 +7,11 @@ import {
MdiLightbulbOn50,
MdiLightbulbOn80
} from '@renderer/components/Icons/SVGIcon'
import { useQuickPanel } from '@renderer/components/QuickPanel'
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { getThinkModelType, isDoubaoThinkingAutoModel, MODEL_SUPPORTED_OPTIONS } from '@renderer/config/models'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { getReasoningEffortOptionsLabel } from '@renderer/i18n/label'
import { Assistant, Model, ThinkingOption } from '@renderer/types'
import { Model, ThinkingOption } from '@renderer/types'
import { Tooltip } from 'antd'
import { FC, ReactElement, useCallback, useImperativeHandle, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@ -22,14 +23,13 @@ export interface ThinkingButtonRef {
interface Props {
ref?: React.RefObject<ThinkingButtonRef | null>
model: Model
assistant: Assistant
ToolbarButton: any
assistantId: string
}
const ThinkingButton: FC<Props> = ({ ref, model, assistant, ToolbarButton }): ReactElement => {
const ThinkingButton: FC<Props> = ({ ref, model, assistantId }): ReactElement => {
const { t } = useTranslation()
const quickPanel = useQuickPanel()
const { updateAssistantSettings } = useAssistant(assistant.id)
const { assistant, updateAssistantSettings } = useAssistant(assistantId)
const currentReasoningEffort = useMemo(() => {
return assistant.settings?.reasoning_effort || 'off'
@ -49,27 +49,6 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistant, ToolbarButton }): Re
return MODEL_SUPPORTED_OPTIONS[modelType]
}, [model, modelType])
const createThinkingIcon = useCallback((option?: ThinkingOption, isActive: boolean = false) => {
const iconColor = isActive ? 'var(--color-primary)' : 'var(--color-icon)'
switch (true) {
case option === 'minimal':
return <MdiLightbulbOn30 width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
case option === 'low':
return <MdiLightbulbOn50 width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
case option === 'medium':
return <MdiLightbulbOn80 width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
case option === 'high':
return <MdiLightbulbOn width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
case option === 'auto':
return <MdiLightbulbAutoOutline width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
case option === 'off':
return <MdiLightbulbOffOutline width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
default:
return <MdiLightbulbOffOutline width={18} height={18} style={{ color: iconColor }} />
}
}, [])
const onThinkingChange = useCallback(
(option?: ThinkingOption) => {
const isEnabled = option !== undefined && option !== 'off'
@ -98,11 +77,11 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistant, ToolbarButton }): Re
level: option,
label: getReasoningEffortOptionsLabel(option),
description: '',
icon: createThinkingIcon(option),
icon: ThinkingIcon(option),
isSelected: currentReasoningEffort === option,
action: () => onThinkingChange(option)
}))
}, [createThinkingIcon, currentReasoningEffort, supportedOptions, onThinkingChange])
}, [currentReasoningEffort, supportedOptions, onThinkingChange])
const isThinkingEnabled = currentReasoningEffort !== undefined && currentReasoningEffort !== 'off'
@ -114,12 +93,12 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistant, ToolbarButton }): Re
quickPanel.open({
title: t('assistants.settings.reasoning_effort.label'),
list: panelItems,
symbol: 'thinking'
symbol: QuickPanelReservedSymbol.Thinking
})
}, [quickPanel, panelItems, t])
const handleOpenQuickPanel = useCallback(() => {
if (quickPanel.isVisible && quickPanel.symbol === 'thinking') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.Thinking) {
quickPanel.close()
return
}
@ -131,12 +110,6 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistant, ToolbarButton }): Re
openQuickPanel()
}, [openQuickPanel, quickPanel, isThinkingEnabled, supportedOptions, disableThinking])
// 获取当前应显示的图标
const getThinkingIcon = useCallback(() => {
// 不再判断选项是否支持,依赖 useAssistant 更新选项为支持选项的行为
return createThinkingIcon(currentReasoningEffort, currentReasoningEffort !== 'off')
}, [createThinkingIcon, currentReasoningEffort])
useImperativeHandle(ref, () => ({
openQuickPanel
}))
@ -151,11 +124,41 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistant, ToolbarButton }): Re
}
mouseLeaveDelay={0}
arrow>
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
{getThinkingIcon()}
</ToolbarButton>
<ActionIconButton onClick={handleOpenQuickPanel} active={currentReasoningEffort !== 'off'}>
{ThinkingIcon(currentReasoningEffort)}
</ActionIconButton>
</Tooltip>
)
}
const ThinkingIcon = (option?: ThinkingOption) => {
let IconComponent: React.FC<React.SVGProps<SVGSVGElement>> | null = null
switch (option) {
case 'minimal':
IconComponent = MdiLightbulbOn30
break
case 'low':
IconComponent = MdiLightbulbOn50
break
case 'medium':
IconComponent = MdiLightbulbOn80
break
case 'high':
IconComponent = MdiLightbulbOn
break
case 'auto':
IconComponent = MdiLightbulbAutoOutline
break
case 'off':
IconComponent = MdiLightbulbOffOutline
break
default:
IconComponent = MdiLightbulbOffOutline
break
}
return <IconComponent className="icon" width={18} height={18} style={{ marginTop: -2 }} />
}
export default ThinkingButton

View File

@ -11,7 +11,6 @@ type Props = {
estimateTokenCount: number
inputTokenCount: number
contextCount: { current: number; max: number }
ToolbarButton: any
} & React.HTMLAttributes<HTMLDivElement>
const TokenCount: FC<Props> = ({ estimateTokenCount, inputTokenCount, contextCount }) => {

View File

@ -1,6 +1,6 @@
import { ActionIconButton } from '@renderer/components/Buttons'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useTimer } from '@renderer/hooks/useTimer'
import { Assistant } from '@renderer/types'
import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Tooltip } from 'antd'
import { Link } from 'lucide-react'
@ -13,13 +13,12 @@ export interface UrlContextButtonRef {
interface Props {
ref?: React.RefObject<UrlContextButtonRef | null>
assistant: Assistant
ToolbarButton: any
assistantId: string
}
const UrlContextButton: FC<Props> = ({ assistant, ToolbarButton }) => {
const UrlContextButton: FC<Props> = ({ assistantId }) => {
const { t } = useTranslation()
const { updateAssistant } = useAssistant(assistant.id)
const { assistant, updateAssistant } = useAssistant(assistantId)
const { setTimeoutTimer } = useTimer()
const urlContentNewState = !assistant.enableUrlContext
@ -48,14 +47,9 @@ const UrlContextButton: FC<Props> = ({ assistant, ToolbarButton }) => {
return (
<Tooltip placement="top" title={t('chat.input.url_context')} arrow>
<ToolbarButton type="text" onClick={handleToggle}>
<Link
size={18}
style={{
color: assistant.enableUrlContext ? 'var(--color-primary)' : 'var(--color-icon)'
}}
/>
</ToolbarButton>
<ActionIconButton onClick={handleToggle} active={assistant.enableUrlContext}>
<Link size={18} />
</ActionIconButton>
</Tooltip>
)
}

View File

@ -1,7 +1,8 @@
import { BaiduOutlined, GoogleOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import { ActionIconButton } from '@renderer/components/Buttons'
import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo, ZhipuLogo } from '@renderer/components/Icons'
import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel'
import { QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { isGeminiModel, isWebSearchModel } from '@renderer/config/models'
import { isGeminiWebSearchProvider } from '@renderer/config/providers'
import { useAssistant } from '@renderer/hooks/useAssistant'
@ -9,7 +10,7 @@ import { useTimer } from '@renderer/hooks/useTimer'
import { useWebSearchProviders } from '@renderer/hooks/useWebSearchProviders'
import { getProviderByModel } from '@renderer/services/AssistantService'
import WebSearchService from '@renderer/services/WebSearchService'
import { Assistant, WebSearchProvider, WebSearchProviderId } from '@renderer/types'
import { WebSearchProvider, WebSearchProviderId } from '@renderer/types'
import { hasObjectKey } from '@renderer/utils'
import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Tooltip } from 'antd'
@ -23,17 +24,16 @@ export interface WebSearchButtonRef {
interface Props {
ref?: React.RefObject<WebSearchButtonRef | null>
assistant: Assistant
ToolbarButton: any
assistantId: string
}
const logger = loggerService.withContext('WebSearchButton')
const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
const WebSearchButton: FC<Props> = ({ ref, assistantId }) => {
const { t } = useTranslation()
const quickPanel = useQuickPanel()
const { providers } = useWebSearchProviders()
const { updateAssistant } = useAssistant(assistant.id)
const { assistant, updateAssistant } = useAssistant(assistantId)
const { setTimeoutTimer } = useTimer()
// 注意assistant.enableWebSearch 有不同的语义
@ -44,24 +44,24 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
({ pid, size = 18, color }: { pid?: WebSearchProviderId; size?: number; color?: string }) => {
switch (pid) {
case 'bocha':
return <BochaLogo width={size} height={size} color={color} />
return <BochaLogo className="icon" width={size} height={size} color={color} />
case 'exa':
// size微调视觉上和其他图标平衡一些
return <ExaLogo width={size - 2} height={size} color={color} />
return <ExaLogo className="icon" width={size - 2} height={size} color={color} />
case 'tavily':
return <TavilyLogo width={size} height={size} color={color} />
return <TavilyLogo className="icon" width={size} height={size} color={color} />
case 'zhipu':
return <ZhipuLogo width={size} height={size} color={color} />
return <ZhipuLogo className="icon" width={size} height={size} color={color} />
case 'searxng':
return <SearXNGLogo width={size} height={size} color={color} />
return <SearXNGLogo className="icon" width={size} height={size} color={color} />
case 'local-baidu':
return <BaiduOutlined size={size} style={{ color, fontSize: size }} />
case 'local-bing':
return <BingLogo width={size} height={size} color={color} />
return <BingLogo className="icon" width={size} height={size} color={color} />
case 'local-google':
return <GoogleOutlined size={size} style={{ color, fontSize: size }} />
default:
return <Globe size={size} style={{ color, fontSize: size }} />
return <Globe className="icon" size={size} style={{ color, fontSize: size }} />
}
},
[]
@ -165,13 +165,13 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
quickPanel.open({
title: t('chat.input.web_search.label'),
list: providerItems,
symbol: '?',
symbol: QuickPanelReservedSymbol.WebSearch,
pageSize: 9
})
}, [quickPanel, t, providerItems])
const handleOpenQuickPanel = useCallback(() => {
if (quickPanel.isVisible && quickPanel.symbol === '?') {
if (quickPanel.isVisible && quickPanel.symbol === QuickPanelReservedSymbol.WebSearch) {
quickPanel.close()
} else {
openQuickPanel()
@ -190,17 +190,15 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
openQuickPanel
}))
const color = enableWebSearch ? 'var(--color-primary)' : 'var(--color-icon)'
return (
<Tooltip
placement="top"
title={enableWebSearch ? t('common.close') : t('chat.input.web_search.label')}
mouseLeaveDelay={0}
arrow>
<ToolbarButton type="text" onClick={onClick}>
<WebSearchIcon color={color} pid={assistant.webSearchProviderId} />
</ToolbarButton>
<ActionIconButton onClick={onClick} active={!!enableWebSearch}>
<WebSearchIcon pid={assistant.webSearchProviderId} />
</ActionIconButton>
</Tooltip>
)
}

View File

@ -1,5 +1,6 @@
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { ActionIconButton } from '@renderer/components/Buttons'
import CustomTag from '@renderer/components/Tags/CustomTag'
import TranslateButton from '@renderer/components/TranslateButton'
import { isGenerateImageModel, isVisionModel } from '@renderer/config/models'
@ -25,7 +26,6 @@ import styled from 'styled-components'
import AttachmentButton, { AttachmentButtonRef } from '../Inputbar/AttachmentButton'
import { FileNameRender, getFileIcon } from '../Inputbar/AttachmentPreview'
import { ToolbarButton } from '../Inputbar/Inputbar'
interface Props {
message: Message
@ -349,27 +349,26 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
setFiles={setFiles}
couldAddImageFile={couldAddImageFile}
extensions={extensions}
ToolbarButton={ToolbarButton}
/>
)}
</ActionBarLeft>
<ActionBarMiddle />
<ActionBarRight>
<Tooltip title={t('common.cancel')}>
<ToolbarButton type="text" onClick={onCancel}>
<ActionIconButton onClick={onCancel}>
<X size={16} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
<Tooltip title={t('common.save')}>
<ToolbarButton type="text" onClick={handleSave}>
<ActionIconButton onClick={handleSave}>
<Save size={16} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
{message.role === 'user' && (
<Tooltip title={t('chat.resend')}>
<ToolbarButton type="text" onClick={handleResend}>
<ActionIconButton onClick={handleResend}>
<Send size={16} />
</ToolbarButton>
</ActionIconButton>
</Tooltip>
)}
</ActionBarRight>

View File

@ -848,7 +848,8 @@ const ContentContainer = styled.div<{ $historyDrawerVisible: boolean }>`
`
const AreaContainer = styled.div`
display: flex;
display: grid;
grid-template-columns: 1fr 1fr;
flex: 1;
gap: 8px;
`
@ -919,6 +920,11 @@ const OutputContainer = styled.div`
border-radius: 10px;
padding: 10px 5px;
height: calc(100vh - var(--navbar-height) - 70px);
overflow: hidden;
& > div > .markdown > pre {
background-color: var(--color-background-mute) !important;
}
&:hover .copy-button {
opacity: 1;

View File

@ -46,8 +46,8 @@ const assistantsSlice = createSlice({
removeAssistant: (state, action: PayloadAction<{ id: string }>) => {
state.assistants = state.assistants.filter((c) => c.id !== action.payload.id)
},
updateAssistant: (state, action: PayloadAction<Assistant>) => {
state.assistants = state.assistants.map((c) => (c.id === action.payload.id ? action.payload : c))
updateAssistant: (state, action: PayloadAction<Partial<Assistant>>) => {
state.assistants = state.assistants.map((c) => (c.id === action.payload.id ? { ...c, ...action.payload } : c))
},
updateAssistantSettings: (
state,

View File

@ -17,6 +17,7 @@ import type { BaseTool, MCPTool } from './tool'
export * from './knowledge'
export * from './mcp'
export * from './notification'
export * from './ocr'
export type Assistant = {

View File

@ -19,15 +19,14 @@
"electron-vite/node",
"vitest/globals"
],
"baseUrl": ".",
"paths": {
"@logger": ["src/main/services/LoggerService"],
"@data/*": ["src/main/data/*"],
"@main/*": ["src/main/*"],
"@types": ["src/renderer/src/types/index.ts"],
"@shared/*": ["packages/shared/*"],
"@mcp-trace/*": ["packages/mcp-trace/*"],
"@modelcontextprotocol/sdk/*": ["node_modules/@modelcontextprotocol/sdk/dist/esm/*"]
"@logger": ["./src/main/services/LoggerService"],
"@data/*": ["./src/main/data/*"],
"@main/*": ["./src/main/*"],
"@types": ["./src/renderer/src/types/index.ts"],
"@shared/*": ["./packages/shared/*"],
"@mcp-trace/*": ["./packages/mcp-trace/*"],
"@modelcontextprotocol/sdk/*": ["./node_modules/@modelcontextprotocol/sdk/dist/esm/*"]
},
"experimentalDecorators": true,
"emitDecoratorMetadata": true,

View File

@ -17,21 +17,20 @@
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo/tsconfig.web.tsbuildinfo",
"jsx": "react-jsx",
"baseUrl": ".",
"moduleResolution": "bundler",
"paths": {
"@logger": ["src/renderer/src/services/LoggerService"],
"@data/*": ["src/renderer/src/data/*"],
"@renderer/*": ["src/renderer/src/*"],
"@shared/*": ["packages/shared/*"],
"@types": ["src/renderer/src/types/index.ts"],
"@mcp-trace/*": ["packages/mcp-trace/*"],
"@cherrystudio/ai-core/provider": ["packages/aiCore/src/core/providers/index.ts"],
"@cherrystudio/ai-core/built-in/plugins": ["packages/aiCore/src/core/plugins/built-in/index.ts"],
"@cherrystudio/ai-core/*": ["packages/aiCore/src/*"],
"@cherrystudio/ai-core": ["packages/aiCore/src/index.ts"],
"@cherrystudio/extension-table-plus": ["packages/extension-table-plus/src/index.ts"],
"@cherrystudio/ui": ["packages/ui/src/index.ts"]
"@logger": ["./src/renderer/src/services/LoggerService"],
"@data/*": ["./src/renderer/src/data/*"],
"@renderer/*": ["./src/renderer/src/*"],
"@shared/*": ["./packages/shared/*"],
"@types": ["./src/renderer/src/types/index.ts"],
"@mcp-trace/*": ["./packages/mcp-trace/*"],
"@cherrystudio/ai-core/provider": ["./packages/aiCore/src/core/providers/index.ts"],
"@cherrystudio/ai-core/built-in/plugins": ["./packages/aiCore/src/core/plugins/built-in/index.ts"],
"@cherrystudio/ai-core/*": ["./packages/aiCore/src/*"],
"@cherrystudio/ai-core": ["./packages/aiCore/src/index.ts"],
"@cherrystudio/extension-table-plus": ["./packages/extension-table-plus/src/index.ts"],
"@cherrystudio/ui": ["./packages/ui/src/index.ts"]
},
"experimentalDecorators": true,
"emitDecoratorMetadata": true,

338
yarn.lock
View File

@ -74,221 +74,157 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/amazon-bedrock@npm:^3.0.0":
version: 3.0.8
resolution: "@ai-sdk/amazon-bedrock@npm:3.0.8"
"@ai-sdk/amazon-bedrock@npm:^3.0.21":
version: 3.0.21
resolution: "@ai-sdk/amazon-bedrock@npm:3.0.21"
dependencies:
"@ai-sdk/anthropic": "npm:2.0.4"
"@ai-sdk/anthropic": "npm:2.0.17"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.3"
"@ai-sdk/provider-utils": "npm:3.0.9"
"@smithy/eventstream-codec": "npm:^4.0.1"
"@smithy/util-utf8": "npm:^4.0.0"
aws4fetch: "npm:^1.0.20"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/d7b303b8581e9d28e9ac375b3718ef3f7fff3353d18185870f0b90fd542eb9398d029768502981e9e45a6b64137a7029f591993afd0b18e9ef74525f625524f7
checksum: 10c0/2d15baaad53e389666cede9673e2b43f5299e2cedb70f5b7afc656b7616e73775a9108c2cc1beee4644ff4c66ad41c8dd0b412373dd05caa4fc3d477c4343ea8
languageName: node
linkType: hard
"@ai-sdk/anthropic@npm:2.0.15":
version: 2.0.15
resolution: "@ai-sdk/anthropic@npm:2.0.15"
"@ai-sdk/anthropic@npm:2.0.17, @ai-sdk/anthropic@npm:^2.0.17":
version: 2.0.17
resolution: "@ai-sdk/anthropic@npm:2.0.17"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.8"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/9597b32be8b83dab67b23f162ca66cde385213fb1665f54091d59430789becf73e2b4fcd2be66ceb13020409f59cd8f9da7dae23adf183bc9eb7ce94f55bde96
checksum: 10c0/783b6a953f3854c4303ad7c30dd56d4706486c7d1151adb17071d87933418c59c26bce53d5c26d34c4d4728eaac4a856ce49a336caed26a7216f982fea562814
languageName: node
linkType: hard
"@ai-sdk/anthropic@npm:2.0.4":
version: 2.0.4
resolution: "@ai-sdk/anthropic@npm:2.0.4"
"@ai-sdk/azure@npm:^2.0.30":
version: 2.0.30
resolution: "@ai-sdk/azure@npm:2.0.30"
dependencies:
"@ai-sdk/openai": "npm:2.0.30"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.3"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/2e5a997b6e2d9a2964c4681418643fd2f347df78ac1f9677a0cc6a3a3454920d05c663e35521d8922f0a382ec77a25e4b92204b3760a1da05876bf00d41adc39
checksum: 10c0/22af450e28026547badc891a627bcb3cfa2d030864089947172506810f06cfa4c74c453aabd6a0d5c05ede5ffdee381b9278772ce781eca0c7c826c7d7ae3dc3
languageName: node
linkType: hard
"@ai-sdk/anthropic@npm:^2.0.5":
version: 2.0.5
resolution: "@ai-sdk/anthropic@npm:2.0.5"
"@ai-sdk/deepseek@npm:^1.0.17":
version: 1.0.17
resolution: "@ai-sdk/deepseek@npm:1.0.17"
dependencies:
"@ai-sdk/openai-compatible": "npm:1.0.17"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.4"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/aaca0d4b2e00715c513a7c688d6b6116eaf29d1d37f005c150f1229200713fb1c393c81a8b01ac29af954fb1ee213f3a537861227051865abe51aa547dca364e
checksum: 10c0/c408701343bb28ed0b3e034b8789e6de1dfd6cfc6a9b53feb68f155889e29a9fbbcf05bd99e63f60809cf05ee4b158abaccdf1cbcd9df92c0987094220a61d08
languageName: node
linkType: hard
"@ai-sdk/azure@npm:^2.0.16":
version: 2.0.16
resolution: "@ai-sdk/azure@npm:2.0.16"
"@ai-sdk/gateway@npm:1.0.23":
version: 1.0.23
resolution: "@ai-sdk/gateway@npm:1.0.23"
dependencies:
"@ai-sdk/openai": "npm:2.0.16"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.4"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/49bd9d27cba3104ba5d8a82c70a16dd475572585c5187e5bc29c9d46a30a373338181b29f37dfe9f61f50b5b82e86808139c93da225eb1721cb15e1a8b97cceb
checksum: 10c0/b1e1a6ab63b9191075eed92c586cd927696f8997ad24f056585aee3f5fffd283d981aa6b071a2560ecda4295445b80a4cfd321fa63c06e7ac54a06bc4c84887f
languageName: node
linkType: hard
"@ai-sdk/deepseek@npm:^1.0.9":
version: 1.0.9
resolution: "@ai-sdk/deepseek@npm:1.0.9"
"@ai-sdk/google-vertex@npm:^3.0.27":
version: 3.0.27
resolution: "@ai-sdk/google-vertex@npm:3.0.27"
dependencies:
"@ai-sdk/openai-compatible": "npm:1.0.9"
"@ai-sdk/anthropic": "npm:2.0.17"
"@ai-sdk/google": "npm:2.0.14"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.4"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/b02a000a98a6df9808d472bf63640ee96297f9acce7422de0d198ffda40edcbcadc0946ae383464b80a92ac033a3a61cf71fa1bc640c08cac589bebc8d5623b9
languageName: node
linkType: hard
"@ai-sdk/gateway@npm:1.0.20":
version: 1.0.20
resolution: "@ai-sdk/gateway@npm:1.0.20"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.8"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/c25e98aab2513f783b2b552245b027e5a73b209d974e25bbfae0e69b67fd3468bba0bf57085ca3d7259b4dc8881e7f40fca769f698f0b1eb028a849f587ad09c
languageName: node
linkType: hard
"@ai-sdk/google-vertex@npm:^3.0.25":
version: 3.0.25
resolution: "@ai-sdk/google-vertex@npm:3.0.25"
dependencies:
"@ai-sdk/anthropic": "npm:2.0.15"
"@ai-sdk/google": "npm:2.0.13"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.8"
"@ai-sdk/provider-utils": "npm:3.0.9"
google-auth-library: "npm:^9.15.0"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/ed67a439fc4a446aa7353d258c61497198aecdf0de55500d2abbea86109bbf1ff4570fffdfcf58508db1c887a2095a71322777634f76326a45e259d28ef0b801
checksum: 10c0/7017838aef9c04c18ce9acec52eb602ee0a38d68a7496977a3898411f1ac235b2d7776011fa686084b90b0881e65c69596014e5465b8ed0d0e313b5db1f967a7
languageName: node
linkType: hard
"@ai-sdk/google@npm:2.0.13, @ai-sdk/google@npm:^2.0.13":
version: 2.0.13
resolution: "@ai-sdk/google@npm:2.0.13"
"@ai-sdk/google@npm:2.0.14, @ai-sdk/google@npm:^2.0.14":
version: 2.0.14
resolution: "@ai-sdk/google@npm:2.0.14"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.8"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/a05210de11d7ab41d49bcd0330c37f4116441b149d8ccc9b6bc5eaa12ea42bae82364dc2cd09502734b15115071f07395525806ea4998930b285b1ce74102186
checksum: 10c0/2c04839cf58c33514a54c9de8190c363b5cacfbfc8404fea5d2ec36ad0af5ced4fc571f978e7aa35876bd9afae138f4c700d2bc1f64a78a37d0401f6797bf8f3
languageName: node
linkType: hard
"@ai-sdk/mistral@npm:^2.0.0":
version: 2.0.4
resolution: "@ai-sdk/mistral@npm:2.0.4"
"@ai-sdk/mistral@npm:^2.0.14":
version: 2.0.14
resolution: "@ai-sdk/mistral@npm:2.0.14"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.3"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/cca88cba855d4952551ca0be748e21f0d1b54537d0c7e08f30facdfbdbac7e6894ff4a1ceb53657aaf6e4380bbaa39d3cc37d1f734d777cdc1caba004c87221f
checksum: 10c0/420be3a039095830aaf59b6f82c1f986ff4800ba5b9438e1dd85530026a42c9454a6e632b6a1a1839816609f4752d0a19140d8943ad78bb976fb5d6a37714e16
languageName: node
linkType: hard
"@ai-sdk/openai-compatible@npm:1.0.9, @ai-sdk/openai-compatible@npm:^1.0.9":
version: 1.0.9
resolution: "@ai-sdk/openai-compatible@npm:1.0.9"
"@ai-sdk/openai-compatible@npm:1.0.17, @ai-sdk/openai-compatible@npm:^1.0.17":
version: 1.0.17
resolution: "@ai-sdk/openai-compatible@npm:1.0.17"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.4"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/a98505438f7a4c0d5c1aee9fb03aae00ff726c1c5ba0eff45d00ddc30ab9f25de634fcfd111a634bd654042150b9f16a131ce3f45887f9661c0241e3807d6ad4
checksum: 10c0/53ab6111e0f44437a2e268a51fb747600844d85b0cd0d170fb87a7b68af3eb21d7728d7bbf14d71c9fcf36e7a0f94ad75f0ad6b1070e473c867ab08ef84f6564
languageName: node
linkType: hard
"@ai-sdk/openai@npm:2.0.16":
version: 2.0.16
resolution: "@ai-sdk/openai@npm:2.0.16"
"@ai-sdk/openai@npm:2.0.30, @ai-sdk/openai@npm:^2.0.30":
version: 2.0.30
resolution: "@ai-sdk/openai@npm:2.0.30"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.4"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/1ea694bd096175a67a383e73fd1f4434eeaa7ddc6c378e44f295333d9a7b4153251d405dac2d8da330f95e4d5ef58641cc8533a3e63ff4d250b3cbc66f9abfea
checksum: 10c0/90a57c1b10dac46c0bbe7e16cf9202557fb250d9f0e94a2a5fb7d95b5ea77815a56add78b00238d3823f0313c9b2c42abe865478d28a6196f72b341d32dd40af
languageName: node
linkType: hard
"@ai-sdk/openai@npm:^2.0.26":
version: 2.0.26
resolution: "@ai-sdk/openai@npm:2.0.26"
"@ai-sdk/perplexity@npm:^2.0.9":
version: 2.0.9
resolution: "@ai-sdk/perplexity@npm:2.0.9"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.8"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/b8cb01c0c38525c38901f41f1693cd15589932a2aceddea14bed30f44719532a5e74615fb0e974eff1a0513048ac204c27456ff8829a9c811d1461cc635c9cc5
checksum: 10c0/2023aadc26c41430571c4897df79074e7a95a12f2238ad57081355484066bcf9e8dfde1da60fa6af12fc9fb2a195899326f753c69f4913dc005a33367f150349
languageName: node
linkType: hard
"@ai-sdk/perplexity@npm:^2.0.8":
version: 2.0.8
resolution: "@ai-sdk/perplexity@npm:2.0.8"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.8"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/acfd6c09c4c0ef5af7eeec6e8bc20b90b24d1d3fc2bc8ee9de4e40770fc0c17ca2c8db8f0248ff07264b71e5aa65f64d37a165db2f43fee84c1b3513cb97983c
languageName: node
linkType: hard
"@ai-sdk/provider-utils@npm:3.0.3":
version: 3.0.3
resolution: "@ai-sdk/provider-utils@npm:3.0.3"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@standard-schema/spec": "npm:^1.0.0"
eventsource-parser: "npm:^3.0.3"
zod-to-json-schema: "npm:^3.24.1"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/f02e26a6b85ef728862505b150475ef2e52d60130ca64b23316ff7b952f1817b01f959b9e48819dad64d82a96ba4ad538610d69dbbfe5be4b4b38469c16a6ccf
languageName: node
linkType: hard
"@ai-sdk/provider-utils@npm:3.0.4, @ai-sdk/provider-utils@npm:^3.0.4":
version: 3.0.4
resolution: "@ai-sdk/provider-utils@npm:3.0.4"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@standard-schema/spec": "npm:^1.0.0"
eventsource-parser: "npm:^3.0.3"
zod-to-json-schema: "npm:^3.24.1"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/6732b99310561d72262cdeef40cc58190afa55248dca0eb3a378ef87fede12086e534c68687e0fe5ef5b092da41f3e745857ce3f9b248a272a78c0dc268dffd4
languageName: node
linkType: hard
"@ai-sdk/provider-utils@npm:3.0.8":
version: 3.0.8
resolution: "@ai-sdk/provider-utils@npm:3.0.8"
"@ai-sdk/provider-utils@npm:3.0.9, @ai-sdk/provider-utils@npm:^3.0.9":
version: 3.0.9
resolution: "@ai-sdk/provider-utils@npm:3.0.9"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@standard-schema/spec": "npm:^1.0.0"
eventsource-parser: "npm:^3.0.5"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/f466657c886cbb9f7ecbcd2dd1abc51a88af9d3f1cff030f7e97e70a4790a99f3338ad886e9c0dccf04dacdcc84522c7d57119b9a4e8e1d84f2dae9c893c397e
checksum: 10c0/f8b659343d7e22ae099f7b6fc514591c0408012eb0aa00f7a912798b6d7d7305cafa8f18a07c7adec0bb5d39d9b6256b76d65c5393c3fc843d1361c52f1f8080
languageName: node
linkType: hard
@ -301,16 +237,16 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/xai@npm:^2.0.9":
version: 2.0.9
resolution: "@ai-sdk/xai@npm:2.0.9"
"@ai-sdk/xai@npm:^2.0.18":
version: 2.0.18
resolution: "@ai-sdk/xai@npm:2.0.18"
dependencies:
"@ai-sdk/openai-compatible": "npm:1.0.9"
"@ai-sdk/openai-compatible": "npm:1.0.17"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.4"
"@ai-sdk/provider-utils": "npm:3.0.9"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/15a3ace8e06b42ee148d8d100cdf946919e0763c45fb1b85454e313d4de43426c6d162c333d07ad338a9de415dc9e68c50411a6ec0305dbc5edb7d623c2023da
checksum: 10c0/7134501a2d315ec13605558aa24d7f5662885fe8b0491a634abefeb0c5c88517149677d1beff0c8abeec78a6dcd14573a2f57d96fa54a1d63d03820ac7ff827a
languageName: node
linkType: hard
@ -2468,19 +2404,19 @@ __metadata:
languageName: node
linkType: hard
"@cherrystudio/ai-core@workspace:*, @cherrystudio/ai-core@workspace:packages/aiCore":
"@cherrystudio/ai-core@workspace:^1.0.0-alpha.16, @cherrystudio/ai-core@workspace:packages/aiCore":
version: 0.0.0-use.local
resolution: "@cherrystudio/ai-core@workspace:packages/aiCore"
dependencies:
"@ai-sdk/anthropic": "npm:^2.0.5"
"@ai-sdk/azure": "npm:^2.0.16"
"@ai-sdk/deepseek": "npm:^1.0.9"
"@ai-sdk/google": "npm:^2.0.13"
"@ai-sdk/openai": "npm:^2.0.26"
"@ai-sdk/openai-compatible": "npm:^1.0.9"
"@ai-sdk/anthropic": "npm:^2.0.17"
"@ai-sdk/azure": "npm:^2.0.30"
"@ai-sdk/deepseek": "npm:^1.0.17"
"@ai-sdk/google": "npm:^2.0.14"
"@ai-sdk/openai": "npm:^2.0.30"
"@ai-sdk/openai-compatible": "npm:^1.0.17"
"@ai-sdk/provider": "npm:^2.0.0"
"@ai-sdk/provider-utils": "npm:^3.0.4"
"@ai-sdk/xai": "npm:^2.0.9"
"@ai-sdk/provider-utils": "npm:^3.0.9"
"@ai-sdk/xai": "npm:^2.0.18"
tsdown: "npm:^0.12.9"
typescript: "npm:^5.0.0"
vitest: "npm:^3.2.4"
@ -14115,6 +14051,87 @@ __metadata:
languageName: node
linkType: hard
"@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20250915.1":
version: 7.0.0-dev.20250915.1
resolution: "@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20250915.1"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20250915.1":
version: 7.0.0-dev.20250915.1
resolution: "@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20250915.1"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20250915.1":
version: 7.0.0-dev.20250915.1
resolution: "@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20250915.1"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-linux-arm@npm:7.0.0-dev.20250915.1":
version: 7.0.0-dev.20250915.1
resolution: "@typescript/native-preview-linux-arm@npm:7.0.0-dev.20250915.1"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@typescript/native-preview-linux-x64@npm:7.0.0-dev.20250915.1":
version: 7.0.0-dev.20250915.1
resolution: "@typescript/native-preview-linux-x64@npm:7.0.0-dev.20250915.1"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20250915.1":
version: 7.0.0-dev.20250915.1
resolution: "@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20250915.1"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-win32-x64@npm:7.0.0-dev.20250915.1":
version: 7.0.0-dev.20250915.1
resolution: "@typescript/native-preview-win32-x64@npm:7.0.0-dev.20250915.1"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview@npm:latest":
version: 7.0.0-dev.20250915.1
resolution: "@typescript/native-preview@npm:7.0.0-dev.20250915.1"
dependencies:
"@typescript/native-preview-darwin-arm64": "npm:7.0.0-dev.20250915.1"
"@typescript/native-preview-darwin-x64": "npm:7.0.0-dev.20250915.1"
"@typescript/native-preview-linux-arm": "npm:7.0.0-dev.20250915.1"
"@typescript/native-preview-linux-arm64": "npm:7.0.0-dev.20250915.1"
"@typescript/native-preview-linux-x64": "npm:7.0.0-dev.20250915.1"
"@typescript/native-preview-win32-arm64": "npm:7.0.0-dev.20250915.1"
"@typescript/native-preview-win32-x64": "npm:7.0.0-dev.20250915.1"
dependenciesMeta:
"@typescript/native-preview-darwin-arm64":
optional: true
"@typescript/native-preview-darwin-x64":
optional: true
"@typescript/native-preview-linux-arm":
optional: true
"@typescript/native-preview-linux-arm64":
optional: true
"@typescript/native-preview-linux-x64":
optional: true
"@typescript/native-preview-win32-arm64":
optional: true
"@typescript/native-preview-win32-x64":
optional: true
bin:
tsgo: bin/tsgo.js
checksum: 10c0/88c8c4d497e610b05ef3a429959364fff7f0fc2b77f191909c15f886b21b06ceabdd9f89d9e5f903ee87076cfeca4d61ee609d2df897326ed115e23e01650fec
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.43.0":
version: 8.43.0
resolution: "@typescript-eslint/visitor-keys@npm:8.43.0"
@ -14878,10 +14895,10 @@ __metadata:
"@agentic/exa": "npm:^7.3.3"
"@agentic/searxng": "npm:^7.3.3"
"@agentic/tavily": "npm:^7.3.3"
"@ai-sdk/amazon-bedrock": "npm:^3.0.0"
"@ai-sdk/google-vertex": "npm:^3.0.25"
"@ai-sdk/mistral": "npm:^2.0.0"
"@ai-sdk/perplexity": "npm:^2.0.8"
"@ai-sdk/amazon-bedrock": "npm:^3.0.21"
"@ai-sdk/google-vertex": "npm:^3.0.27"
"@ai-sdk/mistral": "npm:^2.0.14"
"@ai-sdk/perplexity": "npm:^2.0.9"
"@ant-design/v5-patch-for-react-19": "npm:^1.0.3"
"@anthropic-ai/sdk": "npm:^0.41.0"
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch"
@ -14889,7 +14906,7 @@ __metadata:
"@aws-sdk/client-bedrock-runtime": "npm:^3.840.0"
"@aws-sdk/client-s3": "npm:^3.840.0"
"@biomejs/biome": "npm:2.2.4"
"@cherrystudio/ai-core": "workspace:*"
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.16"
"@cherrystudio/embedjs": "npm:^0.1.31"
"@cherrystudio/embedjs-libsql": "npm:^0.1.31"
"@cherrystudio/embedjs-loader-csv": "npm:^0.1.31"
@ -14989,6 +15006,7 @@ __metadata:
"@types/tinycolor2": "npm:^1"
"@types/turndown": "npm:^5.0.5"
"@types/word-extractor": "npm:^1"
"@typescript/native-preview": "npm:latest"
"@uiw/codemirror-extensions-langs": "npm:^4.25.1"
"@uiw/codemirror-themes-all": "npm:^4.25.1"
"@uiw/react-codemirror": "npm:^4.25.1"
@ -15000,7 +15018,7 @@ __metadata:
"@viz-js/lang-dot": "npm:^1.0.5"
"@viz-js/viz": "npm:^3.14.0"
"@xyflow/react": "npm:^12.4.4"
ai: "npm:^5.0.38"
ai: "npm:^5.0.44"
antd: "patch:antd@npm%3A5.27.0#~/.yarn/patches/antd-npm-5.27.0-aa91c36546.patch"
archiver: "npm:^7.0.1"
async-mutex: "npm:^0.5.0"
@ -15022,6 +15040,7 @@ __metadata:
diff: "npm:^8.0.2"
docx: "npm:^9.0.2"
dompurify: "npm:^3.2.6"
dotenv: "npm:^17.2.2"
dotenv-cli: "npm:^7.4.2"
drizzle-kit: "npm:^0.31.4"
drizzle-orm: "npm:^0.44.2"
@ -15261,17 +15280,17 @@ __metadata:
languageName: node
linkType: hard
"ai@npm:^5.0.38":
version: 5.0.38
resolution: "ai@npm:5.0.38"
"ai@npm:^5.0.44":
version: 5.0.44
resolution: "ai@npm:5.0.44"
dependencies:
"@ai-sdk/gateway": "npm:1.0.20"
"@ai-sdk/gateway": "npm:1.0.23"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.8"
"@ai-sdk/provider-utils": "npm:3.0.9"
"@opentelemetry/api": "npm:1.9.0"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10c0/9ea7a76ae5609574e9edb2f9541e2fe9cf0e7296547c5e9ae30ec000206c967b4c07fbb03b85f9027493f6877e15f6bfbe454faa793fca860826acf306982fc5
checksum: 10c0/528c7e165f75715194204051ce0aa341d8dca7d5536c2abcf3df83ccda7399ed5d91deaa45a81340f93d2461b1c2fc5f740f7804dfd396927c71b0667403569b
languageName: node
linkType: hard
@ -18405,6 +18424,13 @@ __metadata:
languageName: node
linkType: hard
"dotenv@npm:^17.2.2":
version: 17.2.2
resolution: "dotenv@npm:17.2.2"
checksum: 10c0/be66513504590aff6eccb14167625aed9bd42ce80547f4fe5d195860211971a7060949b57108dfaeaf90658f79e40edccd3f233f0a978bff507b5b1565ae162b
languageName: node
linkType: hard
"drizzle-kit@npm:^0.31.4":
version: 0.31.4
resolution: "drizzle-kit@npm:0.31.4"
@ -19659,7 +19685,7 @@ __metadata:
languageName: node
linkType: hard
"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.3":
"eventsource-parser@npm:^3.0.0":
version: 3.0.3
resolution: "eventsource-parser@npm:3.0.3"
checksum: 10c0/2594011630efba56cafafc8ed6bd9a50db8f6d5dd62089b0950346e7961828c16efe07a588bdea3ba79e568fd9246c8163824a2ffaade767e1fdb2270c1fae0b