diff --git a/.github/workflows/claude-translator.yml b/.github/workflows/claude-translator.yml index ab2b6f7e4f..ff317f8532 100644 --- a/.github/workflows/claude-translator.yml +++ b/.github/workflows/claude-translator.yml @@ -1,6 +1,6 @@ name: Claude Translator concurrency: - group: translator-${{ github.event.comment.id || github.event.issue.number }} + group: translator-${{ github.event.comment.id || github.event.issue.number || github.event.review.id }} cancel-in-progress: false on: @@ -8,14 +8,18 @@ on: types: [opened] issue_comment: types: [created, edited] + pull_request_review: + types: [submitted, edited] + pull_request_review_comment: + types: [created, edited] jobs: translate: if: | (github.event_name == 'issues') || - (github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') && - ((github.event_name == 'issue_comment' && github.event.action == 'created' && !contains(github.event.comment.body, 'This issue was translated by Claude')) || - (github.event_name == 'issue_comment' && github.event.action == 'edited')) + (github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') || + (github.event_name == 'pull_request_review' && github.event.sender.type != 'Bot') || + (github.event_name == 'pull_request_review_comment' && github.event.sender.type != 'Bot') runs-on: ubuntu-latest permissions: contents: read @@ -37,23 +41,44 @@ jobs: # Now `contents: read` is safe for files, but we could make a fine-grained token to control it. # See: https://github.com/anthropics/claude-code-action/blob/main/docs/security.md github_token: ${{ secrets.TOKEN_GITHUB_WRITE }} - allowed_non_write_users: '*' + allowed_non_write_users: "*" claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - claude_args: '--allowed-tools Bash(gh issue:*),Bash(gh api:repos/*/issues:*)' + claude_args: "--allowed-tools Bash(gh issue:*),Bash(gh api:repos/*/issues:*),Bash(gh api:repos/*/pulls/*/reviews/*),Bash(gh api:repos/*/pulls/comments/*)" prompt: | - 你是一个多语言翻译助手。请完成以下任务: + 你是一个多语言翻译助手。你需要响应 GitHub Webhooks 中的以下四种事件: + + - issues + - issue_comment + - pull_request_review + - pull_request_review_comment + + 请完成以下任务: + + 1. 获取当前事件的完整信息。 + + - 如果当前事件是 issues,就获取该 issues 的信息。 + - 如果当前事件是 issue_comment,就获取该 comment 的信息。 + - 如果当前事件是 pull_request_review,就获取该 review 的信息。 + - 如果当前事件是 pull_request_review_comment,就获取该 comment 的信息。 - 1. 获取当前issue/comment的完整信息 2. 智能检测内容。 - 1. 如果是已经遵循格式要求翻译过的issue/comment,检查翻译内容和原始内容是否匹配。若不匹配,则重新翻译一次令其匹配,并遵循格式要求;若匹配,则跳过任务。 - 2. 如果是未翻译过的issue/comment,检查其内容语言。若不是英文,则翻译成英文;若已经是英文,则跳过任务。 + + - 如果获取到的信息是已经遵循格式要求翻译过的内容,则检查翻译内容和原始内容是否匹配。若不匹配,则重新翻译一次令其匹配,并遵循格式要求; + - 如果获取到的信息是未翻译过的内容,检查其内容语言。若不是英文,则翻译成英文; + - 如果获取到的信息是部分翻译为英文的内容,则将其翻译为英文; + - 如果获取到的信息包含了对已翻译内容的引用,则将引用内容清理为仅含英文的内容。引用的内容不能够包含"This xxx was translated by Claude"和"Original Content`等内容。 + - 如果获取到的信息包含了其他类型的引用,即对非 Claude 翻译的内容的引用,则直接照原样引用,不进行翻译。 + - 如果获取到的信息是通过邮件回复的内容,则在翻译时应当将邮件内容的引用放到最后。在原始内容和翻译内容中只需要回复的内容本身,不要包含对邮件内容的引用。 + - 如果获取到的信息本身不需要任何处理,则跳过任务。 + 3. 格式要求: + - 标题:英文翻译(如果非英文) - 内容格式: > [!NOTE] - > This issue/comment was translated by Claude. + > This issue/comment/review was translated by Claude. - [英文翻译内容] + [翻译内容] ---
@@ -62,15 +87,21 @@ jobs:
4. 使用gh工具更新: + - 根据环境信息中的Event类型选择正确的命令: - - 如果Event是'issues':gh issue edit [ISSUE_NUMBER] --title "[英文标题]" --body "[翻译内容 + 原始内容]" - - 如果Event是'issue_comment':gh api -X PATCH /repos/[REPO]/issues/comments/[COMMENT_ID] -f body="[翻译内容 + 原始内容]" + - 如果 Event 是 'issues': gh issue edit [ISSUE_NUMBER] --title "[英文标题]" --body "[翻译内容 + 原始内容]" + - 如果 Event 是 'issue_comment': gh api -X PATCH /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }} -f body="[翻译内容 + 原始内容]" + - 如果 Event 是 'pull_request_review': gh api -X PUT /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/${{ github.event.review.id }} -f body="[翻译内容]" + - 如果 Event 是 'pull_request_review_comment': gh api -X PATCH /repos/${{ github.repository }}/pulls/comments/${{ github.event.comment.id }} -f body="[翻译内容 + 原始内容]" 环境信息: - Event: ${{ github.event_name }} - Issue Number: ${{ github.event.issue.number }} - Repository: ${{ github.repository }} - - Comment ID: ${{ github.event.comment.id || 'N/A' }} (only available for comment events) + - (Review) Comment ID: ${{ github.event.comment.id || 'N/A' }} + - Pull Request Number: ${{ github.event.pull_request.number || 'N/A' }} + - Review ID: ${{ github.event.review.id || 'N/A' }} + 使用以下命令获取完整信息: gh issue view ${{ github.event.issue.number }} --json title,body,comments diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a08379caed..79046aa441 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,7 +6,6 @@ "bradlc.vscode-tailwindcss", "vitest.explorer", "oxc.oxc-vscode", - "biomejs.biome", - "typescriptteam.native-preview" + "biomejs.biome" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index eeac67e316..81bbc41671 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,6 +47,5 @@ "search.exclude": { "**/dist/**": true, ".yarn/releases/**": true - }, - "typescript.experimental.useTsgo": true + } } diff --git a/electron-builder.yml b/electron-builder.yml index 80722308e4..6b7056ed3a 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -125,25 +125,16 @@ afterSign: scripts/notarize.js artifactBuildCompleted: scripts/artifact-build-completed.js releaseInfo: releaseNotes: | - ✨ 新功能: - - 重构知识库模块,提升文档处理能力和搜索性能 - - 新增 PaddleOCR 支持,增强文档识别能力 - - 支持自定义窗口控制按钮样式 - - 新增 AI SDK 包,扩展 AI 能力集成 - - 支持标签页拖拽重排序功能 - - 增强笔记编辑器的同步和日志功能 - - 🔧 性能优化: - - 优化 MCP 服务的日志记录和错误处理 - - 改进 WebView 服务的 User-Agent 处理 - - 优化迷你应用的标题栏样式和状态栏适配 - - 重构依赖管理,清理和优化 package.json - 🐛 问题修复: - - 修复输入栏无限状态更新循环问题 - - 修复窗口控制提示框的鼠标悬停延迟 - - 修复翻译输入框粘贴多内容源的处理 - - 修复导航服务初始化时序问题 - - 修复 MCP 通过 JSON 添加时的参数转换 - - 修复模型作用域服务器同步时的 URL 格式 - - 标准化工具提示图标样式 + - 修复 Anthropic API URL 处理,移除尾部斜杠并添加端点路径处理 + - 修复 MessageEditor 缺少 QuickPanelProvider 包装的问题 + - 修复 MiniWindow 高度问题 + + 🚀 性能优化: + - 优化输入栏提及模型状态缓存,在渲染间保持状态 + - 重构网络搜索参数支持模型内置搜索,新增 OpenAI Chat 和 OpenRouter 支持 + + 🔧 重构改进: + - 更新 HeroUIProvider 导入路径,改善上下文管理 + - 更新依赖项和 VSCode 开发环境配置 + - 升级 @cherrystudio/ai-core 到 v1.0.0-alpha.17 diff --git a/package.json b/package.json index 41b243339d..8c7c73627b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CherryStudio", - "version": "1.6.0-beta.7", + "version": "1.6.0-rc.2", "private": true, "description": "A powerful AI assistant for producer.", "main": "./out/main/index.js", @@ -116,7 +116,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:^1.0.0-alpha.16", + "@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18", "@cherrystudio/embedjs": "^0.1.31", "@cherrystudio/embedjs-libsql": "^0.1.31", "@cherrystudio/embedjs-loader-csv": "^0.1.31", @@ -246,7 +246,6 @@ "diff": "^8.0.2", "docx": "^9.0.2", "dompurify": "^3.2.6", - "dotenv": "^17.2.2", "dotenv-cli": "^7.4.2", "drizzle-kit": "^0.31.4", "electron": "37.4.0", @@ -344,7 +343,7 @@ "tsx": "^4.20.3", "turndown-plugin-gfm": "^1.0.2", "tw-animate-css": "^1.3.8", - "typescript": "^5.6.2", + "typescript": "~5.8.2", "undici": "6.21.2", "unified": "^11.0.5", "uuid": "^13.0.0", @@ -385,11 +384,11 @@ "packageManager": "yarn@4.9.1", "lint-staged": { "*.{js,jsx,ts,tsx,cjs,mjs,cts,mts}": [ - "biome format --write", + "biome format --write --no-errors-on-unmatched", "eslint --fix" ], "*.{json,yml,yaml,css,html}": [ - "biome format --write" + "biome format --write --no-errors-on-unmatched" ] } } diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index 292b679d82..75ed6ea34e 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -1,6 +1,6 @@ { "name": "@cherrystudio/ai-core", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.18", "description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/packages/aiCore/src/core/options/factory.ts b/packages/aiCore/src/core/options/factory.ts index 4350e9241b..ffeb15185c 100644 --- a/packages/aiCore/src/core/options/factory.ts +++ b/packages/aiCore/src/core/options/factory.ts @@ -59,7 +59,7 @@ export function createGoogleOptions(options: ExtractProviderOptions<'google'>) { /** * 创建OpenRouter供应商选项的便捷函数 */ -export function createOpenRouterOptions(options: ExtractProviderOptions<'openrouter'>) { +export function createOpenRouterOptions(options: ExtractProviderOptions<'openrouter'> | Record) { return createProviderOptions('openrouter', options) } diff --git a/packages/aiCore/src/core/options/openrouter.ts b/packages/aiCore/src/core/options/openrouter.ts deleted file mode 100644 index b351f8fda1..0000000000 --- a/packages/aiCore/src/core/options/openrouter.ts +++ /dev/null @@ -1,38 +0,0 @@ -export type OpenRouterProviderOptions = { - models?: string[] - - /** - * https://openrouter.ai/docs/use-cases/reasoning-tokens - * One of `max_tokens` or `effort` is required. - * If `exclude` is true, reasoning will be removed from the response. Default is false. - */ - reasoning?: { - exclude?: boolean - } & ( - | { - max_tokens: number - } - | { - effort: 'high' | 'medium' | 'low' - } - ) - - /** - * A unique identifier representing your end-user, which can - * help OpenRouter to monitor and detect abuse. - */ - user?: string - - extraBody?: Record - - /** - * Enable usage accounting to get detailed token usage information. - * https://openrouter.ai/docs/use-cases/usage-accounting - */ - usage?: { - /** - * When true, includes token usage information in the response. - */ - include: boolean - } -} diff --git a/packages/aiCore/src/core/options/types.ts b/packages/aiCore/src/core/options/types.ts index 724dc30698..8571fd2296 100644 --- a/packages/aiCore/src/core/options/types.ts +++ b/packages/aiCore/src/core/options/types.ts @@ -2,9 +2,8 @@ import { type AnthropicProviderOptions } from '@ai-sdk/anthropic' import { type GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' import { type OpenAIResponsesProviderOptions } from '@ai-sdk/openai' import { type SharedV2ProviderMetadata } from '@ai-sdk/provider' - -import { type OpenRouterProviderOptions } from './openrouter' -import { type XaiProviderOptions } from './xai' +import { type XaiProviderOptions } from '@ai-sdk/xai' +import { type OpenRouterProviderOptions } from '@openrouter/ai-sdk-provider' export type ProviderOptions = SharedV2ProviderMetadata[T] diff --git a/packages/aiCore/src/core/options/xai.ts b/packages/aiCore/src/core/options/xai.ts deleted file mode 100644 index 7fe5672778..0000000000 --- a/packages/aiCore/src/core/options/xai.ts +++ /dev/null @@ -1,86 +0,0 @@ -// copy from @ai-sdk/xai/xai-chat-options.ts -// 如果@ai-sdk/xai暴露出了xaiProviderOptions就删除这个文件 - -import { z } from 'zod' - -const webSourceSchema = z.object({ - type: z.literal('web'), - country: z.string().length(2).optional(), - excludedWebsites: z.array(z.string()).max(5).optional(), - allowedWebsites: z.array(z.string()).max(5).optional(), - safeSearch: z.boolean().optional() -}) - -const xSourceSchema = z.object({ - type: z.literal('x'), - xHandles: z.array(z.string()).optional() -}) - -const newsSourceSchema = z.object({ - type: z.literal('news'), - country: z.string().length(2).optional(), - excludedWebsites: z.array(z.string()).max(5).optional(), - safeSearch: z.boolean().optional() -}) - -const rssSourceSchema = z.object({ - type: z.literal('rss'), - links: z.array(z.url()).max(1) // currently only supports one RSS link -}) - -const searchSourceSchema = z.discriminatedUnion('type', [ - webSourceSchema, - xSourceSchema, - newsSourceSchema, - rssSourceSchema -]) - -export const xaiProviderOptions = z.object({ - /** - * reasoning effort for reasoning models - * only supported by grok-3-mini and grok-3-mini-fast models - */ - reasoningEffort: z.enum(['low', 'high']).optional(), - - searchParameters: z - .object({ - /** - * search mode preference - * - "off": disables search completely - * - "auto": model decides whether to search (default) - * - "on": always enables search - */ - mode: z.enum(['off', 'auto', 'on']), - - /** - * whether to return citations in the response - * defaults to true - */ - returnCitations: z.boolean().optional(), - - /** - * start date for search data (ISO8601 format: YYYY-MM-DD) - */ - fromDate: z.string().optional(), - - /** - * end date for search data (ISO8601 format: YYYY-MM-DD) - */ - toDate: z.string().optional(), - - /** - * maximum number of search results to consider - * defaults to 20 - */ - maxSearchResults: z.number().min(1).max(50).optional(), - - /** - * data sources to search from - * defaults to ["web", "x"] if not specified - */ - sources: z.array(searchSourceSchema).optional() - }) - .optional() -}) - -export type XaiProviderOptions = z.infer diff --git a/packages/aiCore/src/core/plugins/built-in/index.ts b/packages/aiCore/src/core/plugins/built-in/index.ts index e7dcae8738..1f8916b09a 100644 --- a/packages/aiCore/src/core/plugins/built-in/index.ts +++ b/packages/aiCore/src/core/plugins/built-in/index.ts @@ -7,5 +7,9 @@ export const BUILT_IN_PLUGIN_PREFIX = 'built-in:' export { googleToolsPlugin } from './googleToolsPlugin' export { createLoggingPlugin } from './logging' export { createPromptToolUsePlugin } from './toolUsePlugin/promptToolUsePlugin' -export type { PromptToolUseConfig, ToolUseRequestContext, ToolUseResult } from './toolUsePlugin/type' -export { webSearchPlugin } from './webSearchPlugin' +export type { + PromptToolUseConfig, + ToolUseRequestContext, + ToolUseResult +} from './toolUsePlugin/type' +export { webSearchPlugin, type WebSearchPluginConfig } from './webSearchPlugin' diff --git a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts index a7c7187bca..4845ce4ace 100644 --- a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts +++ b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts @@ -3,13 +3,16 @@ import { google } from '@ai-sdk/google' import { openai } from '@ai-sdk/openai' import { ProviderOptionsMap } from '../../../options/types' +import { OpenRouterSearchConfig } from './openrouter' /** * 从 AI SDK 的工具函数中提取参数类型,以确保类型安全。 */ -type OpenAISearchConfig = Parameters[0] -type AnthropicSearchConfig = Parameters[0] -type GoogleSearchConfig = Parameters[0] +export type OpenAISearchConfig = NonNullable[0]> +export type OpenAISearchPreviewConfig = NonNullable[0]> +export type AnthropicSearchConfig = NonNullable[0]> +export type GoogleSearchConfig = NonNullable[0]> +export type XAISearchConfig = NonNullable /** * 插件初始化时接收的完整配置对象 @@ -18,10 +21,12 @@ type GoogleSearchConfig = Parameters[0] */ export interface WebSearchPluginConfig { openai?: OpenAISearchConfig + 'openai-chat'?: OpenAISearchPreviewConfig anthropic?: AnthropicSearchConfig xai?: ProviderOptionsMap['xai']['searchParameters'] google?: GoogleSearchConfig 'google-vertex'?: GoogleSearchConfig + openrouter?: OpenRouterSearchConfig } /** @@ -31,6 +36,7 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = { google: {}, 'google-vertex': {}, openai: {}, + 'openai-chat': {}, xai: { mode: 'on', returnCitations: true, @@ -39,6 +45,14 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = { }, anthropic: { maxUses: 5 + }, + openrouter: { + plugins: [ + { + id: 'web', + max_results: 5 + } + ] } } diff --git a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/index.ts b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/index.ts index d941e0d0c7..abd3ce3e2c 100644 --- a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/index.ts +++ b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/index.ts @@ -6,7 +6,7 @@ import { anthropic } from '@ai-sdk/anthropic' import { google } from '@ai-sdk/google' import { openai } from '@ai-sdk/openai' -import { createXaiOptions, mergeProviderOptions } from '../../../options' +import { createOpenRouterOptions, createXaiOptions, mergeProviderOptions } from '../../../options' import { definePlugin } from '../../' import type { AiRequestContext } from '../../types' import { DEFAULT_WEB_SEARCH_CONFIG, WebSearchPluginConfig } from './helper' @@ -31,6 +31,13 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR } break } + case 'openai-chat': { + if (config['openai-chat']) { + if (!params.tools) params.tools = {} + params.tools.web_search_preview = openai.tools.webSearchPreview(config['openai-chat']) + } + break + } case 'anthropic': { if (config.anthropic) { @@ -56,6 +63,14 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR } break } + + case 'openrouter': { + if (config.openrouter) { + const searchOptions = createOpenRouterOptions(config.openrouter) + params.providerOptions = mergeProviderOptions(params.providerOptions, searchOptions) + } + break + } } return params diff --git a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/openrouter.ts b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/openrouter.ts new file mode 100644 index 0000000000..ebf1e7bf9a --- /dev/null +++ b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/openrouter.ts @@ -0,0 +1,26 @@ +export type OpenRouterSearchConfig = { + plugins?: Array<{ + id: 'web' + /** + * Maximum number of search results to include (default: 5) + */ + max_results?: number + /** + * Custom search prompt to guide the search query + */ + search_prompt?: string + }> + /** + * Built-in web search options for models that support native web search + */ + web_search_options?: { + /** + * Maximum number of search results to include + */ + max_results?: number + /** + * Custom search prompt to guide the search query + */ + search_prompt?: string + } +} diff --git a/packages/aiCore/src/core/providers/schemas.ts b/packages/aiCore/src/core/providers/schemas.ts index e4b8d8aa64..83338cf057 100644 --- a/packages/aiCore/src/core/providers/schemas.ts +++ b/packages/aiCore/src/core/providers/schemas.ts @@ -9,7 +9,9 @@ import { createDeepSeek } from '@ai-sdk/deepseek' import { createGoogleGenerativeAI } from '@ai-sdk/google' import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai' import { createOpenAICompatible } from '@ai-sdk/openai-compatible' +import { LanguageModelV2 } from '@ai-sdk/provider' import { createXai } from '@ai-sdk/xai' +import { createOpenRouter } from '@openrouter/ai-sdk-provider' import { customProvider, Provider } from 'ai' import { z } from 'zod' @@ -25,7 +27,8 @@ export const baseProviderIds = [ 'xai', 'azure', 'azure-responses', - 'deepseek' + 'deepseek', + 'openrouter' ] as const /** @@ -38,10 +41,14 @@ export const baseProviderIdSchema = z.enum(baseProviderIds) */ export type BaseProviderId = z.infer +export const isBaseProvider = (id: ProviderId): id is BaseProviderId => { + return baseProviderIdSchema.safeParse(id).success +} + type BaseProvider = { id: BaseProviderId name: string - creator: (options: any) => Provider + creator: (options: any) => Provider | LanguageModelV2 supportsImageGeneration: boolean } @@ -119,6 +126,12 @@ export const baseProviders = [ name: 'DeepSeek', creator: createDeepSeek, supportsImageGeneration: false + }, + { + id: 'openrouter', + name: 'OpenRouter', + creator: createOpenRouter, + supportsImageGeneration: true } ] as const satisfies BaseProvider[] diff --git a/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index 18246cd1e7..3dc2a45a6a 100644 --- a/packages/shared/config/constant.ts +++ b/packages/shared/config/constant.ts @@ -216,5 +216,6 @@ export enum codeTools { qwenCode = 'qwen-code', claudeCode = 'claude-code', geminiCli = 'gemini-cli', - openaiCodex = 'openai-codex' + openaiCodex = 'openai-codex', + iFlowCli = 'iflow-cli' } diff --git a/src/main/services/CodeToolsService.ts b/src/main/services/CodeToolsService.ts index 6e3bb19022..1372bf1f88 100644 --- a/src/main/services/CodeToolsService.ts +++ b/src/main/services/CodeToolsService.ts @@ -51,6 +51,8 @@ class CodeToolsService { return '@openai/codex' case codeTools.qwenCode: return '@qwen-code/qwen-code' + case codeTools.iFlowCli: + return '@iflow-ai/iflow-cli' default: throw new Error(`Unsupported CLI tool: ${cliTool}`) } @@ -66,6 +68,8 @@ class CodeToolsService { return 'codex' case codeTools.qwenCode: return 'qwen' + case codeTools.iFlowCli: + return 'iflow' default: throw new Error(`Unsupported CLI tool: ${cliTool}`) } diff --git a/src/renderer/src/aiCore/middleware/AiSdkMiddlewareBuilder.ts b/src/renderer/src/aiCore/middleware/AiSdkMiddlewareBuilder.ts index f0d3b2eb59..eabdf1815f 100644 --- a/src/renderer/src/aiCore/middleware/AiSdkMiddlewareBuilder.ts +++ b/src/renderer/src/aiCore/middleware/AiSdkMiddlewareBuilder.ts @@ -1,3 +1,4 @@ +import { WebSearchPluginConfig } from '@cherrystudio/ai-core/built-in/plugins' import { loggerService } from '@logger' import type { MCPTool, Message, Model, Provider } from '@renderer/types' import type { Chunk } from '@renderer/types/chunk' @@ -26,6 +27,8 @@ export interface AiSdkMiddlewareConfig { enableUrlContext: boolean mcpTools?: MCPTool[] uiMessages?: Message[] + // 内置搜索配置 + webSearchPluginConfig?: WebSearchPluginConfig } /** @@ -137,7 +140,7 @@ export function buildAiSdkMiddlewares(config: AiSdkMiddlewareConfig): LanguageMo return builder.build() } -const tagNameArray = ['think', 'thought'] +const tagNameArray = ['think', 'thought', 'reasoning'] /** * 添加provider特定的中间件 @@ -164,6 +167,16 @@ function addProviderSpecificMiddlewares(builder: AiSdkMiddlewareBuilder, config: case 'gemini': // Gemini特定中间件 break + case 'aws-bedrock': { + if (config.model?.id.includes('gpt-oss')) { + const tagName = tagNameArray[2] + builder.add({ + name: 'thinking-tag-extraction', + middleware: extractReasoningMiddleware({ tagName }) + }) + } + break + } default: // 其他provider的通用处理 break diff --git a/src/renderer/src/aiCore/plugins/PluginBuilder.ts b/src/renderer/src/aiCore/plugins/PluginBuilder.ts index d94ed4aab9..7c5478eb77 100644 --- a/src/renderer/src/aiCore/plugins/PluginBuilder.ts +++ b/src/renderer/src/aiCore/plugins/PluginBuilder.ts @@ -30,9 +30,8 @@ export function buildPlugins( } // 1. 模型内置搜索 - if (middlewareConfig.enableWebSearch) { - // 内置了默认搜索参数,如果改的话可以传config进去 - plugins.push(webSearchPlugin()) + if (middlewareConfig.enableWebSearch && middlewareConfig.webSearchPluginConfig) { + plugins.push(webSearchPlugin(middlewareConfig.webSearchPluginConfig)) } // 2. 支持工具调用时添加搜索插件 if (middlewareConfig.isSupportedToolUse || middlewareConfig.isPromptToolUse) { diff --git a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts index 3f9e9ff071..0a89e73c62 100644 --- a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts +++ b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts @@ -5,6 +5,8 @@ import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic/edge' import { vertex } from '@ai-sdk/google-vertex/edge' +import { WebSearchPluginConfig } from '@cherrystudio/ai-core/built-in/plugins' +import { isBaseProvider } from '@cherrystudio/ai-core/core/providers/schemas' import { loggerService } from '@logger' import { isGenerateImageModel, @@ -16,8 +18,11 @@ import { isWebSearchModel } from '@renderer/config/models' import { getAssistantSettings, getDefaultModel } from '@renderer/services/AssistantService' +import store from '@renderer/store' +import { CherryWebSearchConfig } from '@renderer/store/websearch' import { type Assistant, type MCPTool, type Provider } from '@renderer/types' import type { StreamTextParams } from '@renderer/types/aiCoreTypes' +import { mapRegexToPatterns } from '@renderer/utils/blacklistMatchPattern' import type { ModelMessage, Tool } from 'ai' import { stepCountIs } from 'ai' @@ -25,6 +30,7 @@ import { getAiSdkProviderId } from '../provider/factory' import { setupToolsConfig } from '../utils/mcp' import { buildProviderOptions } from '../utils/options' import { getAnthropicThinkingBudget } from '../utils/reasoning' +import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch' import { getTemperature, getTopP } from './modelParameters' const logger = loggerService.withContext('parameterBuilder') @@ -42,6 +48,7 @@ export async function buildStreamTextParams( options: { mcpTools?: MCPTool[] webSearchProviderId?: string + webSearchConfig?: CherryWebSearchConfig requestOptions?: { signal?: AbortSignal timeout?: number @@ -57,6 +64,7 @@ export async function buildStreamTextParams( enableGenerateImage: boolean enableUrlContext: boolean } + webSearchPluginConfig?: WebSearchPluginConfig }> { const { mcpTools } = options @@ -93,6 +101,12 @@ export async function buildStreamTextParams( // } // 构建真正的 providerOptions + const webSearchConfig: CherryWebSearchConfig = { + maxResults: store.getState().websearch.maxResults, + excludeDomains: store.getState().websearch.excludeDomains, + searchWithTime: store.getState().websearch.searchWithTime + } + const providerOptions = buildProviderOptions(assistant, model, provider, { enableReasoning, enableWebSearch, @@ -109,15 +123,21 @@ export async function buildStreamTextParams( maxTokens -= getAnthropicThinkingBudget(assistant, model) } - // google-vertex | google-vertex-anthropic + let webSearchPluginConfig: WebSearchPluginConfig | undefined = undefined if (enableWebSearch) { + if (isBaseProvider(aiSdkProviderId)) { + webSearchPluginConfig = buildProviderBuiltinWebSearchConfig(aiSdkProviderId, webSearchConfig) + } if (!tools) { tools = {} } if (aiSdkProviderId === 'google-vertex') { tools.google_search = vertex.tools.googleSearch({}) as ProviderDefinedTool } else if (aiSdkProviderId === 'google-vertex-anthropic') { - tools.web_search = vertexAnthropic.tools.webSearch_20250305({}) as ProviderDefinedTool + tools.web_search = vertexAnthropic.tools.webSearch_20250305({ + maxUses: webSearchConfig.maxResults, + blockedDomains: mapRegexToPatterns(webSearchConfig.excludeDomains) + }) as ProviderDefinedTool } } @@ -151,7 +171,8 @@ export async function buildStreamTextParams( return { params, modelId: model.id, - capabilities: { enableReasoning, enableWebSearch, enableGenerateImage, enableUrlContext } + capabilities: { enableReasoning, enableWebSearch, enableGenerateImage, enableUrlContext }, + webSearchPluginConfig } } diff --git a/src/renderer/src/aiCore/provider/factory.ts b/src/renderer/src/aiCore/provider/factory.ts index 617758753e..bfcd3da383 100644 --- a/src/renderer/src/aiCore/provider/factory.ts +++ b/src/renderer/src/aiCore/provider/factory.ts @@ -69,6 +69,9 @@ export function getAiSdkProviderId(provider: Provider): ProviderId | 'openai-com return resolvedFromType } } + if (provider.apiHost.includes('api.openai.com')) { + return 'openai-chat' + } // 3. 最后的fallback(通常会成为openai-compatible) return provider.id as ProviderId } diff --git a/src/renderer/src/aiCore/utils/options.ts b/src/renderer/src/aiCore/utils/options.ts index f85c8c7879..dec475fc8b 100644 --- a/src/renderer/src/aiCore/utils/options.ts +++ b/src/renderer/src/aiCore/utils/options.ts @@ -82,6 +82,7 @@ export function buildProviderOptions( // 应该覆盖所有类型 switch (baseProviderId) { case 'openai': + case 'openai-chat': case 'azure': providerSpecificOptions = { ...buildOpenAIProviderOptions(assistant, model, capabilities), @@ -101,13 +102,15 @@ export function buildProviderOptions( providerSpecificOptions = buildXAIProviderOptions(assistant, model, capabilities) break case 'deepseek': - case 'openai-compatible': + case 'openrouter': + case 'openai-compatible': { // 对于其他 provider,使用通用的构建逻辑 providerSpecificOptions = { ...buildGenericProviderOptions(assistant, model, capabilities), serviceTier: serviceTierSetting } break + } default: throw new Error(`Unsupported base provider ${baseProviderId}`) } diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index 385d8183c5..bee07b1e0d 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -312,7 +312,7 @@ export function getOpenAIReasoningParams(assistant: Assistant, model: Model): Re export function getAnthropicThinkingBudget(assistant: Assistant, model: Model): number { const { maxTokens, reasoning_effort: reasoningEffort } = getAssistantSettings(assistant) - if (maxTokens === undefined || reasoningEffort === undefined) { + if (reasoningEffort === undefined) { return 0 } const effortRatio = EFFORT_RATIO[reasoningEffort] diff --git a/src/renderer/src/aiCore/utils/websearch.ts b/src/renderer/src/aiCore/utils/websearch.ts index d2d0345826..e95f3e60cf 100644 --- a/src/renderer/src/aiCore/utils/websearch.ts +++ b/src/renderer/src/aiCore/utils/websearch.ts @@ -1,6 +1,13 @@ +import { + AnthropicSearchConfig, + OpenAISearchConfig, + WebSearchPluginConfig +} from '@cherrystudio/ai-core/core/plugins/built-in/webSearchPlugin/helper' +import { BaseProviderId } from '@cherrystudio/ai-core/provider' import { isOpenAIWebSearchChatCompletionOnlyModel } from '@renderer/config/models' -import { WEB_SEARCH_PROMPT_FOR_OPENROUTER } from '@renderer/config/prompts' +import { CherryWebSearchConfig } from '@renderer/store/websearch' import { Model } from '@renderer/types' +import { mapRegexToPatterns } from '@renderer/utils/blacklistMatchPattern' export function getWebSearchParams(model: Model): Record { if (model.provider === 'hunyuan') { @@ -21,11 +28,78 @@ export function getWebSearchParams(model: Model): Record { web_search_options: {} } } - - if (model.provider === 'openrouter') { - return { - plugins: [{ id: 'web', search_prompts: WEB_SEARCH_PROMPT_FOR_OPENROUTER }] - } - } return {} } + +/** + * range in [0, 100] + * @param maxResults + */ +function mapMaxResultToOpenAIContextSize(maxResults: number): OpenAISearchConfig['searchContextSize'] { + if (maxResults <= 33) return 'low' + if (maxResults <= 66) return 'medium' + return 'high' +} + +export function buildProviderBuiltinWebSearchConfig( + providerId: BaseProviderId, + webSearchConfig: CherryWebSearchConfig +): WebSearchPluginConfig { + switch (providerId) { + case 'openai': { + return { + openai: { + searchContextSize: mapMaxResultToOpenAIContextSize(webSearchConfig.maxResults) + } + } + } + case 'openai-chat': { + return { + 'openai-chat': { + searchContextSize: mapMaxResultToOpenAIContextSize(webSearchConfig.maxResults) + } + } + } + case 'anthropic': { + const anthropicSearchOptions: AnthropicSearchConfig = { + maxUses: webSearchConfig.maxResults, + blockedDomains: mapRegexToPatterns(webSearchConfig.excludeDomains) + } + return { + anthropic: anthropicSearchOptions + } + } + case 'xai': { + return { + xai: { + maxSearchResults: webSearchConfig.maxResults, + returnCitations: true, + sources: [ + { + type: 'web', + excludedWebsites: mapRegexToPatterns(webSearchConfig.excludeDomains) + }, + { type: 'news' }, + { type: 'x' } + ], + mode: 'on' + } + } + } + case 'openrouter': { + return { + openrouter: { + plugins: [ + { + id: 'web', + max_results: webSearchConfig.maxResults + } + ] + } + } + } + default: { + throw new Error(`Unsupported provider: ${providerId}`) + } + } +} diff --git a/src/renderer/src/config/models/utils.ts b/src/renderer/src/config/models/utils.ts index 3fd27308f8..39078e2924 100644 --- a/src/renderer/src/config/models/utils.ts +++ b/src/renderer/src/config/models/utils.ts @@ -229,6 +229,11 @@ export const isGPT5SeriesModel = (model: Model) => { return modelId.includes('gpt-5') } +export const isGPT5SeriesReasoningModel = (model: Model) => { + const modelId = getLowerBaseModelName(model.id) + return modelId.includes('gpt-5') && !modelId.includes('chat') +} + export const isGeminiModel = (model: Model) => { const modelId = getLowerBaseModelName(model.id) return modelId.includes('gemini') diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index f28a88c7c7..a710605b64 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -1020,7 +1020,7 @@ export const PROVIDER_URLS: Record = { }, anthropic: { api: { - url: 'https://api.anthropic.com/' + url: 'https://api.anthropic.com' }, websites: { official: 'https://anthropic.com/', diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index 571e5877a8..e9e3b55706 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -38,7 +38,7 @@ export function useAppInit() { customCss, enableDataCollection } = useSettings() - const { isTopNavbar } = useNavbarPosition() + const { isLeftNavbar } = useNavbarPosition() const { minappShow } = useRuntime() const { setDefaultModel, setQuickModel, setTranslateModel } = useDefaultModel() const avatar = useLiveQuery(() => db.settings.get('image://avatar')) @@ -102,16 +102,15 @@ export function useAppInit() { }, [language]) useEffect(() => { - const transparentWindow = windowStyle === 'transparent' && isMac && !minappShow + const isMacTransparentWindow = windowStyle === 'transparent' && isMac - if (minappShow && isTopNavbar) { - window.root.style.background = - windowStyle === 'transparent' && isMac ? 'var(--color-background)' : 'var(--navbar-background)' + if (minappShow && isLeftNavbar) { + window.root.style.background = isMacTransparentWindow ? 'var(--color-background)' : 'var(--navbar-background)' return } - window.root.style.background = transparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)' - }, [windowStyle, minappShow, theme, isTopNavbar]) + window.root.style.background = isMacTransparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)' + }, [windowStyle, minappShow, theme, isLeftNavbar]) useEffect(() => { if (isLocalAi) { diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 57da145599..5dfa5fcda2 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -679,7 +679,12 @@ "title": "Topics", "unpin": "Unpin Topic" }, - "translate": "Translate" + "translate": "Translate", + "web_search": { + "warning": { + "openai": "The GPT-5 model's minimal reasoning effort does not support web search." + } + } }, "code": { "auto_update_to_latest": "Automatically update to latest version", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 21329ea42b..a15732fdfa 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -679,7 +679,12 @@ "title": "话题", "unpin": "取消固定" }, - "translate": "翻译" + "translate": "翻译", + "web_search": { + "warning": { + "openai": "GPT5 模型 minimal 思考强度不支持网络搜索" + } + } }, "code": { "auto_update_to_latest": "检查更新并安装最新版本", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 771c64f66c..bfffe15ef2 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -679,7 +679,12 @@ "title": "話題", "unpin": "取消固定" }, - "translate": "翻譯" + "translate": "翻譯", + "web_search": { + "warning": { + "openai": "GPT-5 模型的最小推理力度不支援網路搜尋。" + } + } }, "code": { "auto_update_to_latest": "檢查更新並安裝最新版本", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index eab721a437..d1d65cc1b3 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -381,8 +381,9 @@ "translate": "Μετάφραση στο {{target_language}}", "translating": "Μετάφραση...", "upload": { + "attachment": "Μεταφόρτωση συνημμένου", "document": "Φόρτωση έγγραφου (το μοντέλο δεν υποστηρίζει εικόνες)", - "label": "Φόρτωση εικόνας ή έγγραφου", + "image_or_document": "Μεταφόρτωση εικόνας ή εγγράφου", "upload_from_local": "Μεταφόρτωση αρχείου από τον υπολογιστή..." }, "url_context": "Περιεχόμενο ιστοσελίδας", @@ -678,7 +679,12 @@ "title": "Θέματα", "unpin": "Ξεκαρφίτσωμα" }, - "translate": "Μετάφραση" + "translate": "Μετάφραση", + "web_search": { + "warning": { + "openai": "Το μοντέλο GPT5 με ελάχιστη ένταση σκέψης δεν υποστηρίζει αναζήτηση στο διαδίκτυο" + } + } }, "code": { "auto_update_to_latest": "Έλεγχος για ενημερώσεις και εγκατάσταση της τελευταίας έκδοσης", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index f76498899c..3a9f92032f 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -381,8 +381,9 @@ "translate": "Traducir a {{target_language}}", "translating": "Traduciendo...", "upload": { + "attachment": "Subir archivo adjunto", "document": "Subir documento (el modelo no admite imágenes)", - "label": "Subir imagen o documento", + "image_or_document": "Subir imagen o documento", "upload_from_local": "Subir archivo local..." }, "url_context": "Contexto de la página web", @@ -678,7 +679,12 @@ "title": "Tema", "unpin": "Quitar fijación" }, - "translate": "Traducir" + "translate": "Traducir", + "web_search": { + "warning": { + "openai": "El modelo GPT5 con intensidad de pensamiento mínima no admite búsqueda en la web." + } + } }, "code": { "auto_update_to_latest": "Comprobar actualizaciones e instalar la versión más reciente", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 59cc1e0bac..7180059c76 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -381,8 +381,9 @@ "translate": "Traduire en {{target_language}}", "translating": "Traduction en cours...", "upload": { + "attachment": "Télécharger la pièce jointe", "document": "Télécharger un document (le modèle ne prend pas en charge les images)", - "label": "Télécharger une image ou un document", + "image_or_document": "Télécharger une image ou un document", "upload_from_local": "Télécharger un fichier local..." }, "url_context": "Contexte de la page web", @@ -678,7 +679,12 @@ "title": "Sujet", "unpin": "Annuler le fixage" }, - "translate": "Traduire" + "translate": "Traduire", + "web_search": { + "warning": { + "openai": "Le modèle GPT5 avec une intensité de réflexion minimale ne prend pas en charge la recherche sur Internet." + } + } }, "code": { "auto_update_to_latest": "Vérifier les mises à jour et installer la dernière version", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index b28177319f..204e7aa713 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -381,8 +381,9 @@ "translate": "{{target_language}}に翻訳", "translating": "翻訳中...", "upload": { + "attachment": "添付ファイルをアップロード", "document": "ドキュメントをアップロード(モデルは画像をサポートしません)", - "label": "画像またはドキュメントをアップロード", + "image_or_document": "画像またはドキュメントをアップロード", "upload_from_local": "ローカルファイルをアップロード..." }, "url_context": "URLコンテキスト", @@ -678,7 +679,12 @@ "title": "トピック", "unpin": "固定解除" }, - "translate": "翻訳" + "translate": "翻訳", + "web_search": { + "warning": { + "openai": "GPT5モデルの最小思考強度ではネット検索はサポートされません" + } + } }, "code": { "auto_update_to_latest": "最新バージョンを自動的に更新する", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 7d6f4cafbb..a188200139 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -381,8 +381,9 @@ "translate": "Traduzir para {{target_language}}", "translating": "Traduzindo...", "upload": { + "attachment": "Carregar anexo", "document": "Carregar documento (o modelo não suporta imagens)", - "label": "Carregar imagem ou documento", + "image_or_document": "Carregar imagem ou documento", "upload_from_local": "Fazer upload de arquivo local..." }, "url_context": "Contexto da Página da Web", @@ -678,7 +679,12 @@ "title": "Tópicos", "unpin": "Desfixar" }, - "translate": "Traduzir" + "translate": "Traduzir", + "web_search": { + "warning": { + "openai": "O modelo GPT5 com intensidade mínima de pensamento não suporta pesquisa na web" + } + } }, "code": { "auto_update_to_latest": "Verificar atualizações e instalar a versão mais recente", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 78285665d6..aa94c67f9a 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -381,8 +381,9 @@ "translate": "Перевести на {{target_language}}", "translating": "Перевод...", "upload": { + "attachment": "Загрузить вложение", "document": "Загрузить документ (модель не поддерживает изображения)", - "label": "Загрузить изображение или документ", + "image_or_document": "Загрузить изображение или документ", "upload_from_local": "Загрузить локальный файл..." }, "url_context": "Контекст страницы", @@ -678,7 +679,12 @@ "title": "Топики", "unpin": "Открепленные темы" }, - "translate": "Перевести" + "translate": "Перевести", + "web_search": { + "warning": { + "openai": "Модель GPT5 с минимальной интенсивностью мышления не поддерживает поиск в интернете" + } + } }, "code": { "auto_update_to_latest": "Автоматически обновлять до последней версии", diff --git a/src/renderer/src/pages/code/index.ts b/src/renderer/src/pages/code/index.ts index 4cbcd56040..f24003a1c4 100644 --- a/src/renderer/src/pages/code/index.ts +++ b/src/renderer/src/pages/code/index.ts @@ -19,7 +19,8 @@ export const CLI_TOOLS = [ { value: codeTools.claudeCode, label: 'Claude Code' }, { value: codeTools.qwenCode, label: 'Qwen Code' }, { value: codeTools.geminiCli, label: 'Gemini CLI' }, - { value: codeTools.openaiCodex, label: 'OpenAI Codex' } + { value: codeTools.openaiCodex, label: 'OpenAI Codex' }, + { value: codeTools.iFlowCli, label: 'iFlow CLI' } ] export const GEMINI_SUPPORTED_PROVIDERS = ['aihubmix', 'dmxapi', 'new-api'] @@ -35,7 +36,8 @@ export const CLI_TOOL_PROVIDER_MAP: Record Pr providers.filter((p) => p.type === 'gemini' || GEMINI_SUPPORTED_PROVIDERS.includes(p.id)), [codeTools.qwenCode]: (providers) => providers.filter((p) => p.type.includes('openai')), [codeTools.openaiCodex]: (providers) => - providers.filter((p) => p.id === 'openai' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id)) + providers.filter((p) => p.id === 'openai' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id)), + [codeTools.iFlowCli]: (providers) => providers.filter((p) => p.type.includes('openai')) } export const getCodeToolsApiBaseUrl = (model: Model, type: EndpointType) => { @@ -144,6 +146,12 @@ export const generateToolEnvironment = ({ env.OPENAI_MODEL = model.id env.OPENAI_MODEL_PROVIDER = modelProvider.id break + + case codeTools.iFlowCli: + env.IFLOW_API_KEY = apiKey + env.IFLOW_BASE_URL = baseUrl + env.IFLOW_MODEL_NAME = model.id + break } return env diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index 4ea6bcdd57..d6d7f08d90 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -155,23 +155,23 @@ const Chat: FC = (props) => { flex={1} justify="space-between" style={{ maxWidth: chatMaxWidth, height: mainHeight }}> - - } - filter={contentSearchFilter} - includeUser={filterIncludeUser} - onIncludeUserChange={userOutlinedItemClickHandler} - /> - {messageNavigation === 'buttons' && } + + } + filter={contentSearchFilter} + includeUser={filterIncludeUser} + onIncludeUserChange={userOutlinedItemClickHandler} + /> + {messageNavigation === 'buttons' && } {isMultiSelectMode && } diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 864ea71a21..9a130e45ff 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -71,6 +71,7 @@ interface Props { let _text = '' let _files: FileType[] = [] +let _mentionedModelsCache: Model[] = [] const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) => { const [text, setText] = useState(_text) @@ -103,7 +104,8 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const spaceClickTimer = useRef(null) const [isTranslating, setIsTranslating] = useState(false) const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState([]) - const [mentionedModels, setMentionedModels] = useState([]) + const [mentionedModels, setMentionedModels] = useState(_mentionedModelsCache) + const mentionedModelsRef = useRef(mentionedModels) const [isDragging, setIsDragging] = useState(false) const [isFileDragging, setIsFileDragging] = useState(false) const [textareaHeight, setTextareaHeight] = useState() @@ -114,6 +116,10 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const isGenerateImageAssistant = useMemo(() => isGenerateImageModel(model), [model]) const { setTimeoutTimer } = useTimer() + useEffect(() => { + mentionedModelsRef.current = mentionedModels + }, [mentionedModels]) + const isVisionSupported = useMemo( () => (mentionedModels.length > 0 && isVisionModels(mentionedModels)) || @@ -179,6 +185,13 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = _text = text _files = files + useEffect(() => { + // 利用useEffect清理函数在卸载组件时更新状态缓存 + return () => { + _mentionedModelsCache = mentionedModelsRef.current + } + }, []) + const focusTextarea = useCallback(() => { textareaRef.current?.focus() }, []) diff --git a/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx b/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx index 5ee0c628c8..7e2b7edd38 100644 --- a/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx @@ -8,7 +8,13 @@ import { MdiLightbulbOn80 } from '@renderer/components/Icons/SVGIcon' import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel' -import { getThinkModelType, isDoubaoThinkingAutoModel, MODEL_SUPPORTED_OPTIONS } from '@renderer/config/models' +import { + getThinkModelType, + isDoubaoThinkingAutoModel, + isGPT5SeriesReasoningModel, + isOpenAIWebSearchModel, + MODEL_SUPPORTED_OPTIONS +} from '@renderer/config/models' import { useAssistant } from '@renderer/hooks/useAssistant' import { getReasoningEffortOptionsLabel } from '@renderer/i18n/label' import { Model, ThinkingOption } from '@renderer/types' @@ -61,6 +67,15 @@ const ThinkingButton: FC = ({ ref, model, assistantId }): ReactElement => }) return } + if ( + isOpenAIWebSearchModel(model) && + isGPT5SeriesReasoningModel(model) && + assistant.enableWebSearch && + option === 'minimal' + ) { + window.toast.warning(t('chat.web_search.warning.openai')) + return + } updateAssistantSettings({ reasoning_effort: option, reasoning_effort_cache: option, @@ -68,7 +83,7 @@ const ThinkingButton: FC = ({ ref, model, assistantId }): ReactElement => }) return }, - [updateAssistantSettings] + [updateAssistantSettings, assistant.enableWebSearch, model, t] ) const panelItems = useMemo(() => { diff --git a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx index cb3c5fb6d9..807781bac4 100644 --- a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx @@ -3,7 +3,12 @@ import { loggerService } from '@logger' import { ActionIconButton } from '@renderer/components/Buttons' import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo, ZhipuLogo } from '@renderer/components/Icons' import { QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel' -import { isGeminiModel, isWebSearchModel } from '@renderer/config/models' +import { + isGeminiModel, + isGPT5SeriesReasoningModel, + isOpenAIWebSearchModel, + isWebSearchModel +} from '@renderer/config/models' import { isGeminiWebSearchProvider } from '@renderer/config/providers' import { useAssistant } from '@renderer/hooks/useAssistant' import { useTimer } from '@renderer/hooks/useTimer' @@ -115,6 +120,15 @@ const WebSearchButton: FC = ({ ref, assistantId }) => { update.enableWebSearch = false window.toast.warning(t('chat.mcp.warning.gemini_web_search')) } + if ( + isOpenAIWebSearchModel(model) && + isGPT5SeriesReasoningModel(model) && + update.enableWebSearch && + assistant.settings?.reasoning_effort === 'minimal' + ) { + update.enableWebSearch = false + window.toast.warning(t('chat.web_search.warning.openai')) + } setTimeoutTimer('updateSelectedWebSearchBuiltin', () => updateAssistant(update), 200) }, [assistant, setTimeoutTimer, t, updateAssistant]) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 0bd7b152f4..68bfc67ba2 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -16,7 +16,13 @@ import { useAppDispatch } from '@renderer/store' import { updateWebSearchProvider } from '@renderer/store/websearch' import { isSystemProvider } from '@renderer/types' import { ApiKeyConnectivity, HealthStatus } from '@renderer/types/healthCheck' -import { formatApiHost, formatApiKeys, getFancyProviderName, isOpenAIProvider } from '@renderer/utils' +import { + formatApiHost, + formatApiKeys, + getFancyProviderName, + isAnthropicProvider, + isOpenAIProvider +} from '@renderer/utils' import { formatErrorMessage } from '@renderer/utils/error' import { Button, Divider, Flex, Input, Select, Space, Switch, Tooltip } from 'antd' import Link from 'antd/es/typography/Link' @@ -212,6 +218,10 @@ const ProviderSetting: FC = ({ providerId }) => { if (provider.type === 'azure-openai') { return formatApiHost(apiHost) + 'openai/v1' } + + if (provider.type === 'anthropic') { + return formatApiHost(apiHost) + 'messages' + } return formatApiHost(apiHost) + 'responses' } @@ -361,7 +371,7 @@ const ProviderSetting: FC = ({ providerId }) => { )} - {isOpenAIProvider(provider) && ( + {(isOpenAIProvider(provider) || isAnthropicProvider(provider)) && ( diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index 01807027ba..d954fc1f85 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -117,7 +117,8 @@ export async function fetchChatCompletion({ const { params: aiSdkParams, modelId, - capabilities + capabilities, + webSearchPluginConfig } = await buildStreamTextParams(messages, assistant, provider, { mcpTools: mcpTools, webSearchProviderId: assistant.webSearchProviderId, @@ -132,6 +133,7 @@ export async function fetchChatCompletion({ isPromptToolUse: isPromptToolUse(assistant), isSupportedToolUse: isSupportedToolUse(assistant), isImageGenerationEndpoint: isDedicatedImageGenerationModel(assistant.model || getDefaultModel()), + webSearchPluginConfig: webSearchPluginConfig, enableWebSearch: capabilities.enableWebSearch, enableGenerateImage: capabilities.enableGenerateImage, enableUrlContext: capabilities.enableUrlContext, diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index dc599d0658..3b3cfa3f0c 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -67,7 +67,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 155, + version: 156, blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index f58059337b..f9b187606c 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -2485,6 +2485,21 @@ const migrateConfig = { logger.error('migrate 155 error', error as Error) return state } + }, + '156': (state: RootState) => { + try { + state.llm.providers.forEach((provider) => { + if (provider.id === SystemProviderIds.anthropic) { + if (provider.apiHost.endsWith('/')) { + provider.apiHost = provider.apiHost.slice(0, -1) + } + } + }) + return state + } catch (error) { + logger.error('migrate 156 error', error as Error) + return state + } } } diff --git a/src/renderer/src/store/websearch.ts b/src/renderer/src/store/websearch.ts index 1e3fe2a25b..16029ccf47 100644 --- a/src/renderer/src/store/websearch.ts +++ b/src/renderer/src/store/websearch.ts @@ -41,6 +41,8 @@ export interface WebSearchState { providerConfig: Record } +export type CherryWebSearchConfig = Pick + export const initialState: WebSearchState = { defaultProvider: 'local-bing', providers: WEB_SEARCH_PROVIDERS, diff --git a/src/renderer/src/utils/__tests__/blacklistMatchPattern.test.ts b/src/renderer/src/utils/__tests__/blacklistMatchPattern.test.ts index 4656e30562..a5c0983479 100644 --- a/src/renderer/src/utils/__tests__/blacklistMatchPattern.test.ts +++ b/src/renderer/src/utils/__tests__/blacklistMatchPattern.test.ts @@ -26,7 +26,7 @@ import { describe, expect, it } from 'vitest' -import { MatchPatternMap } from '../blacklistMatchPattern' +import { mapRegexToPatterns, MatchPatternMap } from '../blacklistMatchPattern' function get(map: MatchPatternMap, url: string) { return map.get(url).sort() @@ -161,3 +161,28 @@ describe('blacklistMatchPattern', () => { expect(get(map, 'https://b.mozilla.org/path/')).toEqual([0, 1, 2, 6]) }) }) + +describe('mapRegexToPatterns', () => { + it('extracts domains from regex patterns', () => { + const result = mapRegexToPatterns([ + '/example\\.com/', + '/(?:www\\.)?sub\\.example\\.co\\.uk/', + '/api\\.service\\.io/', + 'https://baidu.com' + ]) + + expect(result).toEqual(['example.com', 'sub.example.co.uk', 'api.service.io', 'baidu.com']) + }) + + it('deduplicates domains across multiple patterns', () => { + const result = mapRegexToPatterns(['/example\\.com/', '/(example\\.com|test\\.org)/']) + + expect(result).toEqual(['example.com', 'test.org']) + }) + + it('ignores patterns without domain matches', () => { + const result = mapRegexToPatterns(['', 'plain-domain.com', '/^https?:\\/\\/[^/]+$/']) + + expect(result).toEqual(['plain-domain.com']) + }) +}) diff --git a/src/renderer/src/utils/blacklistMatchPattern.ts b/src/renderer/src/utils/blacklistMatchPattern.ts index 9e78ab58fb..b00e07a785 100644 --- a/src/renderer/src/utils/blacklistMatchPattern.ts +++ b/src/renderer/src/utils/blacklistMatchPattern.ts @@ -202,6 +202,43 @@ export async function parseSubscribeContent(url: string): Promise { throw error } } + +export function mapRegexToPatterns(patterns: string[]): string[] { + const patternSet = new Set() + const domainMatcher = /[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g + + patterns.forEach((pattern) => { + if (!pattern) { + return + } + + // Handle regex patterns (wrapped in /) + if (pattern.startsWith('/') && pattern.endsWith('/')) { + const rawPattern = pattern.slice(1, -1) + const normalizedPattern = rawPattern.replace(/\\\./g, '.').replace(/\\\//g, '/') + const matches = normalizedPattern.match(domainMatcher) + + if (matches) { + matches.forEach((match) => { + patternSet.add(match.replace(/http(s)?:\/\//g, '').toLowerCase()) + }) + } + } else if (pattern.includes('://')) { + // Handle URLs with protocol (e.g., https://baidu.com) + const matches = pattern.match(domainMatcher) + if (matches) { + matches.forEach((match) => { + patternSet.add(match.replace(/http(s)?:\/\//g, '').toLowerCase()) + }) + } + } else { + patternSet.add(pattern.toLowerCase()) + } + }) + + return Array.from(patternSet) +} + export async function filterResultWithBlacklist( response: WebSearchProviderResponse, websearch: WebSearchState diff --git a/src/renderer/src/utils/index.ts b/src/renderer/src/utils/index.ts index 828d288ba8..64f943946a 100644 --- a/src/renderer/src/utils/index.ts +++ b/src/renderer/src/utils/index.ts @@ -205,6 +205,10 @@ export function isOpenAIProvider(provider: Provider): boolean { return !['anthropic', 'gemini', 'vertexai'].includes(provider.type) } +export function isAnthropicProvider(provider: Provider): boolean { + return provider.type === 'anthropic' +} + /** * 判断模型是否为用户手动选择 * @param {Model} model 模型对象 diff --git a/src/renderer/src/windows/mini/MiniWindowApp.tsx b/src/renderer/src/windows/mini/MiniWindowApp.tsx index b459a3e027..0f6d8bc60d 100644 --- a/src/renderer/src/windows/mini/MiniWindowApp.tsx +++ b/src/renderer/src/windows/mini/MiniWindowApp.tsx @@ -1,9 +1,9 @@ import '@renderer/databases' -import { HeroUIProvider } from '@heroui/react' import { ErrorBoundary } from '@renderer/components/ErrorBoundary' import { ToastPortal } from '@renderer/components/ToastPortal' import { getToastUtilities } from '@renderer/components/TopView/toast' +import { HeroUIProvider } from '@renderer/context/HeroUIProvider' import { useSettings } from '@renderer/hooks/useSettings' import store, { persistor } from '@renderer/store' import { useEffect } from 'react' diff --git a/src/renderer/src/windows/selection/action/entryPoint.tsx b/src/renderer/src/windows/selection/action/entryPoint.tsx index 44f24ccb97..5aa8d3f745 100644 --- a/src/renderer/src/windows/selection/action/entryPoint.tsx +++ b/src/renderer/src/windows/selection/action/entryPoint.tsx @@ -2,13 +2,13 @@ import '@renderer/assets/styles/index.css' import '@renderer/assets/styles/tailwind.css' import '@ant-design/v5-patch-for-react-19' -import { HeroUIProvider } from '@heroui/react' import KeyvStorage from '@kangfenmao/keyv-storage' import { loggerService } from '@logger' import { ToastPortal } from '@renderer/components/ToastPortal' import { getToastUtilities } from '@renderer/components/TopView/toast' import AntdProvider from '@renderer/context/AntdProvider' import { CodeStyleProvider } from '@renderer/context/CodeStyleProvider' +import { HeroUIProvider } from '@renderer/context/HeroUIProvider' import { ThemeProvider } from '@renderer/context/ThemeProvider' import storeSyncService from '@renderer/services/StoreSyncService' import store, { persistor } from '@renderer/store' diff --git a/yarn.lock b/yarn.lock index a012104817..9eeca22a16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -386,8 +386,8 @@ __metadata: linkType: hard "@anthropic-ai/claude-code@npm:^1.0.113": - version: 1.0.113 - resolution: "@anthropic-ai/claude-code@npm:1.0.113" + version: 1.0.118 + resolution: "@anthropic-ai/claude-code@npm:1.0.118" dependencies: "@img/sharp-darwin-arm64": "npm:^0.33.5" "@img/sharp-darwin-x64": "npm:^0.33.5" @@ -410,7 +410,7 @@ __metadata: optional: true bin: claude: cli.js - checksum: 10c0/954303e43916a221b0ea9185844621957ac0d42835be654a0f66f09e01a06ef0d121e98bf156c8a51a30d65ca75d2002e5ece561a960cfcc618467a79fb21cba + checksum: 10c0/90e9feac857bfcbdaa3797c70cccfad8588ed45bc9ea6c88664e2f08fa71d069cfb28ce17c20a67ddd841d9a8c7a50e61281687975f7d5ebde9b39f63551cefb languageName: node linkType: hard @@ -2338,7 +2338,7 @@ __metadata: languageName: node linkType: hard -"@cherrystudio/ai-core@workspace:^1.0.0-alpha.16, @cherrystudio/ai-core@workspace:packages/aiCore": +"@cherrystudio/ai-core@workspace:^1.0.0-alpha.18, @cherrystudio/ai-core@workspace:packages/aiCore": version: 0.0.0-use.local resolution: "@cherrystudio/ai-core@workspace:packages/aiCore" dependencies: @@ -3449,16 +3449,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/aix-ppc64@npm:0.25.8" +"@esbuild/aix-ppc64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/aix-ppc64@npm:0.25.10" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/aix-ppc64@npm:0.25.9" +"@esbuild/aix-ppc64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/aix-ppc64@npm:0.25.8" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -3470,16 +3470,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/android-arm64@npm:0.25.8" +"@esbuild/android-arm64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/android-arm64@npm:0.25.10" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/android-arm64@npm:0.25.9" +"@esbuild/android-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-arm64@npm:0.25.8" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -3491,16 +3491,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/android-arm@npm:0.25.8" +"@esbuild/android-arm@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/android-arm@npm:0.25.10" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/android-arm@npm:0.25.9" +"@esbuild/android-arm@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-arm@npm:0.25.8" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -3512,16 +3512,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/android-x64@npm:0.25.8" +"@esbuild/android-x64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/android-x64@npm:0.25.10" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/android-x64@npm:0.25.9" +"@esbuild/android-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-x64@npm:0.25.8" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -3533,16 +3533,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/darwin-arm64@npm:0.25.8" +"@esbuild/darwin-arm64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/darwin-arm64@npm:0.25.10" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/darwin-arm64@npm:0.25.9" +"@esbuild/darwin-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/darwin-arm64@npm:0.25.8" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -3554,16 +3554,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/darwin-x64@npm:0.25.8" +"@esbuild/darwin-x64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/darwin-x64@npm:0.25.10" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/darwin-x64@npm:0.25.9" +"@esbuild/darwin-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/darwin-x64@npm:0.25.8" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -3575,16 +3575,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/freebsd-arm64@npm:0.25.8" +"@esbuild/freebsd-arm64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/freebsd-arm64@npm:0.25.10" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/freebsd-arm64@npm:0.25.9" +"@esbuild/freebsd-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/freebsd-arm64@npm:0.25.8" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -3596,16 +3596,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/freebsd-x64@npm:0.25.8" +"@esbuild/freebsd-x64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/freebsd-x64@npm:0.25.10" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/freebsd-x64@npm:0.25.9" +"@esbuild/freebsd-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/freebsd-x64@npm:0.25.8" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -3617,16 +3617,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-arm64@npm:0.25.8" +"@esbuild/linux-arm64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-arm64@npm:0.25.10" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-arm64@npm:0.25.9" +"@esbuild/linux-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-arm64@npm:0.25.8" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -3638,16 +3638,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-arm@npm:0.25.8" +"@esbuild/linux-arm@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-arm@npm:0.25.10" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-arm@npm:0.25.9" +"@esbuild/linux-arm@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-arm@npm:0.25.8" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -3659,16 +3659,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-ia32@npm:0.25.8" +"@esbuild/linux-ia32@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-ia32@npm:0.25.10" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-ia32@npm:0.25.9" +"@esbuild/linux-ia32@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-ia32@npm:0.25.8" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -3680,16 +3680,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-loong64@npm:0.25.8" +"@esbuild/linux-loong64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-loong64@npm:0.25.10" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-loong64@npm:0.25.9" +"@esbuild/linux-loong64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-loong64@npm:0.25.8" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -3701,16 +3701,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-mips64el@npm:0.25.8" +"@esbuild/linux-mips64el@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-mips64el@npm:0.25.10" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-mips64el@npm:0.25.9" +"@esbuild/linux-mips64el@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-mips64el@npm:0.25.8" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -3722,16 +3722,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-ppc64@npm:0.25.8" +"@esbuild/linux-ppc64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-ppc64@npm:0.25.10" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-ppc64@npm:0.25.9" +"@esbuild/linux-ppc64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-ppc64@npm:0.25.8" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -3743,16 +3743,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-riscv64@npm:0.25.8" +"@esbuild/linux-riscv64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-riscv64@npm:0.25.10" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-riscv64@npm:0.25.9" +"@esbuild/linux-riscv64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-riscv64@npm:0.25.8" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -3764,16 +3764,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-s390x@npm:0.25.8" +"@esbuild/linux-s390x@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-s390x@npm:0.25.10" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-s390x@npm:0.25.9" +"@esbuild/linux-s390x@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-s390x@npm:0.25.8" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -3785,6 +3785,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/linux-x64@npm:0.25.10" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-x64@npm:0.25.8" @@ -3792,10 +3799,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-x64@npm:0.25.9" - conditions: os=linux & cpu=x64 +"@esbuild/netbsd-arm64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/netbsd-arm64@npm:0.25.10" + conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard @@ -3806,13 +3813,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/netbsd-arm64@npm:0.25.9" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/netbsd-x64@npm:0.18.20" @@ -3820,6 +3820,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/netbsd-x64@npm:0.25.10" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/netbsd-x64@npm:0.25.8" @@ -3827,10 +3834,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/netbsd-x64@npm:0.25.9" - conditions: os=netbsd & cpu=x64 +"@esbuild/openbsd-arm64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/openbsd-arm64@npm:0.25.10" + conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard @@ -3841,13 +3848,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/openbsd-arm64@npm:0.25.9" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/openbsd-x64@npm:0.18.20" @@ -3855,6 +3855,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/openbsd-x64@npm:0.25.10" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/openbsd-x64@npm:0.25.8" @@ -3862,10 +3869,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/openbsd-x64@npm:0.25.9" - conditions: os=openbsd & cpu=x64 +"@esbuild/openharmony-arm64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/openharmony-arm64@npm:0.25.10" + conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard @@ -3876,13 +3883,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/openharmony-arm64@npm:0.25.9" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/sunos-x64@npm:0.18.20" @@ -3890,16 +3890,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/sunos-x64@npm:0.25.8" +"@esbuild/sunos-x64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/sunos-x64@npm:0.25.10" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/sunos-x64@npm:0.25.9" +"@esbuild/sunos-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/sunos-x64@npm:0.25.8" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -3911,16 +3911,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/win32-arm64@npm:0.25.8" +"@esbuild/win32-arm64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/win32-arm64@npm:0.25.10" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/win32-arm64@npm:0.25.9" +"@esbuild/win32-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-arm64@npm:0.25.8" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -3932,16 +3932,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/win32-ia32@npm:0.25.8" +"@esbuild/win32-ia32@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/win32-ia32@npm:0.25.10" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/win32-ia32@npm:0.25.9" +"@esbuild/win32-ia32@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-ia32@npm:0.25.8" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -3953,16 +3953,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/win32-x64@npm:0.25.8" +"@esbuild/win32-x64@npm:0.25.10": + version: 0.25.10 + resolution: "@esbuild/win32-x64@npm:0.25.10" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/win32-x64@npm:0.25.9" +"@esbuild/win32-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-x64@npm:0.25.8" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -14169,7 +14169,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:^1.0.0-alpha.16" + "@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18" "@cherrystudio/embedjs": "npm:^0.1.31" "@cherrystudio/embedjs-libsql": "npm:^0.1.31" "@cherrystudio/embedjs-loader-csv": "npm:^0.1.31" @@ -14304,7 +14304,6 @@ __metadata: diff: "npm:^8.0.2" docx: "npm:^9.0.2" dompurify: "npm:^3.2.6" - dotenv: "npm:^17.2.2" dotenv-cli: "npm:^7.4.2" drizzle-kit: "npm:^0.31.4" drizzle-orm: "npm:^0.44.5" @@ -14417,7 +14416,7 @@ __metadata: turndown: "npm:7.2.0" turndown-plugin-gfm: "npm:^1.0.2" tw-animate-css: "npm:^1.3.8" - typescript: "npm:^5.6.2" + typescript: "npm:~5.8.2" undici: "npm:6.21.2" unified: "npm:^11.0.5" uuid: "npm:^13.0.0" @@ -17656,13 +17655,6 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^17.2.2": - version: 17.2.2 - resolution: "dotenv@npm:17.2.2" - checksum: 10c0/be66513504590aff6eccb14167625aed9bd42ce80547f4fe5d195860211971a7060949b57108dfaeaf90658f79e40edccd3f233f0a978bff507b5b1565ae162b - languageName: node - linkType: hard - "drizzle-kit@npm:^0.31.4": version: 0.31.4 resolution: "drizzle-kit@npm:0.31.4" @@ -18193,35 +18185,35 @@ __metadata: linkType: hard "esbuild@npm:^0.25.4": - version: 0.25.9 - resolution: "esbuild@npm:0.25.9" + version: 0.25.10 + resolution: "esbuild@npm:0.25.10" dependencies: - "@esbuild/aix-ppc64": "npm:0.25.9" - "@esbuild/android-arm": "npm:0.25.9" - "@esbuild/android-arm64": "npm:0.25.9" - "@esbuild/android-x64": "npm:0.25.9" - "@esbuild/darwin-arm64": "npm:0.25.9" - "@esbuild/darwin-x64": "npm:0.25.9" - "@esbuild/freebsd-arm64": "npm:0.25.9" - "@esbuild/freebsd-x64": "npm:0.25.9" - "@esbuild/linux-arm": "npm:0.25.9" - "@esbuild/linux-arm64": "npm:0.25.9" - "@esbuild/linux-ia32": "npm:0.25.9" - "@esbuild/linux-loong64": "npm:0.25.9" - "@esbuild/linux-mips64el": "npm:0.25.9" - "@esbuild/linux-ppc64": "npm:0.25.9" - "@esbuild/linux-riscv64": "npm:0.25.9" - "@esbuild/linux-s390x": "npm:0.25.9" - "@esbuild/linux-x64": "npm:0.25.9" - "@esbuild/netbsd-arm64": "npm:0.25.9" - "@esbuild/netbsd-x64": "npm:0.25.9" - "@esbuild/openbsd-arm64": "npm:0.25.9" - "@esbuild/openbsd-x64": "npm:0.25.9" - "@esbuild/openharmony-arm64": "npm:0.25.9" - "@esbuild/sunos-x64": "npm:0.25.9" - "@esbuild/win32-arm64": "npm:0.25.9" - "@esbuild/win32-ia32": "npm:0.25.9" - "@esbuild/win32-x64": "npm:0.25.9" + "@esbuild/aix-ppc64": "npm:0.25.10" + "@esbuild/android-arm": "npm:0.25.10" + "@esbuild/android-arm64": "npm:0.25.10" + "@esbuild/android-x64": "npm:0.25.10" + "@esbuild/darwin-arm64": "npm:0.25.10" + "@esbuild/darwin-x64": "npm:0.25.10" + "@esbuild/freebsd-arm64": "npm:0.25.10" + "@esbuild/freebsd-x64": "npm:0.25.10" + "@esbuild/linux-arm": "npm:0.25.10" + "@esbuild/linux-arm64": "npm:0.25.10" + "@esbuild/linux-ia32": "npm:0.25.10" + "@esbuild/linux-loong64": "npm:0.25.10" + "@esbuild/linux-mips64el": "npm:0.25.10" + "@esbuild/linux-ppc64": "npm:0.25.10" + "@esbuild/linux-riscv64": "npm:0.25.10" + "@esbuild/linux-s390x": "npm:0.25.10" + "@esbuild/linux-x64": "npm:0.25.10" + "@esbuild/netbsd-arm64": "npm:0.25.10" + "@esbuild/netbsd-x64": "npm:0.25.10" + "@esbuild/openbsd-arm64": "npm:0.25.10" + "@esbuild/openbsd-x64": "npm:0.25.10" + "@esbuild/openharmony-arm64": "npm:0.25.10" + "@esbuild/sunos-x64": "npm:0.25.10" + "@esbuild/win32-arm64": "npm:0.25.10" + "@esbuild/win32-ia32": "npm:0.25.10" + "@esbuild/win32-x64": "npm:0.25.10" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -18277,7 +18269,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/aaa1284c75fcf45c82f9a1a117fe8dc5c45628e3386bda7d64916ae27730910b51c5aec7dd45a6ba19256be30ba2935e64a8f011a3f0539833071e06bf76d5b3 + checksum: 10c0/8ee5fdd43ed0d4092ce7f41577c63147f54049d5617763f0549c638bbe939e8adaa8f1a2728adb63417eb11df51956b7b0d8eb88ee08c27ad1d42960256158fa languageName: node linkType: hard @@ -29191,7 +29183,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.4.3, typescript@npm:^5.6.2": +"typescript@npm:^5.4.3, typescript@npm:~5.8.2": version: 5.8.3 resolution: "typescript@npm:5.8.3" bin: @@ -29211,7 +29203,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.4.3#optional!builtin, typescript@patch:typescript@npm%3A^5.6.2#optional!builtin": +"typescript@patch:typescript@npm%3A^5.4.3#optional!builtin, typescript@patch:typescript@npm%3A~5.8.2#optional!builtin": version: 5.8.3 resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" bin: