From d44654f003fb377eee5b8e788284971588a6903b Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Tue, 16 Sep 2025 00:53:46 +0800 Subject: [PATCH 1/4] chore: add tailwindcss and vitest extensions to vscode config (#10169) --- .vscode/extensions.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 167792154a..79046aa441 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,6 +3,8 @@ "dbaeumer.vscode-eslint", "editorconfig.editorconfig", "lokalise.i18n-ally", + "bradlc.vscode-tailwindcss", + "vitest.explorer", "oxc.oxc-vscode", "biomejs.biome" ] From c641b116baed315d6de722dbb11cb31f201c722a Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Tue, 16 Sep 2025 02:10:28 +0800 Subject: [PATCH 2/4] fix(markdown): broken layout in translate page if rendered as long markdown (#10187) * style(markdown): improve code block styling and layout - Add text-wrap to pre elements for better readability - Force background color for code blocks with !important - Change display to grid in translate page for consistent layout - Add overflow hidden to output container * style(markdown): remove redundant !important from code block styling Move !important declaration to output container's markdown pre selector where it's actually needed --- src/renderer/src/assets/styles/markdown.css | 1 + src/renderer/src/pages/translate/TranslatePage.tsx | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/assets/styles/markdown.css b/src/renderer/src/assets/styles/markdown.css index 5a1ca236d6..395247c0b0 100644 --- a/src/renderer/src/assets/styles/markdown.css +++ b/src/renderer/src/assets/styles/markdown.css @@ -121,6 +121,7 @@ border-radius: 5px; word-break: keep-all; white-space: pre; + text-wrap: wrap; } .markdown code { diff --git a/src/renderer/src/pages/translate/TranslatePage.tsx b/src/renderer/src/pages/translate/TranslatePage.tsx index 91171fd441..e883955eb1 100644 --- a/src/renderer/src/pages/translate/TranslatePage.tsx +++ b/src/renderer/src/pages/translate/TranslatePage.tsx @@ -846,7 +846,8 @@ const ContentContainer = styled.div<{ $historyDrawerVisible: boolean }>` ` const AreaContainer = styled.div` - display: flex; + display: grid; + grid-template-columns: 1fr 1fr; flex: 1; gap: 8px; ` @@ -917,6 +918,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; From 5451e2f34ab530986c474f37e9a7393a7797ed3f Mon Sep 17 00:00:00 2001 From: SuYao Date: Tue, 16 Sep 2025 10:16:42 +0800 Subject: [PATCH 3/4] Perf/tsgo (#10188) * feat: update tsgo * chore: update alpha.15 * feat: add script * chore: update ai-core version to alpha.16 and add npm registry settings * chore --- .vscode/extensions.json | 3 +- .vscode/settings.json | 3 +- .yarnrc.yml | 2 + package.json | 23 +- packages/aiCore/package.json | 32 +- src/main/ipc.ts | 3 +- src/main/services/NotificationService.ts | 2 +- src/preload/index.ts | 2 +- .../aiCore/prepareParams/parameterBuilder.ts | 10 +- src/renderer/src/aiCore/utils/mcp.ts | 2 +- src/renderer/src/types/index.ts | 1 + tsconfig.node.json | 13 +- tsconfig.web.json | 21 +- yarn.lock | 338 ++++++++++-------- 14 files changed, 250 insertions(+), 205 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 79046aa441..a08379caed 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,6 +6,7 @@ "bradlc.vscode-tailwindcss", "vitest.explorer", "oxc.oxc-vscode", - "biomejs.biome" + "biomejs.biome", + "typescriptteam.native-preview" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 141179f38c..2d62fde832 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,5 +47,6 @@ "search.exclude": { "**/dist/**": true, ".yarn/releases/**": true - } + }, + "typescript.experimental.useTsgo": true } diff --git a/.yarnrc.yml b/.yarnrc.yml index e1e4cf05ca..f127d5c149 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -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 diff --git a/package.json b/package.json index b01cc696da..b3da1832bf 100644 --- a/package.json +++ b/package.json @@ -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", @@ -69,7 +69,10 @@ "format": "biome format --write && biome lint --write", "format:check": "biome format && biome lint", "prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky", - "claude": "dotenv -e .env -- claude" + "claude": "dotenv -e .env -- claude", + "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", @@ -94,10 +97,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", @@ -105,7 +108,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", @@ -200,6 +203,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", @@ -211,7 +215,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", @@ -233,6 +237,7 @@ "diff": "^8.0.2", "docx": "^9.0.2", "dompurify": "^3.2.6", + "dotenv": "^17.2.2", "dotenv-cli": "^7.4.2", "electron": "37.4.0", "electron-builder": "26.0.15", diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index cdf68d9018..292b679d82 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -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", diff --git a/src/main/ipc.ts b/src/main/ipc.ts index d889a098f8..9805b7c6e6 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -11,11 +11,10 @@ import { handleZoomFactor } from '@main/utils/zoom' import { SpanEntity, TokenUsage } from '@mcp-trace/trace-core' import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' -import { FileMetadata, OcrProvider, Provider, Shortcut, SupportedOcrFile, ThemeMode } from '@types' +import { FileMetadata, Notification, OcrProvider, Provider, Shortcut, SupportedOcrFile, ThemeMode } 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' diff --git a/src/main/services/NotificationService.ts b/src/main/services/NotificationService.ts index 2ceb12ee40..fa9261aa7e 100644 --- a/src/main/services/NotificationService.ts +++ b/src/main/services/NotificationService.ts @@ -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' diff --git a/src/preload/index.ts b/src/preload/index.ts index 6633629802..cd241237b8 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -5,6 +5,7 @@ import { UpgradeChannel } from '@shared/config/constant' import type { LogLevel, LogSourceWithContext } from '@shared/config/logger' import type { FileChangeEvent } from '@shared/config/types' import { IpcChannel } from '@shared/IpcChannel' +import type { Notification } from '@types' import { AddMemoryOptions, AssistantMessage, @@ -28,7 +29,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' import type { ActionItem } from '../renderer/src/types/selectionTypes' diff --git a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts index ff51b07973..3f9e9ff071 100644 --- a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts +++ b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts @@ -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, { 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 } // 构建基础参数 diff --git a/src/renderer/src/aiCore/utils/mcp.ts b/src/renderer/src/aiCore/utils/mcp.ts index e7f6b5a393..9606d9ea6e 100644 --- a/src/renderer/src/aiCore/utils/mcp.ts +++ b/src/renderer/src/aiCore/utils/mcp.ts @@ -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 | undefined { +export function setupToolsConfig(mcpTools?: MCPTool[]): Record> | undefined { let tools: ToolSet = {} if (!mcpTools?.length) { diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 9518f1b38a..cd2562e55a 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -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 = { diff --git a/tsconfig.node.json b/tsconfig.node.json index 0e9a295f97..b6f9061cdf 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -19,14 +19,13 @@ "electron-vite/node", "vitest/globals" ], - "baseUrl": ".", "paths": { - "@logger": ["src/main/services/LoggerService"], - "@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"], + "@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, diff --git a/tsconfig.web.json b/tsconfig.web.json index 5936bfaa03..07c1b41066 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -16,19 +16,18 @@ "incremental": true, "tsBuildInfoFile": ".tsbuildinfo/tsconfig.web.tsbuildinfo", "jsx": "react-jsx", - "baseUrl": ".", "moduleResolution": "bundler", "paths": { - "@logger": ["src/renderer/src/services/LoggerService"], - "@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"] + "@logger": ["./src/renderer/src/services/LoggerService"], + "@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"] }, "experimentalDecorators": true, "emitDecoratorMetadata": true, diff --git a/yarn.lock b/yarn.lock index daaf739c67..630c46c05c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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 @@ -2373,19 +2309,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" @@ -12414,6 +12350,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 + "@uiw/codemirror-extensions-basic-setup@npm:4.25.1": version: 4.25.1 resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.1" @@ -13167,10 +13184,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" @@ -13178,7 +13195,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" @@ -13277,6 +13294,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" @@ -13288,7 +13306,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" @@ -13310,6 +13328,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" electron: "npm:37.4.0" electron-builder: "npm:26.0.15" @@ -13546,17 +13565,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 @@ -16612,6 +16631,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 + "dts-resolver@npm:^2.1.1": version: 2.1.1 resolution: "dts-resolver@npm:2.1.1" @@ -17561,7 +17587,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 From 1a5138c5b122a7dccc2359e6d6f9e95adf319ade Mon Sep 17 00:00:00 2001 From: one Date: Tue, 16 Sep 2025 11:10:14 +0800 Subject: [PATCH 4/4] refactor: quick panel and inputbar tools (#10142) * refactor: add QuickPanelReservedSymbol * refactor: pass assistant id instead of assistant object * refactor: simplify InputbarTools props * refactor: add root symbol * refactor: merge file panel to attachment button * refactor: extract ActionButton for reuse * chore: update CLAUDE.md * fix: colors * refactor: add more reserved symbols * fix: keep updateAssistant stable * refactor(components): replace styled-components with cn utility in ActionIconButton - Replace styled-components with cn utility from @heroui/react for better maintainability - Add new --icon and --color-icon CSS variables for consistent icon coloring - Simplify button styling using Tailwind CSS classes --------- Co-authored-by: icarus --- CLAUDE.md | 1 + src/renderer/src/assets/styles/tailwind.css | 3 + .../components/Buttons/ActionIconButton.tsx | 30 +++ src/renderer/src/components/Buttons/index.ts | 1 + src/renderer/src/components/ContentSearch.tsx | 26 +-- .../src/components/QuickPanel/types.ts | 13 ++ src/renderer/src/hooks/useAssistant.ts | 2 +- src/renderer/src/i18n/locales/en-us.json | 3 +- src/renderer/src/i18n/locales/zh-cn.json | 3 +- src/renderer/src/i18n/locales/zh-tw.json | 3 +- .../pages/home/Inputbar/AttachmentButton.tsx | 109 +++++++-- .../home/Inputbar/GenerateImageButton.tsx | 13 +- .../src/pages/home/Inputbar/Inputbar.tsx | 206 ++---------------- .../src/pages/home/Inputbar/InputbarTools.tsx | 195 +++++++++-------- .../home/Inputbar/KnowledgeBaseButton.tsx | 24 +- .../pages/home/Inputbar/MCPToolsButton.tsx | 29 ++- .../home/Inputbar/MentionModelsButton.tsx | 20 +- .../pages/home/Inputbar/NewContextButton.tsx | 9 +- .../home/Inputbar/QuickPhrasesButton.tsx | 28 ++- .../pages/home/Inputbar/ThinkingButton.tsx | 83 +++---- .../src/pages/home/Inputbar/TokenCount.tsx | 1 - .../pages/home/Inputbar/UrlContextbutton.tsx | 20 +- .../pages/home/Inputbar/WebSearchButton.tsx | 38 ++-- .../src/pages/home/Messages/MessageEditor.tsx | 15 +- src/renderer/src/store/assistants.ts | 4 +- 25 files changed, 421 insertions(+), 458 deletions(-) create mode 100644 src/renderer/src/components/Buttons/ActionIconButton.tsx create mode 100644 src/renderer/src/components/Buttons/index.ts diff --git a/CLAUDE.md b/CLAUDE.md index 5c66ab4fed..58b01fe853 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/src/renderer/src/assets/styles/tailwind.css b/src/renderer/src/assets/styles/tailwind.css index 4d9e0d4f00..f05b01b65c 100644 --- a/src/renderer/src/assets/styles/tailwind.css +++ b/src/renderer/src/assets/styles/tailwind.css @@ -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); diff --git a/src/renderer/src/components/Buttons/ActionIconButton.tsx b/src/renderer/src/components/Buttons/ActionIconButton.tsx new file mode 100644 index 0000000000..1448008090 --- /dev/null +++ b/src/renderer/src/components/Buttons/ActionIconButton.tsx @@ -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 = ({ children, active = false, className, ...props }) => { + return ( + + ) +} + +export default memo(ActionIconButton) diff --git a/src/renderer/src/components/Buttons/index.ts b/src/renderer/src/components/Buttons/index.ts new file mode 100644 index 0000000000..623c2f21cc --- /dev/null +++ b/src/renderer/src/components/Buttons/index.ts @@ -0,0 +1 @@ +export { default as ActionIconButton } from './ActionIconButton' diff --git a/src/renderer/src/components/ContentSearch.tsx b/src/renderer/src/components/ContentSearch.tsx index 084a79a439..d322f41616 100644 --- a/src/renderer/src/components/ContentSearch.tsx +++ b/src/renderer/src/components/ContentSearch.tsx @@ -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( {showUserToggle && ( - + - + )} - + - + - + - + @@ -397,15 +397,15 @@ export const ContentSearch = React.forwardRef( )} - + - - + + - - + + - + diff --git a/src/renderer/src/components/QuickPanel/types.ts b/src/renderer/src/components/QuickPanel/types.ts index 97e072dea0..812ec153a6 100644 --- a/src/renderer/src/components/QuickPanel/types.ts +++ b/src/renderer/src/components/QuickPanel/types.ts @@ -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' diff --git a/src/renderer/src/hooks/useAssistant.ts b/src/renderer/src/hooks/useAssistant.ts index 258048f0b1..096c91b5a1 100644 --- a/src/renderer/src/hooks/useAssistant.ts +++ b/src/renderer/src/hooks/useAssistant.ts @@ -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) => dispatch(updateAssistant(assistant)), [dispatch]), updateAssistantSettings } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index c4e7d2aa72..4ac54e374a 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -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", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index ecf4e8c431..248dbad623 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -363,8 +363,9 @@ "translate": "翻译成 {{target_language}}", "translating": "翻译中...", "upload": { + "attachment": "上传附件", "document": "上传文档(模型不支持图片)", - "label": "上传图片或文档", + "image_or_document": "上传图片或文档", "upload_from_local": "上传本地文件..." }, "url_context": "网页上下文", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 5f4118bd95..4b31ca44be 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -362,8 +362,9 @@ "translate": "翻譯成 {{target_language}}", "translating": "翻譯中...", "upload": { + "attachment": "上傳附件", "document": "上傳文件(模型不支援圖片)", - "label": "上傳圖片或文件", + "image_or_document": "上傳圖片或文件", "upload_from_local": "上傳本地文件..." }, "url_context": "網頁上下文", diff --git a/src/renderer/src/pages/home/Inputbar/AttachmentButton.tsx b/src/renderer/src/pages/home/Inputbar/AttachmentButton.tsx index dd7cd8b301..df0dbf9f5a 100644 --- a/src/renderer/src/pages/home/Inputbar/AttachmentButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/AttachmentButton.tsx @@ -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> disabled?: boolean } -const AttachmentButton: FC = ({ - ref, - couldAddImageFile, - extensions, - files, - setFiles, - ToolbarButton, - disabled -}) => { +const AttachmentButton: FC = ({ ref, couldAddImageFile, extensions, files, setFiles, disabled }) => { const { t } = useTranslation() + const quickPanel = useQuickPanel() + const { bases: knowledgeBases } = useKnowledgeBases() const [selecting, setSelecting] = useState(false) - const onSelectFile = useCallback(async () => { + const openFileSelectDialog = useCallback(async () => { if (selecting) { return } @@ -70,23 +68,88 @@ const AttachmentButton: FC = ({ } }, [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: , + 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: , + 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: , + 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 ( - - - + 0} disabled={disabled}> + + ) } diff --git a/src/renderer/src/pages/home/Inputbar/GenerateImageButton.tsx b/src/renderer/src/pages/home/Inputbar/GenerateImageButton.tsx index eadcea8b82..dc6553188c 100644 --- a/src/renderer/src/pages/home/Inputbar/GenerateImageButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/GenerateImageButton.tsx @@ -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 = ({ model, ToolbarButton, assistant, onEnableGenerateImage }) => { +const GenerateImageButton: FC = ({ model, assistant, onEnableGenerateImage }) => { const { t } = useTranslation() return ( @@ -23,9 +23,12 @@ const GenerateImageButton: FC = ({ model, ToolbarButton, assistant, onEna } mouseLeaveDelay={0} arrow> - - - + + + ) } diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 14d209fb27..864ea71a21 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -1,25 +1,23 @@ import { HolderOutlined } from '@ant-design/icons' 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, useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' -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' @@ -27,7 +25,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 WebSearchService from '@renderer/services/WebSearchService' import { useAppDispatch, useAppSelector } from '@renderer/store' import { setSearching } from '@renderer/store/runtime' 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 = ({ assistant: _assistant, setActiveTopic, topic }) = const [textareaHeight, setTextareaHeight] = useState() const startDragY = useRef(0) const startHeight = useRef(0) - const { bases: knowledgeBases } = useKnowledgeBases() const isMultiSelectMode = useAppSelector((state) => state.runtime.chat.isMultiSelectMode) const isVisionAssistant = useMemo(() => isVisionModel(model), [model]) const isGenerateImageAssistant = useMemo(() => isGenerateImageModel(model), [model]) @@ -134,11 +128,6 @@ const Inputbar: FC = ({ 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 = ({ 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 = ({ 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: , - 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: , - 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: , - disabled: length === 0, - isMenu: true, - action: () => openKnowledgeFileList(base) - } - }) - ], - symbol: 'file' - }) - }, [knowledgeBases, openKnowledgeFileList, quickPanel, t, inputbarToolsRef]) - const handleKeyDown = (event: React.KeyboardEvent) => { // 按下Tab键,自动选中${xxx} if (event.key === 'Tab' && inputFocus) { @@ -512,35 +433,31 @@ const Inputbar: FC = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ assistant: _assistant, setActiveTopic, topic }) = {loading && ( - + - + )} @@ -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 diff --git a/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx b/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx index 82a0071ee9..36b17a8bc2 100644 --- a/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx +++ b/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx @@ -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> extensions: string[] - showThinkingButton: boolean - showKnowledgeIcon: boolean - showMcpTools: boolean - selectedKnowledgeBases: KnowledgeBase[] - handleKnowledgeBaseSelect: (bases?: KnowledgeBase[]) => void setText: Dispatch> resizeTextArea: () => void - mentionModels: Model[] - onMentionModel: (model: Model) => void - onClearMentionModels: () => void - couldMentionNotVisionModel: boolean + selectedKnowledgeBases: KnowledgeBase[] + setSelectedKnowledgeBases: Dispatch> + mentionedModels: Model[] + setMentionedModels: Dispatch> 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 }) => { const { t } = useTranslation() const dispatch = useAppDispatch() + const { assistant, updateAssistant } = useAssistant(assistantId) const quickPhrasesButtonRef = useRef(null) const mentionModelsButtonRef = useRef(null) @@ -143,6 +135,54 @@ const InputbarTools = ({ const [targetTool, setTargetTool] = useState(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: , 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> - + - + ) }, { key: 'attachment', - label: t('chat.input.upload.label'), + label: t('chat.input.upload.image_or_document'), component: ( ) }, { key: 'thinking', label: t('chat.input.thinking.label'), - component: ( - - ), + component: , condition: showThinkingButton }, { key: 'web_search', label: t('chat.input.web_search.label'), - component: , + component: , condition: !isMandatoryWebSearchModel(model) }, { key: 'url_context', label: t('chat.input.url_context'), - component: , + component: , 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: ( ), - condition: showMcpTools + condition: showMcpServerButton }, { key: 'generate_image', label: t('chat.input.generate_image'), component: ( - + ), condition: isGenerateImageModel(model) }, @@ -400,10 +425,9 @@ const InputbarTools = ({ component: ( ) }, @@ -429,12 +452,12 @@ const InputbarTools = ({ component: ( - + - + ) }, @@ -447,22 +470,22 @@ const InputbarTools = ({ title={isExpended ? t('chat.input.collapse') : t('chat.input.expand')} mouseLeaveDelay={0} arrow> - + {isExpended ? : } - + ) }, { key: 'new_context', label: t('chat.input.new.context', { Command: '' }), - component: + component: } ] }, [ 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> - dispatch(setIsCollapsed(!isCollapse))}> + dispatch(setIsCollapsed(!isCollapse))}> - + )} diff --git a/src/renderer/src/pages/home/Inputbar/KnowledgeBaseButton.tsx b/src/renderer/src/pages/home/Inputbar/KnowledgeBaseButton.tsx index bdb1a45353..552017e424 100644 --- a/src/renderer/src/pages/home/Inputbar/KnowledgeBaseButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/KnowledgeBaseButton.tsx @@ -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 = ({ ref, selectedBases, onSelect, disabled, ToolbarButton }) => { +const KnowledgeBaseButton: FC = ({ ref, selectedBases, onSelect, disabled }) => { const { t } = useTranslation() const navigate = useNavigate() const quickPanel = useQuickPanel() @@ -77,7 +77,7 @@ const KnowledgeBaseButton: FC = ({ 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 = ({ 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 = ({ 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 = ({ ref, selectedBases, onSelect, disabled return ( - - 0 ? 'var(--color-primary)' : 'var(--color-icon)'} - /> - + 0} + disabled={disabled}> + + ) } diff --git a/src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx b/src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx index ae11cc1914..6680177b24 100644 --- a/src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx @@ -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 setInputValue: React.Dispatch> resizeTextArea: () => void - ToolbarButton: any } // 添加类型定义 @@ -113,14 +113,14 @@ const extractPromptContent = (response: any): string | null => { return null } -const MCPToolsButton: FC = ({ ref, setInputValue, resizeTextArea, ToolbarButton, ...props }) => { +const MCPToolsButton: FC = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ ref, setInputValue, resizeTextArea, Toolbar return ( - - 0 ? 'var(--color-primary)' : 'var(--color-icon)'} - /> - + 0}> + + ) } diff --git a/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx b/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx index b252de981d..ceaa748bf5 100644 --- a/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx @@ -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> } @@ -38,7 +37,6 @@ const MentionModelsButton: FC = ({ onClearMentionModels, couldMentionNotVisionModel, files, - ToolbarButton, setText }) => { const { providers } = useProviders() @@ -242,7 +240,7 @@ const MentionModelsButton: FC = ({ 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 = ({ ) 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 = ({ 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 = ({ // 监听 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 = ({ return ( - - 0 ? 'var(--color-primary)' : 'var(--color-icon)'} /> - + 0}> + + ) } diff --git a/src/renderer/src/pages/home/Inputbar/NewContextButton.tsx b/src/renderer/src/pages/home/Inputbar/NewContextButton.tsx index 26c9941cff..7cf9237a20 100644 --- a/src/renderer/src/pages/home/Inputbar/NewContextButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/NewContextButton.tsx @@ -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 = ({ onNewContext, ToolbarButton }) => { +const NewContextButton: FC = ({ onNewContext }) => { const newContextShortcut = useShortcutDisplay('toggle_new_context') const { t } = useTranslation() @@ -21,9 +20,9 @@ const NewContextButton: FC = ({ onNewContext, ToolbarButton }) => { title={t('chat.input.new.context', { Command: newContextShortcut })} mouseLeaveDelay={0} arrow> - + - + ) } diff --git a/src/renderer/src/pages/home/Inputbar/QuickPhrasesButton.tsx b/src/renderer/src/pages/home/Inputbar/QuickPhrasesButton.tsx index a6d0de67c6..e01447b3fa 100644 --- a/src/renderer/src/pages/home/Inputbar/QuickPhrasesButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/QuickPhrasesButton.tsx @@ -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 setInputValue: React.Dispatch> 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([]) 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 ( <> - + - + model: Model - assistant: Assistant - ToolbarButton: any + assistantId: string } -const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): ReactElement => { +const ThinkingButton: FC = ({ 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 = ({ 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 - case option === 'low': - return - case option === 'medium': - return - case option === 'high': - return - case option === 'auto': - return - case option === 'off': - return - default: - return - } - }, []) - const onThinkingChange = useCallback( (option?: ThinkingOption) => { const isEnabled = option !== undefined && option !== 'off' @@ -98,11 +77,11 @@ const ThinkingButton: FC = ({ 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 = ({ 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 = ({ 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 = ({ ref, model, assistant, ToolbarButton }): Re } mouseLeaveDelay={0} arrow> - - {getThinkingIcon()} - + + {ThinkingIcon(currentReasoningEffort)} + ) } +const ThinkingIcon = (option?: ThinkingOption) => { + let IconComponent: React.FC> | 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 +} + export default ThinkingButton diff --git a/src/renderer/src/pages/home/Inputbar/TokenCount.tsx b/src/renderer/src/pages/home/Inputbar/TokenCount.tsx index d1d8f5f06e..7316e3549f 100644 --- a/src/renderer/src/pages/home/Inputbar/TokenCount.tsx +++ b/src/renderer/src/pages/home/Inputbar/TokenCount.tsx @@ -11,7 +11,6 @@ type Props = { estimateTokenCount: number inputTokenCount: number contextCount: { current: number; max: number } - ToolbarButton: any } & React.HTMLAttributes const TokenCount: FC = ({ estimateTokenCount, inputTokenCount, contextCount }) => { diff --git a/src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx b/src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx index e5e4c939b7..3b96d0cd0f 100644 --- a/src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx +++ b/src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx @@ -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 - assistant: Assistant - ToolbarButton: any + assistantId: string } -const UrlContextButton: FC = ({ assistant, ToolbarButton }) => { +const UrlContextButton: FC = ({ 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 = ({ assistant, ToolbarButton }) => { return ( - - - + + + ) } diff --git a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx index 343b5dbe44..cb3c5fb6d9 100644 --- a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx @@ -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 - assistant: Assistant - ToolbarButton: any + assistantId: string } const logger = loggerService.withContext('WebSearchButton') -const WebSearchButton: FC = ({ ref, assistant, ToolbarButton }) => { +const WebSearchButton: FC = ({ 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 = ({ ref, assistant, ToolbarButton }) => { ({ pid, size = 18, color }: { pid?: WebSearchProviderId; size?: number; color?: string }) => { switch (pid) { case 'bocha': - return + return case 'exa': // size微调,视觉上和其他图标平衡一些 - return + return case 'tavily': - return + return case 'zhipu': - return + return case 'searxng': - return + return case 'local-baidu': return case 'local-bing': - return + return case 'local-google': return default: - return + return } }, [] @@ -165,13 +165,13 @@ const WebSearchButton: FC = ({ 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 = ({ ref, assistant, ToolbarButton }) => { openQuickPanel })) - const color = enableWebSearch ? 'var(--color-primary)' : 'var(--color-icon)' - return ( - - - + + + ) } diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index 3d079fb24b..953dccaa29 100644 --- a/src/renderer/src/pages/home/Messages/MessageEditor.tsx +++ b/src/renderer/src/pages/home/Messages/MessageEditor.tsx @@ -1,4 +1,5 @@ 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 @@ -346,27 +346,26 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC setFiles={setFiles} couldAddImageFile={couldAddImageFile} extensions={extensions} - ToolbarButton={ToolbarButton} /> )} - + - + - + - + {message.role === 'user' && ( - + - + )} diff --git a/src/renderer/src/store/assistants.ts b/src/renderer/src/store/assistants.ts index 15a735bc7f..8f53977835 100644 --- a/src/renderer/src/store/assistants.ts +++ b/src/renderer/src/store/assistants.ts @@ -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) => { - state.assistants = state.assistants.map((c) => (c.id === action.payload.id ? action.payload : c)) + updateAssistant: (state, action: PayloadAction>) => { + state.assistants = state.assistants.map((c) => (c.id === action.payload.id ? { ...c, ...action.payload } : c)) }, updateAssistantSettings: ( state,