diff --git a/.github/workflows/claude-translator.yml b/.github/workflows/claude-translator.yml index c474afeb8e..356e10d0f1 100644 --- a/.github/workflows/claude-translator.yml +++ b/.github/workflows/claude-translator.yml @@ -16,10 +16,13 @@ on: jobs: translate: if: | - (github.event_name == 'issues') || - (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') + (github.event_name == 'issues') + || (github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') + || ( + (github.event_name == 'pull_request_review' || github.event_name == 'pull_request_review_comment') + && github.event.sender.type != 'Bot' + && github.event.pull_request.head.repo.fork == false + ) runs-on: ubuntu-latest permissions: contents: read diff --git a/.yarn/patches/@ai-sdk-google-npm-2.0.14-376d8b03cc.patch b/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch similarity index 68% rename from .yarn/patches/@ai-sdk-google-npm-2.0.14-376d8b03cc.patch rename to .yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch index f8868aa916..34babfe803 100644 --- a/.yarn/patches/@ai-sdk-google-npm-2.0.14-376d8b03cc.patch +++ b/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch @@ -1,8 +1,8 @@ diff --git a/dist/index.mjs b/dist/index.mjs -index 110f37ec18c98b1d55ae2b73cc716194e6f9094d..17e109b7778cbebb904f1919e768d21a2833d965 100644 +index 69ab1599c76801dc1167551b6fa283dded123466..f0af43bba7ad1196fe05338817e65b4ebda40955 100644 --- a/dist/index.mjs +++ b/dist/index.mjs -@@ -448,7 +448,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) { +@@ -477,7 +477,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) { // src/get-model-path.ts function getModelPath(modelId) { diff --git a/eslint.config.mjs b/eslint.config.mjs index 0bcc6b57d1..1c4779fb99 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,6 +2,7 @@ import tseslint from '@electron-toolkit/eslint-config-ts' import eslint from '@eslint/js' import eslintReact from '@eslint-react/eslint-plugin' import { defineConfig } from 'eslint/config' +import importZod from 'eslint-plugin-import-zod' import oxlint from 'eslint-plugin-oxlint' import reactHooks from 'eslint-plugin-react-hooks' import simpleImportSort from 'eslint-plugin-simple-import-sort' @@ -15,7 +16,8 @@ export default defineConfig([ { plugins: { 'simple-import-sort': simpleImportSort, - 'unused-imports': unusedImports + 'unused-imports': unusedImports, + 'import-zod': importZod }, rules: { '@typescript-eslint/explicit-function-return-type': 'off', @@ -25,6 +27,7 @@ export default defineConfig([ 'simple-import-sort/exports': 'error', 'unused-imports/no-unused-imports': 'error', '@eslint-react/no-prop-types': 'error', + 'import-zod/prefer-zod-namespace': 'error' } }, // Configuration for ensuring compatibility with the original ESLint(8.x) rules diff --git a/package.json b/package.json index eaa7c25c1c..dc8d1ccf31 100644 --- a/package.json +++ b/package.json @@ -101,10 +101,10 @@ "@agentic/exa": "^7.3.3", "@agentic/searxng": "^7.3.3", "@agentic/tavily": "^7.3.3", - "@ai-sdk/amazon-bedrock": "^3.0.29", - "@ai-sdk/google-vertex": "^3.0.33", - "@ai-sdk/mistral": "^2.0.17", - "@ai-sdk/perplexity": "^2.0.11", + "@ai-sdk/amazon-bedrock": "^3.0.35", + "@ai-sdk/google-vertex": "^3.0.40", + "@ai-sdk/mistral": "^2.0.19", + "@ai-sdk/perplexity": "^2.0.13", "@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", @@ -154,6 +154,7 @@ "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", + "@opeoginni/github-copilot-openai-compatible": "0.1.18", "@playwright/test": "^1.52.0", "@radix-ui/react-context-menu": "^2.2.16", "@reduxjs/toolkit": "^2.2.5", @@ -221,7 +222,7 @@ "@viz-js/lang-dot": "^1.0.5", "@viz-js/viz": "^3.14.0", "@xyflow/react": "^12.4.4", - "ai": "^5.0.59", + "ai": "^5.0.68", "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", @@ -258,6 +259,7 @@ "emoji-picker-element": "^1.22.1", "epub": "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch", "eslint": "^9.22.0", + "eslint-plugin-import-zod": "^1.2.0", "eslint-plugin-oxlint": "^1.15.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-simple-import-sort": "^12.1.1", @@ -296,7 +298,7 @@ "notion-helper": "^1.3.22", "npx-scope-finder": "^1.2.0", "openai": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch", - "oxlint": "^1.15.0", + "oxlint": "^1.22.0", "oxlint-tsgolint": "^0.2.0", "p-queue": "^8.1.0", "pdf-lib": "^1.17.1", @@ -372,6 +374,7 @@ "app-builder-lib@npm:26.0.13": "patch:app-builder-lib@npm%3A26.0.13#~/.yarn/patches/app-builder-lib-npm-26.0.13-a064c9e1d0.patch", "app-builder-lib@npm:26.0.15": "patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch", "atomically@npm:^1.7.0": "patch:atomically@npm%3A1.7.0#~/.yarn/patches/atomically-npm-1.7.0-e742e5293b.patch", + "esbuild": "^0.25.0", "file-stream-rotator@npm:^0.6.1": "patch:file-stream-rotator@npm%3A0.6.1#~/.yarn/patches/file-stream-rotator-npm-0.6.1-eab45fb13d.patch", "libsql@npm:^0.4.4": "patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch", "node-abi": "4.12.0", @@ -379,10 +382,11 @@ "openai@npm:^4.87.3": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch", "pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch", "pkce-challenge@npm:^4.1.0": "patch:pkce-challenge@npm%3A4.1.0#~/.yarn/patches/pkce-challenge-npm-4.1.0-fbc51695a3.patch", + "tar-fs": "^2.1.4", "undici": "6.21.2", "vite": "npm:rolldown-vite@latest", "tesseract.js@npm:*": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch", - "@ai-sdk/google@npm:2.0.14": "patch:@ai-sdk/google@npm%3A2.0.14#~/.yarn/patches/@ai-sdk-google-npm-2.0.14-376d8b03cc.patch" + "@ai-sdk/google@npm:2.0.20": "patch:@ai-sdk/google@npm%3A2.0.20#~/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch" }, "packageManager": "yarn@4.9.1", "lint-staged": { diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index 7210dcebb9..eb9d000929 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -36,14 +36,14 @@ "ai": "^5.0.26" }, "dependencies": { - "@ai-sdk/anthropic": "^2.0.22", - "@ai-sdk/azure": "^2.0.42", - "@ai-sdk/deepseek": "^1.0.20", - "@ai-sdk/openai": "^2.0.42", - "@ai-sdk/openai-compatible": "^1.0.19", + "@ai-sdk/anthropic": "^2.0.27", + "@ai-sdk/azure": "^2.0.49", + "@ai-sdk/deepseek": "^1.0.23", + "@ai-sdk/openai": "^2.0.48", + "@ai-sdk/openai-compatible": "^1.0.22", "@ai-sdk/provider": "^2.0.0", - "@ai-sdk/provider-utils": "^3.0.10", - "@ai-sdk/xai": "^2.0.23", + "@ai-sdk/provider-utils": "^3.0.12", + "@ai-sdk/xai": "^2.0.26", "zod": "^4.1.5" }, "devDependencies": { 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 e973fc758b..5ccf1f8087 100644 --- a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts +++ b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/helper.ts @@ -1,7 +1,7 @@ import type { anthropic } from '@ai-sdk/anthropic' import type { google } from '@ai-sdk/google' import type { openai } from '@ai-sdk/openai' -import type { InferToolInput, InferToolOutput } from 'ai' +import type { InferToolInput, InferToolOutput, Tool } from 'ai' import type { ProviderOptionsMap } from '../../../options/types' import type { OpenRouterSearchConfig } from './openrouter' @@ -15,6 +15,13 @@ export type AnthropicSearchConfig = NonNullable[0]> export type XAISearchConfig = NonNullable +type NormalizeTool = T extends Tool ? Tool : Tool + +type AnthropicWebSearchTool = NormalizeTool> +type OpenAIWebSearchTool = NormalizeTool> +type OpenAIChatWebSearchTool = NormalizeTool> +type GoogleWebSearchTool = NormalizeTool> + /** * 插件初始化时接收的完整配置对象 * @@ -59,7 +66,7 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = { export type WebSearchToolOutputSchema = { // Anthropic 工具 - 手动定义 - anthropic: InferToolOutput> + anthropic: InferToolOutput // OpenAI 工具 - 基于实际输出 // TODO: 上游定义不规范,是unknown @@ -82,8 +89,8 @@ export type WebSearchToolOutputSchema = { } export type WebSearchToolInputSchema = { - anthropic: InferToolInput> - openai: InferToolInput> - google: InferToolInput> - 'openai-chat': InferToolInput> + anthropic: InferToolInput + openai: InferToolInput + google: InferToolInput + 'openai-chat': InferToolInput } diff --git a/packages/aiCore/src/core/providers/schemas.ts b/packages/aiCore/src/core/providers/schemas.ts index 7d09a3d1e6..147baf2c97 100644 --- a/packages/aiCore/src/core/providers/schemas.ts +++ b/packages/aiCore/src/core/providers/schemas.ts @@ -14,7 +14,7 @@ import { createXai } from '@ai-sdk/xai' import { createOpenRouter } from '@openrouter/ai-sdk-provider' import type { Provider } from 'ai' import { customProvider } from 'ai' -import { z } from 'zod' +import * as z from 'zod' /** * 基础 Provider IDs diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 493c08acbc..0205d4004f 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -53,6 +53,7 @@ export enum IpcChannel { Webview_SetOpenLinkExternal = 'webview:set-open-link-external', Webview_SetSpellCheckEnabled = 'webview:set-spell-check-enabled', + Webview_SearchHotkey = 'webview:search-hotkey', // Open Open_Path = 'open:path', diff --git a/packages/shared/config/types.ts b/packages/shared/config/types.ts index 5fbd2bef5d..6d76173e26 100644 --- a/packages/shared/config/types.ts +++ b/packages/shared/config/types.ts @@ -22,3 +22,12 @@ export type MCPProgressEvent = { callId: string progress: number // 0-1 range } + +export type WebviewKeyEvent = { + webviewId: number + key: string + control: boolean + meta: boolean + shift: boolean + alt: boolean +} diff --git a/src/main/index.ts b/src/main/index.ts index f0c1c0966e..9d8d19f57b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -35,6 +35,7 @@ import { windowService } from './services/WindowService' import { dataRefactorMigrateService } from './data/migrate/dataRefactor/DataRefactorMigrateService' import { dataApiService } from '@data/DataApiService' import { cacheService } from '@data/CacheService' +import { initWebviewHotkeys } from './services/WebviewService' const logger = loggerService.withContext('MainEntry') @@ -187,6 +188,7 @@ if (!app.requestSingleInstanceLock()) { /************FOR TESTING ONLY END****************/ + initWebviewHotkeys() // Set app user model id for windows electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio') diff --git a/src/main/ipc.ts b/src/main/ipc.ts index d9822fd0d1..2f68d5dd04 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -786,7 +786,6 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle(IpcChannel.Webview_SetOpenLinkExternal, (_, webviewId: number, isExternal: boolean) => setOpenLinkExternal(webviewId, isExternal) ) - ipcMain.handle(IpcChannel.Webview_SetSpellCheckEnabled, (_, webviewId: number, isEnable: boolean) => { const webview = webContents.fromId(webviewId) if (!webview) return diff --git a/src/main/mcpServers/dify-knowledge.ts b/src/main/mcpServers/dify-knowledge.ts index 04f010ce16..d1f2c07e1d 100644 --- a/src/main/mcpServers/dify-knowledge.ts +++ b/src/main/mcpServers/dify-knowledge.ts @@ -3,7 +3,7 @@ import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js' import { net } from 'electron' -import { z } from 'zod' +import * as z from 'zod' const logger = loggerService.withContext('DifyKnowledgeServer') diff --git a/src/main/mcpServers/fetch.ts b/src/main/mcpServers/fetch.ts index f170cc54c0..af7bf1ac6a 100644 --- a/src/main/mcpServers/fetch.ts +++ b/src/main/mcpServers/fetch.ts @@ -5,7 +5,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot import { net } from 'electron' import { JSDOM } from 'jsdom' import TurndownService from 'turndown' -import { z } from 'zod' +import * as z from 'zod' export const RequestPayloadSchema = z.object({ url: z.url(), diff --git a/src/main/mcpServers/filesystem.ts b/src/main/mcpServers/filesystem.ts index 9ec5ced0b0..ba10783881 100644 --- a/src/main/mcpServers/filesystem.ts +++ b/src/main/mcpServers/filesystem.ts @@ -8,7 +8,7 @@ import fs from 'fs/promises' import { minimatch } from 'minimatch' import os from 'os' import path from 'path' -import { z } from 'zod' +import * as z from 'zod' const logger = loggerService.withContext('MCP:FileSystemServer') diff --git a/src/main/services/WebviewService.ts b/src/main/services/WebviewService.ts index e437b82ce5..1b60cc6643 100644 --- a/src/main/services/WebviewService.ts +++ b/src/main/services/WebviewService.ts @@ -1,4 +1,5 @@ -import { session, shell, webContents } from 'electron' +import { IpcChannel } from '@shared/IpcChannel' +import { app, session, shell, webContents } from 'electron' /** * init the useragent of the webview session @@ -36,3 +37,61 @@ export function setOpenLinkExternal(webviewId: number, isExternal: boolean) { } }) } + +const attachKeyboardHandler = (contents: Electron.WebContents) => { + if (contents.getType?.() !== 'webview') { + return + } + + const handleBeforeInput = (event: Electron.Event, input: Electron.Input) => { + if (!input) { + return + } + + const key = input.key?.toLowerCase() + if (!key) { + return + } + + const isFindShortcut = (input.control || input.meta) && key === 'f' + const isEscape = key === 'escape' + const isEnter = key === 'enter' + + if (!isFindShortcut && !isEscape && !isEnter) { + return + } + // Prevent default to override the guest page's native find dialog + // and keep shortcuts routed to our custom search overlay + event.preventDefault() + + const host = contents.hostWebContents + if (!host || host.isDestroyed()) { + return + } + + host.send(IpcChannel.Webview_SearchHotkey, { + webviewId: contents.id, + key, + control: Boolean(input.control), + meta: Boolean(input.meta), + shift: Boolean(input.shift), + alt: Boolean(input.alt) + }) + } + + contents.on('before-input-event', handleBeforeInput) + contents.once('destroyed', () => { + contents.removeListener('before-input-event', handleBeforeInput) + }) +} + +export function initWebviewHotkeys() { + webContents.getAllWebContents().forEach((contents) => { + if (contents.isDestroyed()) return + attachKeyboardHandler(contents) + }) + + app.on('web-contents-created', (_, contents) => { + attachKeyboardHandler(contents) + }) +} diff --git a/src/main/services/mcp/oauth/types.ts b/src/main/services/mcp/oauth/types.ts index 384cb48ea9..1eecf08d4f 100644 --- a/src/main/services/mcp/oauth/types.ts +++ b/src/main/services/mcp/oauth/types.ts @@ -4,7 +4,7 @@ import type { OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js' import type EventEmitter from 'events' -import { z } from 'zod' +import * as z from 'zod' export interface OAuthStorageData { clientInfo?: OAuthClientInformation diff --git a/src/main/services/ocr/builtin/PpocrService.ts b/src/main/services/ocr/builtin/PpocrService.ts index 0bcb74776b..61c923c542 100644 --- a/src/main/services/ocr/builtin/PpocrService.ts +++ b/src/main/services/ocr/builtin/PpocrService.ts @@ -2,7 +2,7 @@ import { loadOcrImage } from '@main/utils/ocr' import type { ImageFileMetadata, OcrPpocrConfig, OcrResult, SupportedOcrFile } from '@types' import { isImageFileMetadata } from '@types' import { net } from 'electron' -import { z } from 'zod' +import * as z from 'zod' import { OcrBaseService } from './OcrBaseService' diff --git a/src/preload/index.ts b/src/preload/index.ts index b574733c83..efa0a9181b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -3,7 +3,7 @@ import type { SpanEntity, TokenUsage } from '@mcp-trace/trace-core' import type { SpanContext } from '@opentelemetry/api' import type { TerminalConfig } from '@shared/config/constant' import type { LogLevel, LogSourceWithContext } from '@shared/config/logger' -import type { FileChangeEvent } from '@shared/config/types' +import type { FileChangeEvent, WebviewKeyEvent } from '@shared/config/types' import type { CacheSyncMessage } from '@shared/data/cache/cacheTypes' import type { PreferenceDefaultScopeType, @@ -395,7 +395,16 @@ const api = { setOpenLinkExternal: (webviewId: number, isExternal: boolean) => ipcRenderer.invoke(IpcChannel.Webview_SetOpenLinkExternal, webviewId, isExternal), setSpellCheckEnabled: (webviewId: number, isEnable: boolean) => - ipcRenderer.invoke(IpcChannel.Webview_SetSpellCheckEnabled, webviewId, isEnable) + ipcRenderer.invoke(IpcChannel.Webview_SetSpellCheckEnabled, webviewId, isEnable), + onFindShortcut: (callback: (payload: WebviewKeyEvent) => void) => { + const listener = (_event: Electron.IpcRendererEvent, payload: WebviewKeyEvent) => { + callback(payload) + } + ipcRenderer.on(IpcChannel.Webview_SearchHotkey, listener) + return () => { + ipcRenderer.off(IpcChannel.Webview_SearchHotkey, listener) + } + } }, storeSync: { subscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Subscribe), diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts index 164a7fe92c..6a1de3215e 100644 --- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts @@ -166,9 +166,7 @@ export abstract class OpenAIBaseClient< baseURL: this.getBaseURL(), defaultHeaders: { ...this.defaultHeaders(), - ...this.provider.extra_headers, - ...(this.provider.id === 'copilot' ? { 'editor-version': 'vscode/1.97.2' } : {}), - ...(this.provider.id === 'copilot' ? { 'copilot-vision-request': 'true' } : {}) + ...this.provider.extra_headers } }) as TSdkInstance } diff --git a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts index 9e0fecab23..80b5296bd9 100644 --- a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts +++ b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts @@ -23,6 +23,7 @@ import type { 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 { replacePromptVariables } from '@renderer/utils/prompt' import type { ModelMessage, Tool } from 'ai' import { stepCountIs } from 'ai' @@ -159,14 +160,14 @@ export async function buildStreamTextParams( abortSignal: options.requestOptions?.signal, headers: options.requestOptions?.headers, providerOptions, - stopWhen: stepCountIs(10), + stopWhen: stepCountIs(20), maxRetries: 0 } if (tools) { params.tools = tools } if (assistant.prompt) { - params.system = assistant.prompt + params.system = await replacePromptVariables(assistant.prompt, model.name) } logger.debug('params', params) return { diff --git a/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts b/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts new file mode 100644 index 0000000000..eb6e73c8ae --- /dev/null +++ b/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts @@ -0,0 +1,89 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +vi.mock('@renderer/services/LoggerService', () => ({ + loggerService: { + withContext: () => ({ + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn() + }) + } +})) + +vi.mock('@renderer/services/AssistantService', () => ({ + getProviderByModel: vi.fn() +})) + +vi.mock('@renderer/store', () => ({ + default: { + getState: () => ({ copilot: { defaultHeaders: {} } }) + } +})) + +import type { Model, Provider } from '@renderer/types' + +import { COPILOT_DEFAULT_HEADERS, COPILOT_EDITOR_VERSION, isCopilotResponsesModel } from '../constants' +import { providerToAiSdkConfig } from '../providerConfig' + +const createWindowKeyv = () => { + const store = new Map() + return { + get: (key: string) => store.get(key), + set: (key: string, value: string) => { + store.set(key, value) + } + } +} + +const createCopilotProvider = (): Provider => ({ + id: 'copilot', + type: 'openai', + name: 'GitHub Copilot', + apiKey: 'test-key', + apiHost: 'https://api.githubcopilot.com', + models: [], + isSystem: true +}) + +const createModel = (id: string, name = id): Model => ({ + id, + name, + provider: 'copilot', + group: 'copilot' +}) + +describe('Copilot responses routing', () => { + beforeEach(() => { + ;(globalThis as any).window = { + ...(globalThis as any).window, + keyv: createWindowKeyv() + } + }) + + it('detects official GPT-5 Codex identifiers case-insensitively', () => { + expect(isCopilotResponsesModel(createModel('gpt-5-codex', 'gpt-5-codex'))).toBe(true) + expect(isCopilotResponsesModel(createModel('GPT-5-CODEX', 'GPT-5-CODEX'))).toBe(true) + expect(isCopilotResponsesModel(createModel('gpt-5-codex', 'custom-name'))).toBe(true) + expect(isCopilotResponsesModel(createModel('custom-id', 'custom-name'))).toBe(false) + }) + + it('configures gpt-5-codex with the Copilot provider', () => { + const provider = createCopilotProvider() + const config = providerToAiSdkConfig(provider, createModel('gpt-5-codex', 'GPT-5-CODEX')) + + expect(config.providerId).toBe('github-copilot-openai-compatible') + expect(config.options.headers?.['Editor-Version']).toBe(COPILOT_EDITOR_VERSION) + expect(config.options.headers?.['Copilot-Integration-Id']).toBe(COPILOT_DEFAULT_HEADERS['Copilot-Integration-Id']) + expect(config.options.headers?.['copilot-vision-request']).toBe('true') + }) + + it('uses the Copilot provider for other models and keeps headers', () => { + const provider = createCopilotProvider() + const config = providerToAiSdkConfig(provider, createModel('gpt-4')) + + expect(config.providerId).toBe('github-copilot-openai-compatible') + expect(config.options.headers?.['Editor-Version']).toBe(COPILOT_DEFAULT_HEADERS['Editor-Version']) + expect(config.options.headers?.['Copilot-Integration-Id']).toBe(COPILOT_DEFAULT_HEADERS['Copilot-Integration-Id']) + }) +}) diff --git a/src/renderer/src/aiCore/provider/constants.ts b/src/renderer/src/aiCore/provider/constants.ts new file mode 100644 index 0000000000..c7cd90bd93 --- /dev/null +++ b/src/renderer/src/aiCore/provider/constants.ts @@ -0,0 +1,25 @@ +import type { Model } from '@renderer/types' + +export const COPILOT_EDITOR_VERSION = 'vscode/1.104.1' +export const COPILOT_PLUGIN_VERSION = 'copilot-chat/0.26.7' +export const COPILOT_INTEGRATION_ID = 'vscode-chat' +export const COPILOT_USER_AGENT = 'GitHubCopilotChat/0.26.7' + +export const COPILOT_DEFAULT_HEADERS = { + 'Copilot-Integration-Id': COPILOT_INTEGRATION_ID, + 'User-Agent': COPILOT_USER_AGENT, + 'Editor-Version': COPILOT_EDITOR_VERSION, + 'Editor-Plugin-Version': COPILOT_PLUGIN_VERSION, + 'editor-version': COPILOT_EDITOR_VERSION, + 'editor-plugin-version': COPILOT_PLUGIN_VERSION, + 'copilot-vision-request': 'true' +} as const + +// Models that require the OpenAI Responses endpoint when routed through GitHub Copilot (#10560) +const COPILOT_RESPONSES_MODEL_IDS = ['gpt-5-codex'] + +export function isCopilotResponsesModel(model: Model): boolean { + const normalizedId = model.id?.trim().toLowerCase() + const normalizedName = model.name?.trim().toLowerCase() + return COPILOT_RESPONSES_MODEL_IDS.some((target) => normalizedId === target || normalizedName === target) +} diff --git a/src/renderer/src/aiCore/provider/factory.ts b/src/renderer/src/aiCore/provider/factory.ts index 3b7de72679..4cdbfb6d40 100644 --- a/src/renderer/src/aiCore/provider/factory.ts +++ b/src/renderer/src/aiCore/provider/factory.ts @@ -28,7 +28,8 @@ const STATIC_PROVIDER_MAPPING: Record = { gemini: 'google', // Google Gemini -> google 'azure-openai': 'azure', // Azure OpenAI -> azure 'openai-response': 'openai', // OpenAI Responses -> openai - grok: 'xai' // Grok -> xai + grok: 'xai', // Grok -> xai + copilot: 'github-copilot-openai-compatible' } /** diff --git a/src/renderer/src/aiCore/provider/providerConfig.ts b/src/renderer/src/aiCore/provider/providerConfig.ts index 020cd2a65c..3945462b54 100644 --- a/src/renderer/src/aiCore/provider/providerConfig.ts +++ b/src/renderer/src/aiCore/provider/providerConfig.ts @@ -22,6 +22,7 @@ import { formatApiHost } from '@renderer/utils/api' import { cloneDeep, trim } from 'lodash' import { aihubmixProviderCreator, newApiResolverCreator, vertexAnthropicProviderCreator } from './config' +import { COPILOT_DEFAULT_HEADERS } from './constants' import { getAiSdkProviderId } from './factory' const logger = loggerService.withContext('ProviderConfigProcessor') @@ -109,6 +110,9 @@ function formatProviderApiHost(provider: Provider): Provider { if (!formatted.anthropicApiHost) { formatted.anthropicApiHost = formatted.apiHost } + } else if (formatted.id === 'copilot') { + const trimmed = trim(formatted.apiHost) + formatted.apiHost = trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed } else if (formatted.type === 'gemini') { formatted.apiHost = formatApiHost(formatted.apiHost, 'v1beta') } else { @@ -151,6 +155,26 @@ export function providerToAiSdkConfig( baseURL: trim(actualProvider.apiHost), apiKey: getRotatedApiKey(actualProvider) } + + const isCopilotProvider = actualProvider.id === 'copilot' + if (isCopilotProvider) { + const storedHeaders = store.getState().copilot.defaultHeaders ?? {} + const options = ProviderConfigFactory.fromProvider('github-copilot-openai-compatible', baseConfig, { + headers: { + ...COPILOT_DEFAULT_HEADERS, + ...storedHeaders, + ...actualProvider.extra_headers + }, + name: actualProvider.id, + includeUsage: true + }) + + return { + providerId: 'github-copilot-openai-compatible', + options + } + } + // 处理OpenAI模式 const extraOptions: any = {} if (actualProvider.type === 'openai-response' && !isOpenAIChatCompletionOnlyModel(model)) { @@ -172,15 +196,6 @@ export function providerToAiSdkConfig( } } } - - // copilot - if (actualProvider.id === 'copilot') { - extraOptions.headers = { - ...extraOptions.headers, - 'editor-version': 'vscode/1.97.2', - 'copilot-vision-request': 'true' - } - } // azure if (aiSdkProviderId === 'azure' || actualProvider.type === 'azure-openai') { extraOptions.apiVersion = actualProvider.apiVersion @@ -229,7 +244,6 @@ export function providerToAiSdkConfig( } } - // 如果AI SDK支持该provider,使用原生配置 if (hasProviderConfig(aiSdkProviderId) && aiSdkProviderId !== 'openai-compatible') { const options = ProviderConfigFactory.fromProvider(aiSdkProviderId, baseConfig, extraOptions) return { @@ -277,9 +291,17 @@ export async function prepareSpecialProviderConfig( ) { switch (provider.id) { case 'copilot': { - const defaultHeaders = store.getState().copilot.defaultHeaders - const { token } = await window.api.copilot.getToken(defaultHeaders) + const defaultHeaders = store.getState().copilot.defaultHeaders ?? {} + const headers = { + ...COPILOT_DEFAULT_HEADERS, + ...defaultHeaders + } + const { token } = await window.api.copilot.getToken(headers) config.options.apiKey = token + config.options.headers = { + ...headers, + ...config.options.headers + } break } case 'cherryai': { diff --git a/src/renderer/src/aiCore/provider/providerInitialization.ts b/src/renderer/src/aiCore/provider/providerInitialization.ts index 3c188313b9..2a19729d7e 100644 --- a/src/renderer/src/aiCore/provider/providerInitialization.ts +++ b/src/renderer/src/aiCore/provider/providerInitialization.ts @@ -32,6 +32,14 @@ export const NEW_PROVIDER_CONFIGS: ProviderConfig[] = [ supportsImageGeneration: true, aliases: ['vertexai-anthropic'] }, + { + id: 'github-copilot-openai-compatible', + name: 'GitHub Copilot OpenAI Compatible', + import: () => import('@opeoginni/github-copilot-openai-compatible'), + creatorFunctionName: 'createGitHubCopilotOpenAICompatible', + supportsImageGeneration: false, + aliases: ['copilot', 'github-copilot'] + }, { id: 'bedrock', name: 'Amazon Bedrock', diff --git a/src/renderer/src/aiCore/tools/KnowledgeSearchTool.ts b/src/renderer/src/aiCore/tools/KnowledgeSearchTool.ts index a4b0492752..dcd2ef6095 100644 --- a/src/renderer/src/aiCore/tools/KnowledgeSearchTool.ts +++ b/src/renderer/src/aiCore/tools/KnowledgeSearchTool.ts @@ -4,7 +4,7 @@ import type { ExtractResults, KnowledgeExtractResults } from '@renderer/utils/ex import { REFERENCE_PROMPT } from '@shared/config/prompts' import { type InferToolInput, type InferToolOutput, tool } from 'ai' import { isEmpty } from 'lodash' -import { z } from 'zod' +import * as z from 'zod' /** * 知识库搜索工具 diff --git a/src/renderer/src/aiCore/tools/MemorySearchTool.ts b/src/renderer/src/aiCore/tools/MemorySearchTool.ts index 8e67363595..20064dd1b2 100644 --- a/src/renderer/src/aiCore/tools/MemorySearchTool.ts +++ b/src/renderer/src/aiCore/tools/MemorySearchTool.ts @@ -1,7 +1,7 @@ import store from '@renderer/store' import { selectCurrentUserId, selectGlobalMemoryEnabled, selectMemoryConfig } from '@renderer/store/memory' import { type InferToolInput, type InferToolOutput, tool } from 'ai' -import { z } from 'zod' +import * as z from 'zod' import { MemoryProcessor } from '../../services/MemoryProcessor' diff --git a/src/renderer/src/aiCore/tools/WebSearchTool.ts b/src/renderer/src/aiCore/tools/WebSearchTool.ts index a4d31f0e84..19b4595223 100644 --- a/src/renderer/src/aiCore/tools/WebSearchTool.ts +++ b/src/renderer/src/aiCore/tools/WebSearchTool.ts @@ -3,7 +3,7 @@ import type { WebSearchProvider, WebSearchProviderResponse } from '@renderer/typ import type { ExtractResults } from '@renderer/utils/extract' import { REFERENCE_PROMPT } from '@shared/config/prompts' import { type InferToolInput, type InferToolOutput, tool } from 'ai' -import { z } from 'zod' +import * as z from 'zod' /** * 使用预提取关键词的网络搜索工具 diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index f3dca9eaa6..bc4c3208c6 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -6,6 +6,7 @@ import { getThinkModelType, isDeepSeekHybridInferenceModel, isDoubaoThinkingAutoModel, + isGrok4FastReasoningModel, isGrokReasoningModel, isOpenAIReasoningModel, isQwenAlwaysThinkModel, @@ -53,7 +54,12 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin return {} } // Don't disable reasoning for models that require it - if (isGrokReasoningModel(model) || isOpenAIReasoningModel(model) || model.id.includes('seed-oss')) { + if ( + isGrokReasoningModel(model) || + isOpenAIReasoningModel(model) || + isQwenAlwaysThinkModel(model) || + model.id.includes('seed-oss') + ) { return {} } return { reasoning: { enabled: false, exclude: true } } @@ -101,6 +107,7 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin // reasoningEffort有效的情况 // DeepSeek hybrid inference models, v3.1 and maybe more in the future // 不同的 provider 有不同的思考控制方式,在这里统一解决 + if (isDeepSeekHybridInferenceModel(model)) { if (isSystemProvider(provider)) { switch (provider.id) { @@ -143,6 +150,16 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin // OpenRouter models if (model.provider === SystemProviderIds.openrouter) { + // Grok 4 Fast doesn't support effort levels, always use enabled: true + if (isGrok4FastReasoningModel(model)) { + return { + reasoning: { + enabled: true // Ignore effort level, just enable reasoning + } + } + } + + // Other OpenRouter models that support effort levels if (isSupportedReasoningEffortModel(model) || isSupportedThinkingTokenModel(model)) { return { reasoning: { @@ -413,6 +430,13 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re return {} } +/** + * Get XAI-specific reasoning parameters + * This function should only be called for XAI provider models + * @param assistant - The assistant configuration + * @param model - The model being used + * @returns XAI-specific reasoning parameters + */ export function getXAIReasoningParams(assistant: Assistant, model: Model): Record { if (!isSupportedReasoningEffortGrokModel(model)) { return {} @@ -420,6 +444,11 @@ export function getXAIReasoningParams(assistant: Assistant, model: Model): Recor const { reasoning_effort: reasoningEffort } = getAssistantSettings(assistant) + if (!reasoningEffort) { + return {} + } + + // For XAI provider Grok models, use reasoningEffort parameter directly return { reasoningEffort } diff --git a/src/renderer/src/assets/images/apps/3mintop.png b/src/renderer/src/assets/images/apps/3mintop.png index d43da03cbb..4063caf74a 100644 Binary files a/src/renderer/src/assets/images/apps/3mintop.png and b/src/renderer/src/assets/images/apps/3mintop.png differ diff --git a/src/renderer/src/assets/images/apps/stepfun.png b/src/renderer/src/assets/images/apps/stepfun.png new file mode 100644 index 0000000000..74aba7e592 Binary files /dev/null and b/src/renderer/src/assets/images/apps/stepfun.png differ diff --git a/src/renderer/src/assets/images/apps/wpslingxi.webp b/src/renderer/src/assets/images/apps/wpslingxi.webp index 4c2c3b83ea..facadaccb0 100644 Binary files a/src/renderer/src/assets/images/apps/wpslingxi.webp and b/src/renderer/src/assets/images/apps/wpslingxi.webp differ diff --git a/src/renderer/src/assets/images/apps/yuewen.png b/src/renderer/src/assets/images/apps/yuewen.png deleted file mode 100644 index de9d81e545..0000000000 Binary files a/src/renderer/src/assets/images/apps/yuewen.png and /dev/null differ diff --git a/src/renderer/src/assets/images/providers/silicon.png b/src/renderer/src/assets/images/providers/silicon.png index 138f90309f..041d090eae 100644 Binary files a/src/renderer/src/assets/images/providers/silicon.png and b/src/renderer/src/assets/images/providers/silicon.png differ diff --git a/src/renderer/src/components/MinApp/MinappPopupContainer.tsx b/src/renderer/src/components/MinApp/MinappPopupContainer.tsx index 786f4145ce..6f532f15a2 100644 --- a/src/renderer/src/components/MinApp/MinappPopupContainer.tsx +++ b/src/renderer/src/components/MinApp/MinappPopupContainer.tsx @@ -542,7 +542,7 @@ const MinappPopupContainer: React.FC = () => { {/* 在所有小程序中显示GoogleLoginTip */} {!isReady && ( - + = // Default quick assistant model glm45FlashModel ], - // cherryin: [], + cherryin: [], vertexai: [], '302ai': [ { diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 403eca947d..8d306e7d8e 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -14,7 +14,7 @@ import { GEMINI_FLASH_MODEL_REGEX } from './websearch' // Reasoning models export const REASONING_REGEX = - /^(o\d+(?:-[\w-]+)?|.*\b(?:reasoning|reasoner|thinking)\b.*|.*-[rR]\d+.*|.*\bqwq(?:-[\w-]+)?\b.*|.*\bhunyuan-t1(?:-[\w-]+)?\b.*|.*\bglm-zero-preview\b.*|.*\bgrok-(?:3-mini|4)(?:-[\w-]+)?\b.*)$/i + /^(?!.*-non-reasoning\b)(o\d+(?:-[\w-]+)?|.*\b(?:reasoning|reasoner|thinking)\b.*|.*-[rR]\d+.*|.*\bqwq(?:-[\w-]+)?\b.*|.*\bhunyuan-t1(?:-[\w-]+)?\b.*|.*\bglm-zero-preview\b.*|.*\bgrok-(?:3-mini|4|4-fast)(?:-[\w-]+)?\b.*)$/i // 模型类型到支持的reasoning_effort的映射表 // TODO: refactor this. too many identical options @@ -24,6 +24,7 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = { gpt5: ['minimal', 'low', 'medium', 'high'] as const, gpt5_codex: ['low', 'medium', 'high'] as const, grok: ['low', 'high'] as const, + grok4_fast: ['auto'] as const, gemini: ['low', 'medium', 'high', 'auto'] as const, gemini_pro: ['low', 'medium', 'high', 'auto'] as const, qwen: ['low', 'medium', 'high'] as const, @@ -43,6 +44,7 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { gpt5: [...MODEL_SUPPORTED_REASONING_EFFORT.gpt5] as const, gpt5_codex: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_codex, grok: MODEL_SUPPORTED_REASONING_EFFORT.grok, + grok4_fast: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.grok4_fast] as const, gemini: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini] as const, gemini_pro: MODEL_SUPPORTED_REASONING_EFFORT.gemini_pro, qwen: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen] as const, @@ -66,6 +68,8 @@ export const getThinkModelType = (model: Model): ThinkingModelType => { } } else if (isSupportedReasoningEffortOpenAIModel(model)) { thinkingModelType = 'o' + } else if (isGrok4FastReasoningModel(model)) { + thinkingModelType = 'grok4_fast' } else if (isSupportedThinkingTokenGeminiModel(model)) { if (GEMINI_FLASH_MODEL_REGEX.test(model.id)) { thinkingModelType = 'gemini' @@ -142,19 +146,46 @@ export function isSupportedReasoningEffortGrokModel(model?: Model): boolean { } const modelId = getLowerBaseModelName(model.id) + const providerId = model.provider.toLowerCase() if (modelId.includes('grok-3-mini')) { return true } + if (providerId === 'openrouter' && modelId.includes('grok-4-fast')) { + return true + } + return false } +/** + * Checks if the model is Grok 4 Fast reasoning version + * Explicitly excludes non-reasoning variants (models with 'non-reasoning' in their ID) + * + * Note: XAI official uses different model IDs for reasoning vs non-reasoning + * Third-party providers like OpenRouter expose a single ID with reasoning parameters, while first-party providers require separate IDs. Only the OpenRouter variant supports toggling. + * + * @param model - The model to check + * @returns true if the model is a reasoning-enabled Grok 4 Fast model + */ +export function isGrok4FastReasoningModel(model?: Model): boolean { + if (!model) { + return false + } + + const modelId = getLowerBaseModelName(model.id) + return modelId.includes('grok-4-fast') && !modelId.includes('non-reasoning') +} + export function isGrokReasoningModel(model?: Model): boolean { if (!model) { return false } const modelId = getLowerBaseModelName(model.id) - if (isSupportedReasoningEffortGrokModel(model) || modelId.includes('grok-4')) { + if ( + isSupportedReasoningEffortGrokModel(model) || + (modelId.includes('grok-4') && !modelId.includes('non-reasoning')) + ) { return true } @@ -265,7 +296,11 @@ export function isQwenAlwaysThinkModel(model?: Model): boolean { return false } const modelId = getLowerBaseModelName(model.id, '/') - return modelId.startsWith('qwen3') && modelId.includes('thinking') + // 包括 qwen3 开头的 thinking 模型和 qwen3-vl 的 thinking 模型 + return ( + (modelId.startsWith('qwen3') && modelId.includes('thinking')) || + (modelId.includes('qwen3-vl') && modelId.includes('thinking')) + ) } // Doubao 支持思考模式的模型正则 @@ -329,7 +364,10 @@ export const isPerplexityReasoningModel = (model?: Model): boolean => { } const modelId = getLowerBaseModelName(model.id, '/') - return isSupportedReasoningEffortPerplexityModel(model) || modelId.includes('reasoning') + return ( + isSupportedReasoningEffortPerplexityModel(model) || + (modelId.includes('reasoning') && !modelId.includes('non-reasoning')) + ) } export const isSupportedReasoningEffortPerplexityModel = (model: Model): boolean => { @@ -443,6 +481,8 @@ export const THINKING_TOKEN_MAP: Record = // qwen-plus-x 系列自 qwen-plus-2025-07-28 后模型最长思维链变为 81_920, qwen-plus 模型于 2025.9.16 同步变更 'qwen3-235b-a22b-thinking-2507$': { min: 0, max: 81_920 }, 'qwen3-30b-a3b-thinking-2507$': { min: 0, max: 81_920 }, + 'qwen3-vl-235b-a22b-thinking$': { min: 0, max: 81_920 }, + 'qwen3-vl-30b-a3b-thinking$': { min: 0, max: 81_920 }, 'qwen-plus-2025-07-14$': { min: 0, max: 38_912 }, 'qwen-plus-2025-04-28$': { min: 0, max: 38_912 }, 'qwen3-1\\.7b$': { min: 0, max: 30_720 }, diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index 10de514c87..b9dfac5311 100644 --- a/src/renderer/src/config/models/vision.ts +++ b/src/renderer/src/config/models/vision.ts @@ -24,7 +24,7 @@ const visionAllowedModels = [ 'qwen2.5-vl', 'qwen3-vl', 'qwen2.5-omni', - 'qwen3-omni', + 'qwen3-omni(?:-[\\w-]+)?', 'qvq', 'internvl2', 'grok-vision-beta', @@ -82,14 +82,14 @@ export const IMAGE_ENHANCEMENT_MODELS = [ 'grok-2-image(?:-[\\w-]+)?', 'qwen-image-edit', 'gpt-image-1', - 'gemini-2.5-flash-image-preview', + 'gemini-2.5-flash-image', 'gemini-2.0-flash-preview-image-generation' ] const IMAGE_ENHANCEMENT_MODELS_REGEX = new RegExp(IMAGE_ENHANCEMENT_MODELS.join('|'), 'i') // Models that should auto-enable image generation button when selected -export const AUTO_ENABLE_IMAGE_MODELS = ['gemini-2.5-flash-image-preview', ...DEDICATED_IMAGE_MODELS] +export const AUTO_ENABLE_IMAGE_MODELS = ['gemini-2.5-flash-image', ...DEDICATED_IMAGE_MODELS] export const OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS = [ 'o3', @@ -107,7 +107,7 @@ export const GENERATE_IMAGE_MODELS = [ 'gemini-2.0-flash-exp', 'gemini-2.0-flash-exp-image-generation', 'gemini-2.0-flash-preview-image-generation', - 'gemini-2.5-flash-image-preview', + 'gemini-2.5-flash-image', ...DEDICATED_IMAGE_MODELS ] diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 5bc516b35d..84a2590658 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -74,16 +74,16 @@ export const CHERRYAI_PROVIDER: SystemProvider = { } export const SYSTEM_PROVIDERS_CONFIG: Record = { - // cherryin: { - // id: 'cherryin', - // name: 'CherryIN', - // type: 'openai', - // apiKey: '', - // apiHost: 'https://open.cherryin.ai', - // models: [], - // isSystem: true, - // enabled: true - // }, + cherryin: { + id: 'cherryin', + name: 'CherryIN', + type: 'openai', + apiKey: '', + apiHost: 'https://open.cherryin.net', + models: [], + isSystem: true, + enabled: true + }, silicon: { id: 'silicon', name: 'Silicon', @@ -734,17 +734,17 @@ type ProviderUrls = { } export const PROVIDER_URLS: Record = { - // cherryin: { - // api: { - // url: 'https://open.cherryin.ai' - // }, - // websites: { - // official: 'https://open.cherryin.ai', - // apiKey: 'https://open.cherryin.ai/console/token', - // docs: 'https://open.cherryin.ai', - // models: 'https://open.cherryin.ai/pricing' - // } - // }, + cherryin: { + api: { + url: 'https://open.cherryin.net' + }, + websites: { + official: 'https://open.cherryin.ai', + apiKey: 'https://open.cherryin.ai/console/token', + docs: 'https://open.cherryin.ai', + models: 'https://open.cherryin.ai/pricing' + } + }, ph8: { api: { url: 'https://ph8.co' diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 74b2911dd1..0d46390767 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "Nami AI Search", "qwen": "Qwen", "sensechat": "SenseChat", + "stepfun": "Stepfun", "tencent-yuanbao": "Yuanbao", "tiangong-ai": "Skywork", "wanzhi": "Wanzhi", "wenxin": "ERNIE", "wps-copilot": "WPS Copilot", "xiaoyi": "Xiaoyi", - "yuewen": "Yuewen", "zhihu": "Zhihu" }, "miniwindow": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 821d755aa8..2ae391419f 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "纳米AI搜索", "qwen": "通义千问", "sensechat": "商量", + "stepfun": "阶跃AI", "tencent-yuanbao": "腾讯元宝", "tiangong-ai": "天工AI", "wanzhi": "万知", "wenxin": "文心一言", "wps-copilot": "WPS灵犀", "xiaoyi": "小艺", - "yuewen": "跃问", "zhihu": "知乎直答" }, "miniwindow": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index bac03e14a2..3e9f321f17 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "納米AI搜索", "qwen": "通義千問", "sensechat": "商量", + "stepfun": "階躍AI", "tencent-yuanbao": "騰訊元寶", "tiangong-ai": "天工AI", "wanzhi": "萬知", "wenxin": "文心一言", "wps-copilot": "WPS靈犀", "xiaoyi": "小藝", - "yuewen": "躍問", "zhihu": "知乎直答" }, "miniwindow": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index a9e730734f..ed2ae1aee7 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "Nami AI Search", "qwen": "Qwen", "sensechat": "SenseChat", + "stepfun": "Stepfun", "tencent-yuanbao": "Yuanbao", "tiangong-ai": "Skywork", "wanzhi": "Wanzhi", "wenxin": "ERNIE", "wps-copilot": "WPS Copilot", "xiaoyi": "Xiaoyi", - "yuewen": "Yuewen", "zhihu": "Zhihu" }, "miniwindow": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 3cb4d7a3ea..eeb943c39f 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "Nami AI Search", "qwen": "Qwen", "sensechat": "SenseChat", + "stepfun": "Stepfun", "tencent-yuanbao": "Yuanbao", "tiangong-ai": "Skywork", "wanzhi": "Wanzhi", "wenxin": "ERNIE", "wps-copilot": "WPS Copilot", "xiaoyi": "Xiaoyi", - "yuewen": "Yuewen", "zhihu": "Zhihu" }, "miniwindow": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index c0f4ff3def..b0e4a1afed 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "Nami AI Search", "qwen": "Qwen", "sensechat": "SenseChat", + "stepfun": "Stepfun", "tencent-yuanbao": "Yuanbao", "tiangong-ai": "Skywork", "wanzhi": "Wanzhi", "wenxin": "ERNIE", "wps-copilot": "WPS Copilot", "xiaoyi": "Xiaoyi", - "yuewen": "Yuewen", "zhihu": "Zhihu" }, "miniwindow": { diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index dc047aec76..7d07988276 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "Nami AI Search", "qwen": "通義千問", "sensechat": "SenseChat", + "stepfun": "Stepfun", "tencent-yuanbao": "騰訊元宝", "tiangong-ai": "Skywork", "wanzhi": "万知", "wenxin": "ERNIE", "wps-copilot": "WPS Copilot", "xiaoyi": "小藝", - "yuewen": "躍問", "zhihu": "知乎直答" }, "miniwindow": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index f24fea2013..e4707b846d 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "Nami AI Search", "qwen": "Qwen", "sensechat": "SenseChat", + "stepfun": "Stepfun", "tencent-yuanbao": "Yuanbao", "tiangong-ai": "Skywork", "wanzhi": "Wanzhi", "wenxin": "ERNIE", "wps-copilot": "WPS Copilot", "xiaoyi": "Xiaoyi", - "yuewen": "Yuewen", "zhihu": "Zhihu" }, "miniwindow": { diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index a210be8dd4..d4c631d81c 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -1806,13 +1806,13 @@ "nami-ai-search": "Nami AI Search", "qwen": "Qwen", "sensechat": "SenseChat", + "stepfun": "Stepfun", "tencent-yuanbao": "Tencent Yuanbao", "tiangong-ai": "Skywork", "wanzhi": "Wanzhi", "wenxin": "ERNIE", "wps-copilot": "WPS Copilot", "xiaoyi": "Xiaoyi", - "yuewen": "Yuewen", "zhihu": "Zhihu" }, "miniwindow": { diff --git a/src/renderer/src/pages/home/Markdown/CitationTooltip.tsx b/src/renderer/src/pages/home/Markdown/CitationTooltip.tsx index 258357692a..dc204afec2 100644 --- a/src/renderer/src/pages/home/Markdown/CitationTooltip.tsx +++ b/src/renderer/src/pages/home/Markdown/CitationTooltip.tsx @@ -2,7 +2,7 @@ import { Tooltip } from '@cherrystudio/ui' import Favicon from '@renderer/components/Icons/FallbackFavicon' import React, { memo, useCallback, useMemo } from 'react' import styled from 'styled-components' -import { z } from 'zod' +import * as z from 'zod' export const CitationSchema = z.object({ url: z.url(), diff --git a/src/renderer/src/pages/home/Messages/MessageGroup.tsx b/src/renderer/src/pages/home/Messages/MessageGroup.tsx index 706381e899..c3b626b1e1 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroup.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroup.tsx @@ -361,8 +361,7 @@ const GridContainer = styled(Scrollbar)<{ $count: number; $gridColumns: number } &.vertical { grid-template-columns: repeat(1, minmax(0, 1fr)); gap: 8px; - overflow-y: auto; - overflow-x: hidden; + overflow: hidden; } &.grid { grid-template-columns: repeat( diff --git a/src/renderer/src/pages/home/Tabs/AgentSettingsTab.tsx b/src/renderer/src/pages/home/Tabs/AgentSettingsTab.tsx new file mode 100644 index 0000000000..d1f588ff1f --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/AgentSettingsTab.tsx @@ -0,0 +1,40 @@ +import { Button, Divider } from '@heroui/react' +import type { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' +import { AgentSettingsPopup } from '@renderer/pages/settings/AgentSettings' +import AdvancedSettings from '@renderer/pages/settings/AgentSettings/AdvancedSettings' +import AgentEssentialSettings from '@renderer/pages/settings/AgentSettings/AgentEssentialSettings' +import type { GetAgentResponse } from '@renderer/types/agent' +import type { FC } from 'react' +import { useTranslation } from 'react-i18next' + +interface Props { + agent: GetAgentResponse | undefined | null + update: ReturnType['updateAgent'] +} + +const AgentSettingsTab: FC = ({ agent, update }) => { + const { t } = useTranslation() + + const onMoreSetting = () => { + if (agent?.id) { + AgentSettingsPopup.show({ agentId: agent.id! }) + } + } + + if (!agent) { + return null + } + + return ( +
+ + + + +
+ ) +} + +export default AgentSettingsTab diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 15f70f44e4..8b9fd3a6bd 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -1,11 +1,28 @@ +import { Alert, Spinner } from '@heroui/react' import Scrollbar from '@renderer/components/Scrollbar' +import { useAgents } from '@renderer/hooks/agents/useAgents' +import { useAssistants } from '@renderer/hooks/useAssistant' +import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets' +import { useRuntime } from '@renderer/hooks/useRuntime' +import { useSettings } from '@renderer/hooks/useSettings' +import { useAssistantsTabSortType } from '@renderer/hooks/useStore' +import { useTags } from '@renderer/hooks/useTags' +import { useAppDispatch } from '@renderer/store' +import { addIknowAction } from '@renderer/store/runtime' import type { Assistant } from '@renderer/types' +import type { AssistantTabSortType } from '@shared/data/preference/preferenceTypes' import type { FC } from 'react' -import { useRef } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { AgentSection } from './components/AgentSection' -import Assistants from './components/Assistants' +import UnifiedAddButton from './components/UnifiedAddButton' +import { UnifiedList } from './components/UnifiedList' +import { UnifiedTagGroups } from './components/UnifiedTagGroups' +import { useActiveAgent } from './hooks/useActiveAgent' +import { useUnifiedGrouping } from './hooks/useUnifiedGrouping' +import { useUnifiedItems } from './hooks/useUnifiedItems' +import { useUnifiedSorting } from './hooks/useUnifiedSorting' interface AssistantsTabProps { activeAssistant: Assistant @@ -14,12 +31,143 @@ interface AssistantsTabProps { onCreateDefaultAssistant: () => void } +const ALERT_KEY = 'enable_api_server_to_use_agent' + const AssistantsTab: FC = (props) => { + const { activeAssistant, setActiveAssistant, onCreateAssistant, onCreateDefaultAssistant } = props const containerRef = useRef(null) + const { t } = useTranslation() + const { apiServer } = useSettings() + const { iknow, chat } = useRuntime() + const dispatch = useAppDispatch() + + // Agent related hooks + const { agents, deleteAgent, isLoading: agentsLoading, error: agentsError } = useAgents() + const { activeAgentId } = chat + const { setActiveAgentId } = useActiveAgent() + + // Assistant related hooks + const { assistants, removeAssistant, copyAssistant, updateAssistants } = useAssistants() + const { addAssistantPreset } = useAssistantPresets() + const { collapsedTags, toggleTagCollapse } = useTags() + const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType() + const [dragging, setDragging] = useState(false) + + // Unified items management + const { unifiedItems, handleUnifiedListReorder } = useUnifiedItems({ + agents, + assistants, + apiServerEnabled: apiServer.enabled, + agentsLoading, + agentsError, + updateAssistants + }) + + // Sorting + const { sortByPinyinAsc, sortByPinyinDesc } = useUnifiedSorting({ + unifiedItems, + updateAssistants + }) + + // Grouping + const { groupedUnifiedItems, handleUnifiedGroupReorder } = useUnifiedGrouping({ + unifiedItems, + assistants, + agents, + apiServerEnabled: apiServer.enabled, + agentsLoading, + agentsError, + updateAssistants + }) + + useEffect(() => { + if (!agentsLoading && agents.length > 0 && !activeAgentId && apiServer.enabled) { + setActiveAgentId(agents[0].id) + } + }, [agentsLoading, agents, activeAgentId, setActiveAgentId, apiServer.enabled]) + + const onDeleteAssistant = useCallback( + (assistant: Assistant) => { + const remaining = assistants.filter((a) => a.id !== assistant.id) + if (assistant.id === activeAssistant?.id) { + const newActive = remaining[remaining.length - 1] + newActive ? setActiveAssistant(newActive) : onCreateDefaultAssistant() + } + removeAssistant(assistant.id) + }, + [activeAssistant, assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant] + ) + + const handleSortByChange = useCallback( + (sortType: AssistantTabSortType) => { + setAssistantsTabSortType(sortType) + }, + [setAssistantsTabSortType] + ) + return ( - - + {!apiServer.enabled && !iknow[ALERT_KEY] && ( + { + dispatch(addIknowAction(ALERT_KEY)) + }} + /> + )} + + {agentsLoading && } + {apiServer.enabled && agentsError && } + + {assistantsTabSortType === 'tags' ? ( + setDragging(true)} + onDragEnd={() => setDragging(false)} + onToggleTagCollapse={toggleTagCollapse} + onAssistantSwitch={setActiveAssistant} + onAssistantDelete={onDeleteAssistant} + onAgentDelete={deleteAgent} + onAgentPress={setActiveAgentId} + addPreset={addAssistantPreset} + copyAssistant={copyAssistant} + onCreateDefaultAssistant={onCreateDefaultAssistant} + handleSortByChange={handleSortByChange} + sortByPinyinAsc={sortByPinyinAsc} + sortByPinyinDesc={sortByPinyinDesc} + /> + ) : ( + setDragging(true)} + onDragEnd={() => setDragging(false)} + onAssistantSwitch={setActiveAssistant} + onAssistantDelete={onDeleteAssistant} + onAgentDelete={deleteAgent} + onAgentPress={setActiveAgentId} + addPreset={addAssistantPreset} + copyAssistant={copyAssistant} + onCreateDefaultAssistant={onCreateDefaultAssistant} + handleSortByChange={handleSortByChange} + sortByPinyinAsc={sortByPinyinAsc} + sortByPinyinDesc={sortByPinyinDesc} + /> + )} + + + + {!dragging &&
}
) } @@ -28,7 +176,6 @@ const Container = styled(Scrollbar)` display: flex; flex-direction: column; padding: 10px; - margin-top: 3px; ` export default AssistantsTab diff --git a/src/renderer/src/pages/home/Tabs/components/AddButton.tsx b/src/renderer/src/pages/home/Tabs/components/AddButton.tsx new file mode 100644 index 0000000000..62feecd226 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/components/AddButton.tsx @@ -0,0 +1,25 @@ +import type { ButtonProps } from '@heroui/react' +import { Button, cn } from '@heroui/react' +import { PlusIcon } from 'lucide-react' +import type { FC } from 'react' + +interface Props extends ButtonProps { + children: React.ReactNode +} + +const AddButton: FC = ({ children, className, ...props }) => { + return ( + + ) +} + +export default AddButton diff --git a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx index 56d12a925b..45065b0b0d 100644 --- a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx @@ -1,11 +1,14 @@ -import { Button, Chip, cn } from '@heroui/react' +import { cn } from '@heroui/react' import { DeleteIcon, EditIcon } from '@renderer/components/Icons' import { useSessions } from '@renderer/hooks/agents/useSessions' +import { useSettings } from '@renderer/hooks/useSettings' import AgentSettingsPopup from '@renderer/pages/settings/AgentSettings/AgentSettingsPopup' import { AgentLabel } from '@renderer/pages/settings/AgentSettings/shared' +import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import type { AgentEntity } from '@renderer/types' import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu' -import { type FC, memo } from 'react' +import { Bot } from 'lucide-react' +import { type FC, memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' // const logger = loggerService.withContext('AgentItem') @@ -20,81 +23,100 @@ interface AgentItemProps { const AgentItem: FC = ({ agent, isActive, onDelete, onPress }) => { const { t } = useTranslation() const { sessions } = useSessions(agent.id) + const { clickAssistantToShowTopic, topicPosition } = useSettings() + + const handlePress = useCallback(() => { + // Show session sidebar if setting is enabled (reusing the assistant setting for consistency) + if (clickAssistantToShowTopic) { + if (topicPosition === 'left') { + EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR) + } + } + onPress() + }, [clickAssistantToShowTopic, topicPosition, onPress]) return ( - <> - - - - + + + + + - {isActive && ( - - {sessions.length} - - )} - - - - - { - // onOpen() - await AgentSettingsPopup.show({ - agentId: agent.id - }) - }}> - - {t('common.edit')} - - { - window.modal.confirm({ - title: t('agent.delete.title'), - content: t('agent.delete.content'), - centered: true, - okButtonProps: { danger: true }, - onOk: () => onDelete(agent) - }) - }}> - - {t('common.delete')} - - - - {/* */} - + + + + {isActive ? {sessions.length} : } + + + + + AgentSettingsPopup.show({ agentId: agent.id })}> + + {t('common.edit')} + + { + window.modal.confirm({ + title: t('agent.delete.title'), + content: t('agent.delete.content'), + centered: true, + okButtonProps: { danger: true }, + onOk: () => onDelete(agent) + }) + }}> + + {t('common.delete')} + + + ) } -const ButtonContainer: React.FC> = ({ className, children, ...props }) => ( - + )} + {...props} + /> ) -const AssistantNameRow: React.FC> = ({ className, ...props }) => ( +export const AssistantNameRow: React.FC> = ({ className, ...props }) => (
+) + +export const AgentNameWrapper: React.FC> = ({ className, ...props }) => ( +
+) + +export const MenuButton: React.FC> = ({ className, ...props }) => ( +
+) + +export const SessionCount: React.FC> = ({ className, ...props }) => ( +
) diff --git a/src/renderer/src/pages/home/Tabs/components/AgentSection.tsx b/src/renderer/src/pages/home/Tabs/components/AgentSection.tsx deleted file mode 100644 index 14b51e78d8..0000000000 --- a/src/renderer/src/pages/home/Tabs/components/AgentSection.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Alert } from '@heroui/react' -import { useRuntime } from '@renderer/hooks/useRuntime' -import { useSettings } from '@renderer/hooks/useSettings' -import { useAppDispatch } from '@renderer/store' -import { addIknowAction } from '@renderer/store/runtime' -import { useTranslation } from 'react-i18next' - -import { Agents } from './Agents' -import { SectionName } from './SectionName' - -const ALERT_KEY = 'enable_api_server_to_use_agent' - -export const AgentSection = () => { - const { t } = useTranslation() - const { apiServer } = useSettings() - const { iknow } = useRuntime() - const dispatch = useAppDispatch() - - if (!apiServer.enabled) { - if (iknow[ALERT_KEY]) return null - return ( - { - dispatch(addIknowAction(ALERT_KEY)) - }} - /> - ) - } - - return ( -
- - -
- ) -} diff --git a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx index 0d8d59f064..2bc28d5f84 100644 --- a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx @@ -1,4 +1,5 @@ import { usePreference } from '@data/hooks/usePreference' +import { cn } from '@heroui/react' import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import EmojiIcon from '@renderer/components/EmojiIcon' import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons' @@ -30,10 +31,9 @@ import { Tag, Tags } from 'lucide-react' -import type { FC } from 'react' +import type { FC, PropsWithChildren } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import styled from 'styled-components' import * as tinyPinyin from 'tiny-pinyin' import AssistantTagsPopup from './AssistantTagsPopup' @@ -49,6 +49,8 @@ interface AssistantItemProps { copyAssistant: (assistant: Assistant) => void onTagClick?: (tag: string) => void handleSortByChange?: (sortType: AssistantTabSortType) => void + sortByPinyinAsc?: () => void + sortByPinyinDesc?: () => void } const AssistantItem: FC = ({ @@ -59,7 +61,9 @@ const AssistantItem: FC = ({ onDelete, addPreset, copyAssistant, - handleSortByChange + handleSortByChange, + sortByPinyinAsc: externalSortByPinyinAsc, + sortByPinyinDesc: externalSortByPinyinDesc }) => { const [assistantIconType, setAssistantIconType] = usePreference('assistant.icon_type') const [clickAssistantToShowTopic] = usePreference('assistant.click_to_show_topic') @@ -84,14 +88,19 @@ const AssistantItem: FC = ({ setIsPending(hasPending) }, [isActive, assistant.topics]) - const sortByPinyinAsc = useCallback(() => { + // Local sort functions + const localSortByPinyinAsc = useCallback(() => { updateAssistants(sortAssistantsByPinyin(assistants, true)) }, [assistants, updateAssistants]) - const sortByPinyinDesc = useCallback(() => { + const localSortByPinyinDesc = useCallback(() => { updateAssistants(sortAssistantsByPinyin(assistants, false)) }, [assistants, updateAssistants]) + // Use external sort functions if provided, otherwise use local ones + const sortByPinyinAsc = externalSortByPinyinAsc || localSortByPinyinAsc + const sortByPinyinDesc = externalSortByPinyinDesc || localSortByPinyinDesc + const menuItems = useMemo( () => getMenuItems({ @@ -151,7 +160,7 @@ const AssistantItem: FC = ({ menu={{ items: menuItems }} trigger={['contextMenu']} popupRender={(menu) =>
e.stopPropagation()}>{menu}
}> - + {assistantIconType === 'model' ? ( >) => ( +
+ {children} +
+) - &:hover { - background-color: var(--color-list-item-hover); - } - &.active { - background-color: var(--color-list-item); - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - } -` +const AssistantNameRow = ({ + children, + className, + ...props +}: PropsWithChildren<{} & React.HTMLAttributes>) => ( +
+ {children} +
+) -const AssistantNameRow = styled.div` - color: var(--color-text); - font-size: 13px; - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; -` +const AssistantName = ({ + children, + className, + ...props +}: PropsWithChildren<{} & React.HTMLAttributes>) => ( +
+ {children} +
+) -const AssistantName = styled.div` - font-size: 13px; -` +const MenuButton = ({ + children, + className, + ...props +}: PropsWithChildren<{} & React.HTMLAttributes>) => ( +
+ {children} +
+) -const MenuButton = styled.div` - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - min-width: 22px; - height: 22px; - min-height: 22px; - border-radius: 11px; - position: absolute; - background-color: var(--color-background); - right: 9px; - top: 6px; - padding: 0 5px; - border: 0.5px solid var(--color-border); -` - -const TopicCount = styled.div` - color: var(--color-text); - font-size: 10px; - border-radius: 10px; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; -` +const TopicCount = ({ + children, + className, + ...props +}: PropsWithChildren<{} & React.HTMLAttributes>) => ( +
+ {children} +
+) export default memo(AssistantItem) diff --git a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx index f443352bf7..7b2c949f16 100644 --- a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx @@ -1,4 +1,4 @@ -import { Alert, Button, Spinner } from '@heroui/react' +import { Alert, Spinner } from '@heroui/react' import { DynamicVirtualList } from '@renderer/components/VirtualList' import { useAgent } from '@renderer/hooks/agents/useAgent' import { useSessions } from '@renderer/hooks/agents/useSessions' @@ -13,10 +13,10 @@ import { import type { CreateSessionForm } from '@renderer/types' import { buildAgentSessionTopicId } from '@renderer/utils/agentSession' import { AnimatePresence, motion } from 'framer-motion' -import { Plus } from 'lucide-react' import { memo, useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' +import AddButton from './AddButton' import SessionItem from './SessionItem' // const logger = loggerService.withContext('SessionsTab') @@ -115,12 +115,9 @@ const Sessions: React.FC = ({ agentId }) => { transition={{ duration: 0.3 }} className="sessions-tab flex h-full w-full flex-col p-2"> - + {/* h-9 */} diff --git a/src/renderer/src/pages/home/Tabs/components/TagGroup.tsx b/src/renderer/src/pages/home/Tabs/components/TagGroup.tsx new file mode 100644 index 0000000000..85cbb43b69 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/components/TagGroup.tsx @@ -0,0 +1,63 @@ +import { DownOutlined, RightOutlined } from '@ant-design/icons' +import { Tooltip } from '@cherrystudio/ui' +import { cn } from '@heroui/react' +import type { FC, ReactNode } from 'react' + +interface TagGroupProps { + tag: string + isCollapsed: boolean + onToggle: (tag: string) => void + showTitle?: boolean + children: ReactNode +} + +export const TagGroup: FC = ({ tag, isCollapsed, onToggle, showTitle = true, children }) => { + return ( + + {showTitle && ( + onToggle(tag)}> + + + {isCollapsed ? ( + + ) : ( + + )} + {tag} + + + + + )} + {!isCollapsed &&
{children}
} +
+ ) +} + +const TagsContainer: FC> = ({ children, ...props }) => ( +
+ {children} +
+) + +const GroupTitle: FC> = ({ children, ...props }) => ( +
+ {children} +
+) + +const GroupTitleName: FC> = ({ children, ...props }) => ( +
+ {children} +
+) + +const GroupTitleDivider: FC> = (props) => ( +
+) diff --git a/src/renderer/src/pages/home/Tabs/components/Topics.tsx b/src/renderer/src/pages/home/Tabs/components/Topics.tsx index db1605dc71..d8754f34b6 100644 --- a/src/renderer/src/pages/home/Tabs/components/Topics.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Topics.tsx @@ -42,7 +42,6 @@ import { PackagePlus, PinIcon, PinOffIcon, - PlusIcon, Save, Sparkles, UploadIcon, @@ -52,6 +51,9 @@ import { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } f import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import styled from 'styled-components' + +import AddButton from './AddButton' + interface Props { assistant: Assistant activeTopic: Topic @@ -505,13 +507,12 @@ export const Topics: React.FC = ({ assistant: _assistant, activeTopic, se className="topics-tab" list={sortedTopics} onUpdate={updateTopics} - style={{ height: '100%', padding: '13px 0 10px 10px' }} + style={{ height: '100%', padding: '11px 0 10px 10px' }} itemContainerStyle={{ paddingBottom: '8px' }} header={ - EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)}> - + EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)} className="mb-2"> {t('chat.add.topic.title')} - + }> {(topic) => { const isActive = topic.id === activeTopic?.id @@ -748,31 +749,6 @@ const FulfilledIndicator = styled.div.attrs({ background-color: var(--color-status-success); ` -const AddTopicButton = styled.div` - display: flex; - align-items: center; - gap: 6px; - width: calc(100% - 10px); - padding: 7px 12px; - margin-bottom: 8px; - background: transparent; - color: var(--color-text-2); - font-size: 13px; - border-radius: var(--list-item-border-radius); - cursor: pointer; - transition: all 0.2s; - margin-top: -5px; - - &:hover { - background-color: var(--color-list-item-hover); - color: var(--color-text-1); - } - - .anticon { - font-size: 12px; - } -` - const TopicPromptText = styled.div` color: var(--color-text-2); font-size: 12px; diff --git a/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx b/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx new file mode 100644 index 0000000000..363536e8f7 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx @@ -0,0 +1,62 @@ +import { Button, Popover, PopoverContent, PopoverTrigger } from '@heroui/react' +import { AgentModal } from '@renderer/components/Popups/agent/AgentModal' +import { Bot, MessageSquare } from 'lucide-react' +import type { FC } from 'react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +import AddButton from './AddButton' + +interface UnifiedAddButtonProps { + onCreateAssistant: () => void +} + +const UnifiedAddButton: FC = ({ onCreateAssistant }) => { + const { t } = useTranslation() + const [isPopoverOpen, setIsPopoverOpen] = useState(false) + const [isAgentModalOpen, setIsAgentModalOpen] = useState(false) + + const handleAddAssistant = () => { + setIsPopoverOpen(false) + onCreateAssistant() + } + + const handleAddAgent = () => { + setIsPopoverOpen(false) + setIsAgentModalOpen(true) + } + + return ( +
+ + + {t('chat.add.assistant.title')} + + +
+ + +
+
+
+ + setIsAgentModalOpen(false)} /> +
+ ) +} + +export default UnifiedAddButton diff --git a/src/renderer/src/pages/home/Tabs/components/UnifiedList.tsx b/src/renderer/src/pages/home/Tabs/components/UnifiedList.tsx new file mode 100644 index 0000000000..14eddd69c1 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/components/UnifiedList.tsx @@ -0,0 +1,110 @@ +import { DraggableList } from '@renderer/components/DraggableList' +import type { Assistant } from '@renderer/types' +import type { AssistantTabSortType } from '@shared/data/preference/preferenceTypes' +import type { FC } from 'react' +import { useCallback } from 'react' + +import type { UnifiedItem } from '../hooks/useUnifiedItems' +import AgentItem from './AgentItem' +import AssistantItem from './AssistantItem' + +interface UnifiedListProps { + items: UnifiedItem[] + activeAssistantId: string + activeAgentId: string | null + sortBy: AssistantTabSortType + onReorder: (newList: UnifiedItem[]) => void + onDragStart: () => void + onDragEnd: () => void + onAssistantSwitch: (assistant: Assistant) => void + onAssistantDelete: (assistant: Assistant) => void + onAgentDelete: (agentId: string) => void + onAgentPress: (agentId: string) => void + addPreset: (assistant: Assistant) => void + copyAssistant: (assistant: Assistant) => void + onCreateDefaultAssistant: () => void + handleSortByChange: (sortType: AssistantTabSortType) => void + sortByPinyinAsc: () => void + sortByPinyinDesc: () => void +} + +export const UnifiedList: FC = (props) => { + const { + items, + activeAssistantId, + activeAgentId, + sortBy, + onReorder, + onDragStart, + onDragEnd, + onAssistantSwitch, + onAssistantDelete, + onAgentDelete, + onAgentPress, + addPreset, + copyAssistant, + onCreateDefaultAssistant, + handleSortByChange, + sortByPinyinAsc, + sortByPinyinDesc + } = props + + const renderUnifiedItem = useCallback( + (item: UnifiedItem) => { + if (item.type === 'agent') { + return ( + onAgentDelete(item.data.id)} + onPress={() => onAgentPress(item.data.id)} + /> + ) + } else { + return ( + + ) + } + }, + [ + activeAgentId, + activeAssistantId, + sortBy, + onAssistantSwitch, + onAssistantDelete, + onAgentDelete, + onAgentPress, + addPreset, + copyAssistant, + onCreateDefaultAssistant, + handleSortByChange, + sortByPinyinAsc, + sortByPinyinDesc + ] + ) + + return ( + `${item.type}-${item.data.id}`} + onUpdate={onReorder} + onDragStart={onDragStart} + onDragEnd={onDragEnd}> + {renderUnifiedItem} + + ) +} diff --git a/src/renderer/src/pages/home/Tabs/components/UnifiedTagGroups.tsx b/src/renderer/src/pages/home/Tabs/components/UnifiedTagGroups.tsx new file mode 100644 index 0000000000..0a2f2d2662 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/components/UnifiedTagGroups.tsx @@ -0,0 +1,134 @@ +import { DraggableList } from '@renderer/components/DraggableList' +import type { Assistant } from '@renderer/types' +import type { AssistantTabSortType } from '@shared/data/preference/preferenceTypes' +import type { FC } from 'react' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' + +import type { UnifiedItem } from '../hooks/useUnifiedItems' +import AgentItem from './AgentItem' +import AssistantItem from './AssistantItem' +import { TagGroup } from './TagGroup' + +interface GroupedItems { + tag: string + items: UnifiedItem[] +} + +interface UnifiedTagGroupsProps { + groupedItems: GroupedItems[] + activeAssistantId: string + activeAgentId: string | null + sortBy: AssistantTabSortType + collapsedTags: Record + onGroupReorder: (tag: string, newList: UnifiedItem[]) => void + onDragStart: () => void + onDragEnd: () => void + onToggleTagCollapse: (tag: string) => void + onAssistantSwitch: (assistant: Assistant) => void + onAssistantDelete: (assistant: Assistant) => void + onAgentDelete: (agentId: string) => void + onAgentPress: (agentId: string) => void + addPreset: (assistant: Assistant) => void + copyAssistant: (assistant: Assistant) => void + onCreateDefaultAssistant: () => void + handleSortByChange: (sortType: AssistantTabSortType) => void + sortByPinyinAsc: () => void + sortByPinyinDesc: () => void +} + +export const UnifiedTagGroups: FC = (props) => { + const { + groupedItems, + activeAssistantId, + activeAgentId, + sortBy, + collapsedTags, + onGroupReorder, + onDragStart, + onDragEnd, + onToggleTagCollapse, + onAssistantSwitch, + onAssistantDelete, + onAgentDelete, + onAgentPress, + addPreset, + copyAssistant, + onCreateDefaultAssistant, + handleSortByChange, + sortByPinyinAsc, + sortByPinyinDesc + } = props + + const { t } = useTranslation() + + const renderUnifiedItem = useCallback( + (item: UnifiedItem) => { + if (item.type === 'agent') { + return ( + onAgentDelete(item.data.id)} + onPress={() => onAgentPress(item.data.id)} + /> + ) + } else { + return ( + + ) + } + }, + [ + activeAgentId, + activeAssistantId, + sortBy, + onAssistantSwitch, + onAssistantDelete, + onAgentDelete, + onAgentPress, + addPreset, + copyAssistant, + onCreateDefaultAssistant, + handleSortByChange, + sortByPinyinAsc, + sortByPinyinDesc + ] + ) + + return ( +
+ {groupedItems.map((group) => ( + + `${item.type}-${item.data.id}`} + onUpdate={(newList) => onGroupReorder(group.tag, newList)} + onDragStart={onDragStart} + onDragEnd={onDragEnd}> + {renderUnifiedItem} + + + ))} +
+ ) +} diff --git a/src/renderer/src/pages/home/Tabs/hooks/useActiveAgent.ts b/src/renderer/src/pages/home/Tabs/hooks/useActiveAgent.ts new file mode 100644 index 0000000000..8f65af437d --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/hooks/useActiveAgent.ts @@ -0,0 +1,19 @@ +import { useAgentSessionInitializer } from '@renderer/hooks/agents/useAgentSessionInitializer' +import { useAppDispatch } from '@renderer/store' +import { setActiveAgentId as setActiveAgentIdAction } from '@renderer/store/runtime' +import { useCallback } from 'react' + +export const useActiveAgent = () => { + const dispatch = useAppDispatch() + const { initializeAgentSession } = useAgentSessionInitializer() + + const setActiveAgentId = useCallback( + async (id: string) => { + dispatch(setActiveAgentIdAction(id)) + await initializeAgentSession(id) + }, + [dispatch, initializeAgentSession] + ) + + return { setActiveAgentId } +} diff --git a/src/renderer/src/pages/home/Tabs/hooks/useUnifiedGrouping.ts b/src/renderer/src/pages/home/Tabs/hooks/useUnifiedGrouping.ts new file mode 100644 index 0000000000..d416a09838 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/hooks/useUnifiedGrouping.ts @@ -0,0 +1,140 @@ +import { useAppDispatch } from '@renderer/store' +import { setUnifiedListOrder } from '@renderer/store/assistants' +import type { AgentEntity, Assistant } from '@renderer/types' +import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' + +import type { UnifiedItem } from './useUnifiedItems' + +interface UseUnifiedGroupingOptions { + unifiedItems: UnifiedItem[] + assistants: Assistant[] + agents: AgentEntity[] + apiServerEnabled: boolean + agentsLoading: boolean + agentsError: Error | null + updateAssistants: (assistants: Assistant[]) => void +} + +export const useUnifiedGrouping = (options: UseUnifiedGroupingOptions) => { + const { unifiedItems, assistants, agents, apiServerEnabled, agentsLoading, agentsError, updateAssistants } = options + const { t } = useTranslation() + const dispatch = useAppDispatch() + + // Group unified items by tags + const groupedUnifiedItems = useMemo(() => { + const groups = new Map() + + unifiedItems.forEach((item) => { + if (item.type === 'agent') { + // Agents go to untagged group + const groupKey = t('assistants.tags.untagged') + if (!groups.has(groupKey)) { + groups.set(groupKey, []) + } + groups.get(groupKey)!.push(item) + } else { + // Assistants use their tags + const tags = item.data.tags?.length ? item.data.tags : [t('assistants.tags.untagged')] + tags.forEach((tag) => { + if (!groups.has(tag)) { + groups.set(tag, []) + } + groups.get(tag)!.push(item) + }) + } + }) + + // Sort groups: untagged first, then tagged groups + const untaggedKey = t('assistants.tags.untagged') + const sortedGroups = Array.from(groups.entries()).sort(([tagA], [tagB]) => { + if (tagA === untaggedKey) return -1 + if (tagB === untaggedKey) return 1 + return 0 + }) + + return sortedGroups.map(([tag, items]) => ({ tag, items })) + }, [unifiedItems, t]) + + const handleUnifiedGroupReorder = useCallback( + (tag: string, newGroupList: UnifiedItem[]) => { + // Extract only assistants from the new list for updating + const newAssistants = newGroupList.filter((item) => item.type === 'assistant').map((item) => item.data) + + // Update assistants state + let insertIndex = 0 + const updatedAssistants = assistants.map((a) => { + const tags = a.tags?.length ? a.tags : [t('assistants.tags.untagged')] + if (tags.includes(tag)) { + const replaced = newAssistants[insertIndex] + insertIndex += 1 + return replaced || a + } + return a + }) + updateAssistants(updatedAssistants) + + // Rebuild unified order and save to Redux + const newUnifiedItems: UnifiedItem[] = [] + const availableAgents = new Map() + const availableAssistants = new Map() + + if (apiServerEnabled && !agentsLoading && !agentsError) { + agents.forEach((agent) => availableAgents.set(agent.id, agent)) + } + updatedAssistants.forEach((assistant) => availableAssistants.set(assistant.id, assistant)) + + // Reconstruct order based on current groupedUnifiedItems structure + groupedUnifiedItems.forEach((group) => { + if (group.tag === tag) { + // Use the new group list for this tag + newGroupList.forEach((item) => { + newUnifiedItems.push(item) + if (item.type === 'agent') { + availableAgents.delete(item.data.id) + } else { + availableAssistants.delete(item.data.id) + } + }) + } else { + // Keep existing order for other tags + group.items.forEach((item) => { + newUnifiedItems.push(item) + if (item.type === 'agent') { + availableAgents.delete(item.data.id) + } else { + availableAssistants.delete(item.data.id) + } + }) + } + }) + + // Add any remaining items + availableAgents.forEach((agent) => newUnifiedItems.push({ type: 'agent', data: agent })) + availableAssistants.forEach((assistant) => newUnifiedItems.push({ type: 'assistant', data: assistant })) + + // Save to Redux + const orderToSave = newUnifiedItems.map((item) => ({ + type: item.type, + id: item.data.id + })) + dispatch(setUnifiedListOrder(orderToSave)) + }, + [ + assistants, + t, + updateAssistants, + apiServerEnabled, + agentsLoading, + agentsError, + agents, + groupedUnifiedItems, + dispatch + ] + ) + + return { + groupedUnifiedItems, + handleUnifiedGroupReorder + } +} diff --git a/src/renderer/src/pages/home/Tabs/hooks/useUnifiedItems.ts b/src/renderer/src/pages/home/Tabs/hooks/useUnifiedItems.ts new file mode 100644 index 0000000000..32522a5823 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/hooks/useUnifiedItems.ts @@ -0,0 +1,73 @@ +import { useAppDispatch, useAppSelector } from '@renderer/store' +import { setUnifiedListOrder } from '@renderer/store/assistants' +import type { AgentEntity, Assistant } from '@renderer/types' +import { useCallback, useMemo } from 'react' + +export type UnifiedItem = { type: 'agent'; data: AgentEntity } | { type: 'assistant'; data: Assistant } + +interface UseUnifiedItemsOptions { + agents: AgentEntity[] + assistants: Assistant[] + apiServerEnabled: boolean + agentsLoading: boolean + agentsError: Error | null + updateAssistants: (assistants: Assistant[]) => void +} + +export const useUnifiedItems = (options: UseUnifiedItemsOptions) => { + const { agents, assistants, apiServerEnabled, agentsLoading, agentsError, updateAssistants } = options + const dispatch = useAppDispatch() + const unifiedListOrder = useAppSelector((state) => state.assistants.unifiedListOrder || []) + + // Create unified items list (agents + assistants) with saved order + const unifiedItems = useMemo(() => { + const items: UnifiedItem[] = [] + + // Collect all available items + const availableAgents = new Map() + const availableAssistants = new Map() + + if (apiServerEnabled && !agentsLoading && !agentsError) { + agents.forEach((agent) => availableAgents.set(agent.id, agent)) + } + assistants.forEach((assistant) => availableAssistants.set(assistant.id, assistant)) + + // Apply saved order + unifiedListOrder.forEach((item) => { + if (item.type === 'agent' && availableAgents.has(item.id)) { + items.push({ type: 'agent', data: availableAgents.get(item.id)! }) + availableAgents.delete(item.id) + } else if (item.type === 'assistant' && availableAssistants.has(item.id)) { + items.push({ type: 'assistant', data: availableAssistants.get(item.id)! }) + availableAssistants.delete(item.id) + } + }) + + // Add new items (not in saved order) to the end + availableAgents.forEach((agent) => items.push({ type: 'agent', data: agent })) + availableAssistants.forEach((assistant) => items.push({ type: 'assistant', data: assistant })) + + return items + }, [agents, assistants, apiServerEnabled, agentsLoading, agentsError, unifiedListOrder]) + + const handleUnifiedListReorder = useCallback( + (newList: UnifiedItem[]) => { + // Save the unified order to Redux + const orderToSave = newList.map((item) => ({ + type: item.type, + id: item.data.id + })) + dispatch(setUnifiedListOrder(orderToSave)) + + // Extract and update assistants order + const newAssistants = newList.filter((item) => item.type === 'assistant').map((item) => item.data) + updateAssistants(newAssistants) + }, + [dispatch, updateAssistants] + ) + + return { + unifiedItems, + handleUnifiedListReorder + } +} diff --git a/src/renderer/src/pages/home/Tabs/hooks/useUnifiedSorting.ts b/src/renderer/src/pages/home/Tabs/hooks/useUnifiedSorting.ts new file mode 100644 index 0000000000..533427813f --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/hooks/useUnifiedSorting.ts @@ -0,0 +1,56 @@ +import { useAppDispatch } from '@renderer/store' +import { setUnifiedListOrder } from '@renderer/store/assistants' +import type { Assistant } from '@renderer/types' +import { useCallback } from 'react' +import * as tinyPinyin from 'tiny-pinyin' + +import type { UnifiedItem } from './useUnifiedItems' + +interface UseUnifiedSortingOptions { + unifiedItems: UnifiedItem[] + updateAssistants: (assistants: Assistant[]) => void +} + +export const useUnifiedSorting = (options: UseUnifiedSortingOptions) => { + const { unifiedItems, updateAssistants } = options + const dispatch = useAppDispatch() + + const sortUnifiedItemsByPinyin = useCallback((items: UnifiedItem[], isAscending: boolean) => { + return [...items].sort((a, b) => { + const nameA = a.type === 'agent' ? a.data.name || a.data.id : a.data.name + const nameB = b.type === 'agent' ? b.data.name || b.data.id : b.data.name + const pinyinA = tinyPinyin.convertToPinyin(nameA, '', true) + const pinyinB = tinyPinyin.convertToPinyin(nameB, '', true) + return isAscending ? pinyinA.localeCompare(pinyinB) : pinyinB.localeCompare(pinyinA) + }) + }, []) + + const sortByPinyinAsc = useCallback(() => { + const sorted = sortUnifiedItemsByPinyin(unifiedItems, true) + const orderToSave = sorted.map((item) => ({ + type: item.type, + id: item.data.id + })) + dispatch(setUnifiedListOrder(orderToSave)) + // Also update assistants order + const newAssistants = sorted.filter((item) => item.type === 'assistant').map((item) => item.data) + updateAssistants(newAssistants) + }, [unifiedItems, sortUnifiedItemsByPinyin, dispatch, updateAssistants]) + + const sortByPinyinDesc = useCallback(() => { + const sorted = sortUnifiedItemsByPinyin(unifiedItems, false) + const orderToSave = sorted.map((item) => ({ + type: item.type, + id: item.data.id + })) + dispatch(setUnifiedListOrder(orderToSave)) + // Also update assistants order + const newAssistants = sorted.filter((item) => item.type === 'assistant').map((item) => item.data) + updateAssistants(newAssistants) + }, [unifiedItems, sortUnifiedItemsByPinyin, dispatch, updateAssistants]) + + return { + sortByPinyinAsc, + sortByPinyinDesc + } +} diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx index 51a50a5f17..6a0f8d420a 100644 --- a/src/renderer/src/pages/home/Tabs/index.tsx +++ b/src/renderer/src/pages/home/Tabs/index.tsx @@ -1,5 +1,7 @@ import { usePreference } from '@data/hooks/usePreference' import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup' +import { useAgent } from '@renderer/hooks/agents/useAgent' +import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant' import { useNavbarPosition } from '@renderer/hooks/useNavbar' import { useRuntime } from '@renderer/hooks/useRuntime' @@ -13,6 +15,7 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +import AgentSettingsTab from './AgentSettingsTab' import Assistants from './AssistantsTab' import Settings from './SettingsTab' import Topics from './TopicsTab' @@ -43,11 +46,12 @@ const HomeTabs: FC = ({ const { defaultAssistant } = useDefaultAssistant() const { toggleShowTopics } = useShowTopics() const { isLeftNavbar } = useNavbarPosition() - const { t } = useTranslation() - const { chat } = useRuntime() - const { activeTopicOrSession } = chat + const { activeTopicOrSession, activeAgentId } = chat + const { agent } = useAgent(activeAgentId) + const { updateAgent } = useUpdateAgent() + const isSessionView = activeTopicOrSession === 'session' const isTopicView = activeTopicOrSession === 'topic' @@ -63,7 +67,6 @@ const HomeTabs: FC = ({ } const showTab = position === 'left' && topicPosition === 'left' - const shouldShowSettingsTab = !isSessionView const onCreateAssistant = async () => { const assistant = await AddAssistantPopup.show() @@ -106,12 +109,6 @@ const HomeTabs: FC = ({ } }, [position, tab, topicPosition, forceToSeeAllTab]) - useEffect(() => { - if (activeTopicOrSession === 'session' && tab === 'settings') { - setTab('topic') - } - }, [activeTopicOrSession, tab]) - return ( = ({ {t('assistants.abbr')} setTab('topic')}> - {isTopicView ? t('common.topics') : t('agent.session.label_other')} + {t('common.topics')} + + setTab('settings')}> + {t('settings.title')} - {shouldShowSettingsTab && ( - setTab('settings')}> - {t('settings.title')} - - )} )} @@ -160,7 +155,8 @@ const HomeTabs: FC = ({ position={position} /> )} - {tab === 'settings' && shouldShowSettingsTab && } + {tab === 'settings' && isTopicView && } + {tab === 'settings' && isSessionView && } ) @@ -216,7 +212,7 @@ const CustomTabs = styled.div` const TabItem = styled.button<{ active: boolean }>` flex: 1; - height: 32px; + height: 30px; border: none; background: transparent; color: ${(props) => (props.active ? 'var(--color-text)' : 'var(--color-text-secondary)')}; @@ -241,7 +237,7 @@ const TabItem = styled.button<{ active: boolean }>` &::after { content: ''; position: absolute; - bottom: -9px; + bottom: -8px; left: 50%; transform: translateX(-50%); width: ${(props) => (props.active ? '30px' : '0')}; diff --git a/src/renderer/src/pages/minapps/components/WebviewSearch.tsx b/src/renderer/src/pages/minapps/components/WebviewSearch.tsx index 633013aa6a..a0de23ba96 100644 --- a/src/renderer/src/pages/minapps/components/WebviewSearch.tsx +++ b/src/renderer/src/pages/minapps/components/WebviewSearch.tsx @@ -22,11 +22,11 @@ const WebviewSearch: FC = ({ webviewRef, isWebviewReady, app const [query, setQuery] = useState('') const [matchCount, setMatchCount] = useState(0) const [activeIndex, setActiveIndex] = useState(0) - const [currentWebview, setCurrentWebview] = useState(null) const inputRef = useRef(null) const focusFrameRef = useRef(null) const lastAppIdRef = useRef(appId) const attachedWebviewRef = useRef(null) + const activeWebview = webviewRef.current ?? null const focusInput = useCallback(() => { if (focusFrameRef.current !== null) { @@ -119,34 +119,66 @@ const WebviewSearch: FC = ({ webviewRef, isWebviewReady, app }, [performSearch, query]) useEffect(() => { - const nextWebview = webviewRef.current ?? null - if (currentWebview === nextWebview) return - setCurrentWebview(nextWebview) - }, [webviewRef, currentWebview]) - - useEffect(() => { - const target = currentWebview - if (!target) { - attachedWebviewRef.current = null + attachedWebviewRef.current = activeWebview + if (!activeWebview) { return } const handle = handleFoundInPage - attachedWebviewRef.current = target - target.addEventListener('found-in-page', handle) + activeWebview.addEventListener('found-in-page', handle) return () => { - target.removeEventListener('found-in-page', handle) - if (attachedWebviewRef.current === target) { + activeWebview.removeEventListener('found-in-page', handle) + if (attachedWebviewRef.current === activeWebview) { try { - target.stopFindInPage('clearSelection') + activeWebview.stopFindInPage('clearSelection') } catch (error) { logger.error('stopFindInPage failed', { error }) } attachedWebviewRef.current = null } } - }, [currentWebview, handleFoundInPage]) + }, [activeWebview, handleFoundInPage]) + + useEffect(() => { + if (!activeWebview) return + const onFindShortcut = window.api?.webview?.onFindShortcut + if (!onFindShortcut) return + + const webContentsId = activeWebview.getWebContentsId?.() + if (!webContentsId) { + logger.warn('WebviewSearch: missing webContentsId', { appId }) + return + } + + const unsubscribe = onFindShortcut(({ webviewId, key, control, meta, shift }) => { + if (webviewId !== webContentsId) return + + if ((control || meta) && key === 'f') { + openSearch() + return + } + + if (!isVisible) return + + if (key === 'escape') { + closeSearch() + return + } + + if (key === 'enter') { + if (shift) { + goToPrevious() + } else { + goToNext() + } + } + }) + + return () => { + unsubscribe?.() + } + }, [appId, activeWebview, closeSearch, goToNext, goToPrevious, isVisible, openSearch]) useEffect(() => { if (!isVisible) return @@ -160,7 +192,7 @@ const WebviewSearch: FC = ({ webviewRef, isWebviewReady, app return } performSearch(query) - }, [currentWebview, isVisible, performSearch, query]) + }, [activeWebview, isVisible, performSearch, query]) useEffect(() => { const handleKeydown = (event: KeyboardEvent) => { diff --git a/src/renderer/src/pages/minapps/components/__tests__/WebviewSearch.test.tsx b/src/renderer/src/pages/minapps/components/__tests__/WebviewSearch.test.tsx index 0f1985f6bb..50e8c60ca2 100644 --- a/src/renderer/src/pages/minapps/components/__tests__/WebviewSearch.test.tsx +++ b/src/renderer/src/pages/minapps/components/__tests__/WebviewSearch.test.tsx @@ -1,3 +1,4 @@ +import type { WebviewKeyEvent } from '@shared/config/types' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import type { WebviewTag } from 'electron' @@ -36,6 +37,7 @@ const createWebviewMock = () => { listeners.get(type)?.delete(listener) } ), + getWebContentsId: vi.fn(() => 1), findInPage: findInPageMock as unknown as WebviewTag['findInPage'], stopFindInPage: stopFindInPageMock as unknown as WebviewTag['stopFindInPage'] } as unknown as WebviewTag @@ -102,13 +104,34 @@ describe('WebviewSearch', () => { info: vi.fn(), addToast: vi.fn() } + let removeFindShortcutListenerMock: ReturnType + let onFindShortcutMock: ReturnType + const invokeLatestShortcut = (payload: WebviewKeyEvent) => { + const handler = onFindShortcutMock.mock.calls.at(-1)?.[0] as ((args: WebviewKeyEvent) => void) | undefined + if (!handler) { + throw new Error('Shortcut handler not registered') + } + act(() => { + handler(payload) + }) + } beforeEach(() => { + removeFindShortcutListenerMock = vi.fn() + onFindShortcutMock = vi.fn(() => removeFindShortcutListenerMock) + Object.assign(window as any, { + api: { + webview: { + onFindShortcut: onFindShortcutMock + } + } + }) Object.assign(window, { toast: toastMock }) }) afterEach(() => { vi.clearAllMocks() + Reflect.deleteProperty(window, 'api') }) it('opens the search overlay with keyboard shortcut', async () => { @@ -124,6 +147,47 @@ describe('WebviewSearch', () => { expect(screen.getByPlaceholderText('Search')).toBeInTheDocument() }) + it('opens the search overlay when webview shortcut is forwarded', async () => { + const { webview } = createWebviewMock() + const webviewRef = { current: webview } as React.RefObject + + render() + + await waitFor(() => { + expect(onFindShortcutMock).toHaveBeenCalled() + }) + + invokeLatestShortcut({ webviewId: 1, key: 'f', control: true, meta: false, shift: false, alt: false }) + + await waitFor(() => { + expect(screen.getByPlaceholderText('Search')).toBeInTheDocument() + }) + }) + + it('closes the search overlay when escape is forwarded from the webview', async () => { + const { webview } = createWebviewMock() + const webviewRef = { current: webview } as React.RefObject + + render() + + await waitFor(() => { + expect(onFindShortcutMock).toHaveBeenCalled() + }) + invokeLatestShortcut({ webviewId: 1, key: 'f', control: true, meta: false, shift: false, alt: false }) + await waitFor(() => { + expect(screen.getByPlaceholderText('Search')).toBeInTheDocument() + }) + + await waitFor(() => { + expect(onFindShortcutMock.mock.calls.length).toBeGreaterThanOrEqual(2) + }) + + invokeLatestShortcut({ webviewId: 1, key: 'escape', control: false, meta: false, shift: false, alt: false }) + await waitFor(() => { + expect(screen.queryByPlaceholderText('Search')).not.toBeInTheDocument() + }) + }) + it('performs searches and navigates between results', async () => { const { emit, findInPageMock, webview } = createWebviewMock() const webviewRef = { current: webview } as React.RefObject @@ -165,6 +229,45 @@ describe('WebviewSearch', () => { }) }) + it('navigates results when enter is forwarded from the webview', async () => { + const { findInPageMock, webview } = createWebviewMock() + const webviewRef = { current: webview } as React.RefObject + const user = userEvent.setup() + + render() + + await waitFor(() => { + expect(onFindShortcutMock).toHaveBeenCalled() + }) + invokeLatestShortcut({ webviewId: 1, key: 'f', control: true, meta: false, shift: false, alt: false }) + await waitFor(() => { + expect(screen.getByPlaceholderText('Search')).toBeInTheDocument() + }) + + await waitFor(() => { + expect(onFindShortcutMock.mock.calls.length).toBeGreaterThanOrEqual(2) + }) + + const input = screen.getByRole('textbox') + await user.type(input, 'Cherry') + + await waitFor(() => { + expect(findInPageMock).toHaveBeenCalledWith('Cherry', undefined) + }) + findInPageMock.mockClear() + + invokeLatestShortcut({ webviewId: 1, key: 'enter', control: false, meta: false, shift: false, alt: false }) + await waitFor(() => { + expect(findInPageMock).toHaveBeenCalledWith('Cherry', { forward: true, findNext: true }) + }) + + findInPageMock.mockClear() + invokeLatestShortcut({ webviewId: 1, key: 'enter', control: false, meta: false, shift: true, alt: false }) + await waitFor(() => { + expect(findInPageMock).toHaveBeenCalledWith('Cherry', { forward: false, findNext: true }) + }) + }) + it('clears search state when appId changes', async () => { const { findInPageMock, stopFindInPageMock, webview } = createWebviewMock() const webviewRef = { current: webview } as React.RefObject @@ -219,6 +322,7 @@ describe('WebviewSearch', () => { unmount() expect(stopFindInPageMock).toHaveBeenCalledWith('clearSelection') + expect(removeFindShortcutListenerMock).toHaveBeenCalled() }) it('ignores keyboard shortcut when webview is not ready', async () => { diff --git a/src/renderer/src/pages/paintings/SiliconPage.tsx b/src/renderer/src/pages/paintings/SiliconPage.tsx index f13282ad8b..359ed95617 100644 --- a/src/renderer/src/pages/paintings/SiliconPage.tsx +++ b/src/renderer/src/pages/paintings/SiliconPage.tsx @@ -14,6 +14,7 @@ import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navb import Scrollbar from '@renderer/components/Scrollbar' import TranslateButton from '@renderer/components/TranslateButton' import { isMac } from '@renderer/config/constant' +import { getProviderLogo } from '@renderer/config/providers' import { LanguagesEnum } from '@renderer/config/translate' import { useTheme } from '@renderer/context/ThemeProvider' import { usePaintings } from '@renderer/hooks/usePaintings' @@ -24,7 +25,7 @@ import FileManager from '@renderer/services/FileManager' import { translateText } from '@renderer/services/TranslateService' import type { FileMetadata, Painting } from '@renderer/types' import { getErrorMessage, uuid } from '@renderer/utils' -import { Input, InputNumber, Radio, Select, Slider } from 'antd' +import { Avatar, Input, InputNumber, Radio, Select, Slider } from 'antd' import TextArea from 'antd/es/input/TextArea' import type { FC } from 'react' import { useEffect, useRef, useState } from 'react' @@ -384,7 +385,16 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { {t('common.provider')} - + {providerOptions.map((provider) => ( + + + + {provider.label} + + + ))} + {t('common.model')} setName(value)} onBlur={() => { if (name !== base.name) { diff --git a/src/renderer/src/services/WebSearchService.ts b/src/renderer/src/services/WebSearchService.ts index f2e667fdf9..b20b0ede5d 100644 --- a/src/renderer/src/services/WebSearchService.ts +++ b/src/renderer/src/services/WebSearchService.ts @@ -22,7 +22,6 @@ import type { ExtractResults } from '@renderer/utils/extract' import { fetchWebContents } from '@renderer/utils/fetch' import { consolidateReferencesByUrl, selectReferences } from '@renderer/utils/websearch' import dayjs from 'dayjs' -import { LRUCache } from 'lru-cache' import { sliceByTokens } from 'tokenx' import { getKnowledgeBaseParams } from './KnowledgeService' @@ -32,7 +31,6 @@ const logger = loggerService.withContext('WebSearchService') interface RequestState { signal: AbortSignal | null - searchBase?: KnowledgeBase isPaused: boolean createdAt: number } @@ -49,16 +47,7 @@ class WebSearchService { isPaused = false // 管理不同请求的状态 - private requestStates = new LRUCache({ - max: 5, // 最多5个并发请求 - ttl: 1000 * 60 * 2, // 2分钟过期 - dispose: (requestState: RequestState, requestId: string) => { - if (!requestState.searchBase) return - window.api.knowledgeBase - .delete(removeSpecialCharactersForFileName(requestState.searchBase.id)) - .catch((error) => logger.warn(`Failed to cleanup search base for ${requestId}:`, error)) - } - }) + private requestStates = new Map() /** * 获取或创建单个请求的状态 @@ -212,7 +201,7 @@ class WebSearchService { } } /** - * 确保搜索压缩知识库存在并配置正确 + * 创建临时搜索知识库 */ private async ensureSearchBase( config: CompressionConfig, @@ -221,25 +210,13 @@ class WebSearchService { ): Promise { // requestId: eg: openai-responses-openai/gpt-5-timestamp-uuid const baseId = `websearch-compression-${requestId}` - const state = this.getRequestState(requestId) - - // 如果已存在且配置未变,直接复用 - if (state.searchBase && this.isConfigMatched(state.searchBase, config)) { - return state.searchBase - } - - // 清理旧的知识库 - if (state.searchBase) { - // 将requestId中的 '/' 映射为 '_' - await window.api.knowledgeBase.delete(removeSpecialCharactersForFileName(state.searchBase.id)) - } if (!config.embeddingModel) { throw new Error('Embedding model is required for RAG compression') } // 创建新的知识库 - state.searchBase = { + const searchBase: KnowledgeBase = { id: baseId, name: `WebSearch-RAG-${requestId}`, model: config.embeddingModel, @@ -252,25 +229,23 @@ class WebSearchService { version: 1 } - // 更新LRU cache - this.requestStates.set(requestId, state) - // 创建知识库 - const baseParams = getKnowledgeBaseParams(state.searchBase) + const baseParams = getKnowledgeBaseParams(searchBase) await window.api.knowledgeBase.create(baseParams) - return state.searchBase + return searchBase } /** - * 检查配置是否匹配 + * 清理临时搜索知识库 */ - private isConfigMatched(base: KnowledgeBase, config: CompressionConfig): boolean { - return ( - base.model.id === config.embeddingModel?.id && - base.rerankModel?.id === config.rerankModel?.id && - base.dimensions === config.embeddingDimensions - ) + private async cleanupSearchBase(searchBase: KnowledgeBase): Promise { + try { + await window.api.knowledgeBase.delete(removeSpecialCharactersForFileName(searchBase.id)) + logger.debug(`Cleaned up search base: ${searchBase.id}`) + } catch (error) { + logger.warn(`Failed to cleanup search base ${searchBase.id}:`, error as Error) + } } /** @@ -337,45 +312,50 @@ class WebSearchService { const searchBase = await this.ensureSearchBase(config, totalDocumentCount, requestId) logger.debug('Search base for RAG compression: ', searchBase) - // 1. 清空知识库 - const baseParams = getKnowledgeBaseParams(searchBase) - await window.api.knowledgeBase.reset(baseParams) + try { + // 1. 清空知识库 + const baseParams = getKnowledgeBaseParams(searchBase) + await window.api.knowledgeBase.reset(baseParams) - logger.debug('Search base parameters for RAG compression: ', baseParams) + logger.debug('Search base parameters for RAG compression: ', baseParams) - // 2. 顺序添加所有搜索结果到知识库 - // FIXME: 目前的知识库 add 不支持并发 - for (const result of rawResults) { - const item: KnowledgeItem & { sourceUrl?: string } = { - id: uuid(), - type: 'note', - content: result.content, - sourceUrl: result.url, // 设置 sourceUrl 用于映射 - created_at: Date.now(), - updated_at: Date.now(), - processingStatus: 'pending' + // 2. 顺序添加所有搜索结果到知识库 + // FIXME: 目前的知识库 add 不支持并发 + for (const result of rawResults) { + const item: KnowledgeItem & { sourceUrl?: string } = { + id: uuid(), + type: 'note', + content: result.content, + sourceUrl: result.url, // 设置 sourceUrl 用于映射 + created_at: Date.now(), + updated_at: Date.now(), + processingStatus: 'pending' + } + + await window.api.knowledgeBase.add({ + base: getKnowledgeBaseParams(searchBase), + item + }) } - await window.api.knowledgeBase.add({ - base: getKnowledgeBaseParams(searchBase), - item + // 3. 对知识库执行多问题搜索获取压缩结果 + const references = await this.querySearchBase(questions, searchBase) + + // 4. 使用 Round Robin 策略选择引用 + const selectedReferences = selectReferences(rawResults, references, totalDocumentCount) + + logger.verbose('With RAG, the number of search results:', { + raw: rawResults.length, + retrieved: references.length, + selected: selectedReferences.length }) + + // 5. 按 sourceUrl 分组并合并同源片段 + return consolidateReferencesByUrl(rawResults, selectedReferences) + } finally { + // 无论成功或失败都立即清理知识库 + await this.cleanupSearchBase(searchBase) } - - // 3. 对知识库执行多问题搜索获取压缩结果 - const references = await this.querySearchBase(questions, searchBase) - - // 4. 使用 Round Robin 策略选择引用 - const selectedReferences = selectReferences(rawResults, references, totalDocumentCount) - - logger.verbose('With RAG, the number of search results:', { - raw: rawResults.length, - retrieved: references.length, - selected: selectedReferences.length - }) - - // 5. 按 sourceUrl 分组并合并同源片段 - return consolidateReferencesByUrl(rawResults, selectedReferences) } /** diff --git a/src/renderer/src/store/assistants.ts b/src/renderer/src/store/assistants.ts index b9cb809ecf..e3db7c8197 100644 --- a/src/renderer/src/store/assistants.ts +++ b/src/renderer/src/store/assistants.ts @@ -14,6 +14,7 @@ export interface AssistantsState { tagsOrder: string[] collapsedTags: Record presets: AssistantPreset[] + unifiedListOrder: Array<{ type: 'agent' | 'assistant'; id: string }> } const initialState: AssistantsState = { @@ -21,7 +22,8 @@ const initialState: AssistantsState = { assistants: [getDefaultAssistant()], tagsOrder: [], collapsedTags: {}, - presets: [] + presets: [], + unifiedListOrder: [] } const assistantsSlice = createSlice({ @@ -97,6 +99,9 @@ const assistantsSlice = createSlice({ [tag]: !prev[tag] } }, + setUnifiedListOrder: (state, action: PayloadAction>) => { + state.unifiedListOrder = action.payload + }, addTopic: (state, action: PayloadAction<{ assistantId: string; topic: Topic }>) => { const topic = action.payload.topic topic.createdAt = topic.createdAt || new Date().toISOString() @@ -245,6 +250,7 @@ export const { setTagsOrder, updateAssistantSettings, updateTagCollapse, + setUnifiedListOrder, setAssistantPresets, addAssistantPreset, removeAssistantPreset, diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 7b5ccdc0df..151075799f 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -69,7 +69,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 160, + version: 162, blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index b8f5413dc6..08ba587736 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -71,6 +71,7 @@ function removeMiniAppIconsFromState(state: RootState) { function removeMiniAppFromState(state: RootState, id: string) { if (state.minapps) { + state.minapps.pinned = state.minapps.pinned.filter((app) => app.id !== id) state.minapps.enabled = state.minapps.enabled.filter((app) => app.id !== id) state.minapps.disabled = state.minapps.disabled.filter((app) => app.id !== id) } @@ -2600,16 +2601,30 @@ const migrateConfig = { return state } }, - '160': (state: RootState) => { + '161': (state: RootState) => { + try { + removeMiniAppFromState(state, 'nm-search') + removeMiniAppFromState(state, 'hika') + removeMiniAppFromState(state, 'hugging-chat') + addProvider(state, 'cherryin') + state.llm.providers = moveProvider(state.llm.providers, 'cherryin', 1) + return state + } catch (error) { + logger.error('migrate 161 error', error as Error) + return state + } + }, + '162': (state: RootState) => { try { // @ts-ignore if (state?.agents?.agents) { // @ts-ignore state.assistants.presets = [...state.agents.agents] - // @ts-ignore delete state.agents.agents + } + if (state.settings.sidebarIcons) { state.settings.sidebarIcons.visible = state.settings.sidebarIcons.visible.map((icon) => { // @ts-ignore return icon === 'agents' ? 'store' : icon @@ -2619,6 +2634,7 @@ const migrateConfig = { return icon === 'agents' ? 'store' : icon }) } + state.llm.providers.forEach((provider) => { if (provider.anthropicApiHost) { return @@ -2646,16 +2662,13 @@ const migrateConfig = { case 'new-api': provider.anthropicApiHost = 'http://localhost:3000' break - case 'cherryai': - provider.anthropicApiHost = 'https://api.cherry-ai.com' - break case 'grok': provider.anthropicApiHost = 'https://api.x.ai' } }) return state } catch (error) { - logger.error('migrate 160 error', error as Error) + logger.error('migrate 162 error', error as Error) return state } } diff --git a/src/renderer/src/trace/dataHandler/StreamHandler.ts b/src/renderer/src/trace/dataHandler/StreamHandler.ts index 35c610caa2..43d748ef1a 100644 --- a/src/renderer/src/trace/dataHandler/StreamHandler.ts +++ b/src/renderer/src/trace/dataHandler/StreamHandler.ts @@ -41,7 +41,7 @@ export class StreamHandler { this.usage.total_tokens += completionChunk.usage.total_tokens || 0 } context = chunk.choices - .map((choice) => { + ?.map((choice) => { if (!choice.delta) { return '' } else if ('reasoning_content' in choice.delta) { diff --git a/src/renderer/src/types/agent.ts b/src/renderer/src/types/agent.ts index e05f05a798..d3171e16d4 100644 --- a/src/renderer/src/types/agent.ts +++ b/src/renderer/src/types/agent.ts @@ -5,7 +5,7 @@ * WARNING: Any null value will be converted to undefined from api. */ import type { ModelMessage, TextStreamPart } from 'ai' -import { z } from 'zod' +import * as z from 'zod' import type { Message, MessageBlock } from './newMessage' diff --git a/src/renderer/src/types/apiModels.ts b/src/renderer/src/types/apiModels.ts index 412f772a5d..565eb9241a 100644 --- a/src/renderer/src/types/apiModels.ts +++ b/src/renderer/src/types/apiModels.ts @@ -1,5 +1,5 @@ import type { Model } from '@types' -import { z } from 'zod' +import * as z from 'zod' import { ProviderTypeSchema } from './provider' diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index bdddef19da..d0540a69b2 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -81,6 +81,7 @@ const ThinkModelTypes = [ 'gpt5', 'gpt5_codex', 'grok', + 'grok4_fast', 'gemini', 'gemini_pro', 'qwen', diff --git a/src/renderer/src/types/provider.ts b/src/renderer/src/types/provider.ts index 1152079fdf..424f9bdac5 100644 --- a/src/renderer/src/types/provider.ts +++ b/src/renderer/src/types/provider.ts @@ -1,5 +1,5 @@ import type { Model } from '@types' -import z from 'zod' +import * as z from 'zod' export const ProviderTypeSchema = z.enum([ 'openai', @@ -106,7 +106,7 @@ export type Provider = { } export const SystemProviderIds = { - // cherryin: 'cherryin', + cherryin: 'cherryin', silicon: 'silicon', aihubmix: 'aihubmix', ocoolai: 'ocoolai', diff --git a/src/renderer/src/types/tool.ts b/src/renderer/src/types/tool.ts index ad6f2727e1..c803c76fcb 100644 --- a/src/renderer/src/types/tool.ts +++ b/src/renderer/src/types/tool.ts @@ -1,4 +1,4 @@ -import { z } from 'zod' +import * as z from 'zod' export type ToolType = 'builtin' | 'provider' | 'mcp' diff --git a/src/renderer/src/utils/bocha.ts b/src/renderer/src/utils/bocha.ts index 45d83cd8bf..485a17cb76 100644 --- a/src/renderer/src/utils/bocha.ts +++ b/src/renderer/src/utils/bocha.ts @@ -1,4 +1,4 @@ -import { z } from 'zod' +import * as z from 'zod' export const freshnessOptions = ['oneDay', 'oneWeek', 'oneMonth', 'oneYear', 'noLimit'] as const diff --git a/src/renderer/src/utils/error.ts b/src/renderer/src/utils/error.ts index 5a8ee38c31..e4ebe6095f 100644 --- a/src/renderer/src/utils/error.ts +++ b/src/renderer/src/utils/error.ts @@ -13,7 +13,7 @@ import type { NoSuchToolError } from 'ai' import { InvalidToolInputError } from 'ai' import { type AxiosError, isAxiosError } from 'axios' import { t } from 'i18next' -import type { z } from 'zod' +import type * as z from 'zod' import { ZodError } from 'zod' import { parseJSON } from './json' diff --git a/src/renderer/src/utils/memory-prompts.ts b/src/renderer/src/utils/memory-prompts.ts index 3b4589c652..c704cbc1d7 100644 --- a/src/renderer/src/utils/memory-prompts.ts +++ b/src/renderer/src/utils/memory-prompts.ts @@ -1,4 +1,4 @@ -import { z } from 'zod' +import * as z from 'zod' // Define Zod schema for fact retrieval output export const FactRetrievalSchema = z.object({ diff --git a/yarn.lock b/yarn.lock index b6340b2f34..635ec39ec1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -74,112 +74,137 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/amazon-bedrock@npm:^3.0.29": - version: 3.0.29 - resolution: "@ai-sdk/amazon-bedrock@npm:3.0.29" +"@ai-sdk/amazon-bedrock@npm:^3.0.35": + version: 3.0.35 + resolution: "@ai-sdk/amazon-bedrock@npm:3.0.35" dependencies: - "@ai-sdk/anthropic": "npm:2.0.22" + "@ai-sdk/anthropic": "npm:2.0.27" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" "@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.1.8 - checksum: 10c0/7add02e6c13774943929bb5d568b3110f6badc6d95cb56c6d3011cafc45778e27c0133417dd7fe835e7f0b1ae7767c22a7d5e3d39f725e2aa44e2b6e47d95fb7 + checksum: 10c0/0e3e0ed1730fa6a14d8d7ca14b7823ec0b80c9d666435d97a505e7fb0c1818378343cdb647e3cc08d7f15d201cbeb04272c5128065f6cc6858b4404961eca761 languageName: node linkType: hard -"@ai-sdk/anthropic@npm:2.0.22, @ai-sdk/anthropic@npm:^2.0.22": - version: 2.0.22 - resolution: "@ai-sdk/anthropic@npm:2.0.22" +"@ai-sdk/anthropic@npm:2.0.27, @ai-sdk/anthropic@npm:^2.0.27": + version: 2.0.27 + resolution: "@ai-sdk/anthropic@npm:2.0.27" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/d922d2ff606b2429fb14c099628ba6734ef7c9b0e9225635f3faaf2d067362dea6ae0e920a35c05ccf15a01c59fef93ead5f147a9609dd3dd8c3ac18a3123b85 + checksum: 10c0/b568b3b8639af8ec7ea9b766061a4f18bcdef16f2bb12da3a4c4124c751bd6aab1b96dbe1a0eb8e38831d305871ce0787a6536d1a4d8a8ab8aaf03aca3e48e3f languageName: node linkType: hard -"@ai-sdk/azure@npm:^2.0.42": - version: 2.0.42 - resolution: "@ai-sdk/azure@npm:2.0.42" +"@ai-sdk/azure@npm:^2.0.49": + version: 2.0.49 + resolution: "@ai-sdk/azure@npm:2.0.49" dependencies: - "@ai-sdk/openai": "npm:2.0.42" + "@ai-sdk/openai": "npm:2.0.48" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/14d3d6edac691df57879a9a7efc46d5d00b6bde5b64cd62a67a7668455c341171119ae90a431e57ac37009bced19add50b3da26998376b7e56e080bc2c997c00 + checksum: 10c0/d4dc5a8e0cbe0cefc8db987c4a7b784a9898d40cc55ef38618c71eba7f40dbef77b754aec1d507559f643fed49e538ffe2b677b327f001a2efc0474f6b544ba9 languageName: node linkType: hard -"@ai-sdk/deepseek@npm:^1.0.20": - version: 1.0.20 - resolution: "@ai-sdk/deepseek@npm:1.0.20" +"@ai-sdk/deepseek@npm:^1.0.23": + version: 1.0.23 + resolution: "@ai-sdk/deepseek@npm:1.0.23" dependencies: - "@ai-sdk/openai-compatible": "npm:1.0.19" + "@ai-sdk/openai-compatible": "npm:1.0.22" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/e66ece8cf6371c2bac5436ed82cd1e2bb5c367fae6df60090f91cff62bf241f4df0abded99c33558013f8dc0bcc7d962f2126086eba8587ba929da50afd3d806 + checksum: 10c0/39736e9787420ce86d0f2ce6935ba51f2b721acfb4c0d2b77064a8b939cf22b0767a83b82a5c99efff1311080532a3aaa2f34804d7981133f671a050521ed197 languageName: node linkType: hard -"@ai-sdk/gateway@npm:1.0.32": - version: 1.0.32 - resolution: "@ai-sdk/gateway@npm:1.0.32" +"@ai-sdk/gateway@npm:1.0.39": + version: 1.0.39 + resolution: "@ai-sdk/gateway@npm:1.0.39" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" + "@vercel/oidc": "npm:3.0.2" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/82c98db6e4e8e235e1ff66410318ebe77cc1518ebf06d8d4757b4f30aaa3bf7075d3028816438551fef2f89e2d4c8c26e4efcd9913a06717aee1308dad3ddc30 + checksum: 10c0/1b6eedf12ac641c96a1eb75e48e43474694b60eb7dca273f76a636a4e2bfc89efda1d9855d5abf9cc464e23cdbf5a3119fed65c3d22cec726e29a2bad3c3318b languageName: node linkType: hard -"@ai-sdk/google-vertex@npm:^3.0.33": - version: 3.0.33 - resolution: "@ai-sdk/google-vertex@npm:3.0.33" +"@ai-sdk/google-vertex@npm:^3.0.40": + version: 3.0.40 + resolution: "@ai-sdk/google-vertex@npm:3.0.40" dependencies: - "@ai-sdk/anthropic": "npm:2.0.22" - "@ai-sdk/google": "npm:2.0.17" + "@ai-sdk/anthropic": "npm:2.0.27" + "@ai-sdk/google": "npm:2.0.20" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" google-auth-library: "npm:^9.15.0" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/d440e46f702385985a34f2260074eb41cf2516036598039c8c72d6155825114452942c3c012a181da7661341bee9a38958e5f9a53bba145b9c5dc4446411a651 + checksum: 10c0/680a06e1b80bc036744e2f13e1a55b57661c3674000ab82b863d6536730edfc3696b1b0b2235f6354de11fa323c4ef817d8edbd2dbf94dc4037ea882e560c9ea languageName: node linkType: hard -"@ai-sdk/google@npm:2.0.17": - version: 2.0.17 - resolution: "@ai-sdk/google@npm:2.0.17" +"@ai-sdk/google@npm:2.0.20": + version: 2.0.20 + resolution: "@ai-sdk/google@npm:2.0.20" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/174bcde507e5bf4bf95f20dbe4eaba73870715b13779e320f3df44995606e4d7ccd1e1f4b759d224deaf58bdfc6aa2e43a24dcbe5fa335ddfe91df1b06114218 + checksum: 10c0/9c73bb67061673b16f0996c85bf4e79ab9968c8a203c4f9731bf569e45960db88950dfc227aca69661ea805d381b285697ba1763faa03a38c01b86e6d2e90629 languageName: node linkType: hard -"@ai-sdk/mistral@npm:^2.0.17": - version: 2.0.17 - resolution: "@ai-sdk/mistral@npm:2.0.17" +"@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.20#~/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch": + version: 2.0.20 + resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.20#~/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch::version=2.0.20&hash=1f2ccb" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/58a129357c93cc7f2b15b2ba6ccfb9df3fb72e06163641602ea41c858f835cd76985d66665a56e4ed3fa1eb19ca75a83ae12986d466ec41942e9bf13d558c441 + checksum: 10c0/2d567361d533a4e2be83aa135cb5f01f09ea54c255d7751171855ef4244cfaeff73fe7b3c7b044b384a9c170e89d053160a26933176ad68dcaf03bd3c69c0be3 languageName: node linkType: hard -"@ai-sdk/openai-compatible@npm:1.0.19, @ai-sdk/openai-compatible@npm:^1.0.19": +"@ai-sdk/mistral@npm:^2.0.19": + version: 2.0.19 + resolution: "@ai-sdk/mistral@npm:2.0.19" + dependencies: + "@ai-sdk/provider": "npm:2.0.0" + "@ai-sdk/provider-utils": "npm:3.0.12" + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/522d1e4631e9318f82f5993030c8fa2a28341e749bc920d32966d91d5cd5a4d1638980b7e0a62601aaaaf7a25e04fefed18b07ce50034c5c5d903ac5bebb65ec + languageName: node + linkType: hard + +"@ai-sdk/openai-compatible@npm:1.0.22, @ai-sdk/openai-compatible@npm:^1.0.22": + version: 1.0.22 + resolution: "@ai-sdk/openai-compatible@npm:1.0.22" + dependencies: + "@ai-sdk/provider": "npm:2.0.0" + "@ai-sdk/provider-utils": "npm:3.0.12" + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/31eb07b63eaf07384391e81d824e16589af540f3af2fde1cb24f2a6d04dd07ddb843c9301dbceca78fa5ae5002cb235fc376c41532ab167d1564491526e6011b + languageName: node + linkType: hard + +"@ai-sdk/openai-compatible@npm:^1.0.19": version: 1.0.19 resolution: "@ai-sdk/openai-compatible@npm:1.0.19" dependencies: @@ -191,27 +216,39 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/openai@npm:2.0.42, @ai-sdk/openai@npm:^2.0.42": - version: 2.0.42 - resolution: "@ai-sdk/openai@npm:2.0.42" +"@ai-sdk/openai@npm:2.0.48, @ai-sdk/openai@npm:^2.0.48": + version: 2.0.48 + resolution: "@ai-sdk/openai@npm:2.0.48" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/b1ab158aafc86735e53c4621ffe125d469bc1732c533193652768a9f66ecd4d169303ce7ca59069b7baf725da49e55bcf81210848f09f66deaf2a8335399e6d7 + checksum: 10c0/6c584d7ffb80025da6b7253106a83f8c7a023e8ca322fd32e6858453782d6a0a6d268d7afa7145e3ea743a9c6cbc882932bb59eb1a659750f5205639c414fb49 languageName: node linkType: hard -"@ai-sdk/perplexity@npm:^2.0.11": - version: 2.0.11 - resolution: "@ai-sdk/perplexity@npm:2.0.11" +"@ai-sdk/openai@npm:^2.0.42": + version: 2.0.47 + resolution: "@ai-sdk/openai@npm:2.0.47" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.11" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/a8722b68f529b3d1baaa1ba4624c61efe732f22b24dfc20e27afae07bb25d72532bcb62d022191ab5e49df24496af619eabc092a4e6ad293b3fe231ef61b6467 + checksum: 10c0/7fabcdda707134971bcc2b285705d4595f8bf419285dbdd9266b3b0858ea11b6ac200e63dd2eeb1822f99571910093d64d4a76154a365331cf184f56452933c6 + languageName: node + linkType: hard + +"@ai-sdk/perplexity@npm:^2.0.13": + version: 2.0.13 + resolution: "@ai-sdk/perplexity@npm:2.0.13" + dependencies: + "@ai-sdk/provider": "npm:2.0.0" + "@ai-sdk/provider-utils": "npm:3.0.12" + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/80434eebec088d5f373901f1beb77ca4ba564df5f04dec43c69a7996ea0d88344a3d86ca5e5ef2dc4f5c45f45fc478dabf3a0e44a3faea86a0190c087491a661 languageName: node linkType: hard @@ -228,6 +265,32 @@ __metadata: languageName: node linkType: hard +"@ai-sdk/provider-utils@npm:3.0.11": + version: 3.0.11 + resolution: "@ai-sdk/provider-utils@npm:3.0.11" + 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.1.8 + checksum: 10c0/31081b127b48f3eefb448eaca59574b4631da9577aa0778622d28669c71bbde0361c9b37962c5edbb1d0c163ed1479755fc889da9251a03e906b1e27d0d2eb24 + languageName: node + linkType: hard + +"@ai-sdk/provider-utils@npm:3.0.12, @ai-sdk/provider-utils@npm:^3.0.12": + version: 3.0.12 + resolution: "@ai-sdk/provider-utils@npm:3.0.12" + 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.1.8 + checksum: 10c0/83886bf188cad0cc655b680b710a10413989eaba9ec59dd24a58b985c02a8a1d50ad0f96dd5259385c07592ec3c37a7769fdf4a1ef569a73c9edbdb2cd585915 + languageName: node + linkType: hard + "@ai-sdk/provider@npm:2.0.0, @ai-sdk/provider@npm:^2.0.0": version: 2.0.0 resolution: "@ai-sdk/provider@npm:2.0.0" @@ -237,16 +300,25 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/xai@npm:^2.0.23": - version: 2.0.23 - resolution: "@ai-sdk/xai@npm:2.0.23" +"@ai-sdk/provider@npm:^2.1.0-beta.4": + version: 2.1.0-beta.5 + resolution: "@ai-sdk/provider@npm:2.1.0-beta.5" dependencies: - "@ai-sdk/openai-compatible": "npm:1.0.19" + json-schema: "npm:^0.4.0" + checksum: 10c0/4f51813285a8e92be18ef14645b0505bb0c9d2daa0d9290cac8a3c1f87d8e2e8507b1edf1818ae2305a90723e8cd44477f55f0631407dee35912ab8fdded52ba + languageName: node + linkType: hard + +"@ai-sdk/xai@npm:^2.0.26": + version: 2.0.26 + resolution: "@ai-sdk/xai@npm:2.0.26" + dependencies: + "@ai-sdk/openai-compatible": "npm:1.0.22" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/4cf6b3bc71024797d1b2e37b57fb746f7387f9a7c1da530fd040aad1a840603a1a86fb7df7e428c723eba9b1547f89063d68f84e6e08444d2d4f152dee321dc3 + checksum: 10c0/72fef55a96d9c3820de02beb9b63e53902649c5db906a892b7818a984b6e8afe161daa225b8d527b74f783e2c4eecd474af6e96efbb95761aca2c508e0c7c2d9 languageName: node linkType: hard @@ -2462,14 +2534,14 @@ __metadata: version: 0.0.0-use.local resolution: "@cherrystudio/ai-core@workspace:packages/aiCore" dependencies: - "@ai-sdk/anthropic": "npm:^2.0.22" - "@ai-sdk/azure": "npm:^2.0.42" - "@ai-sdk/deepseek": "npm:^1.0.20" - "@ai-sdk/openai": "npm:^2.0.42" - "@ai-sdk/openai-compatible": "npm:^1.0.19" + "@ai-sdk/anthropic": "npm:^2.0.27" + "@ai-sdk/azure": "npm:^2.0.49" + "@ai-sdk/deepseek": "npm:^1.0.23" + "@ai-sdk/openai": "npm:^2.0.48" + "@ai-sdk/openai-compatible": "npm:^1.0.22" "@ai-sdk/provider": "npm:^2.0.0" - "@ai-sdk/provider-utils": "npm:^3.0.10" - "@ai-sdk/xai": "npm:^2.0.23" + "@ai-sdk/provider-utils": "npm:^3.0.12" + "@ai-sdk/xai": "npm:^2.0.26" tsdown: "npm:^0.12.9" typescript: "npm:^5.0.0" vitest: "npm:^3.2.4" @@ -3628,520 +3700,184 @@ __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" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/android-arm64@npm:0.25.8" - 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" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/android-arm@npm:0.25.8" - 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" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-x64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/android-x64@npm:0.25.8" - 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" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-arm64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/darwin-arm64@npm:0.25.8" - 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" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-x64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/darwin-x64@npm:0.25.8" - 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" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-arm64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/freebsd-arm64@npm:0.25.8" - 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" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-x64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/freebsd-x64@npm:0.25.8" - 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" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/linux-arm64@npm:0.25.8" - 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" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/linux-arm@npm:0.25.8" - 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" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ia32@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/linux-ia32@npm:0.25.8" - 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" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-loong64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/linux-loong64@npm:0.25.8" - 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" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-mips64el@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/linux-mips64el@npm:0.25.8" - 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" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ppc64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/linux-ppc64@npm:0.25.8" - 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" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-riscv64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/linux-riscv64@npm:0.25.8" - 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" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-s390x@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/linux-s390x@npm:0.25.8" - 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" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-x64@npm:0.18.20" +"@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" - conditions: os=linux & cpu=x64 - 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 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/netbsd-arm64@npm:0.25.8" +"@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 -"@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" +"@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" - conditions: os=netbsd & cpu=x64 - 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 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/openbsd-arm64@npm:0.25.8" +"@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 -"@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" +"@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" - conditions: os=openbsd & cpu=x64 - 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 - languageName: node - linkType: hard - -"@esbuild/openharmony-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/openharmony-arm64@npm:0.25.8" +"@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 -"@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" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/sunos-x64@npm:0.25.8" - 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" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-arm64@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/win32-arm64@npm:0.25.8" - 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" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-ia32@npm:0.18.20" +"@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.8": - version: 0.25.8 - resolution: "@esbuild/win32-ia32@npm:0.25.8" - 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" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-x64@npm:0.18.20" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@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 - -"@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.10": + version: 0.25.10 + resolution: "@esbuild/win32-x64@npm:0.25.10" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -8756,6 +8492,18 @@ __metadata: languageName: node linkType: hard +"@opeoginni/github-copilot-openai-compatible@npm:0.1.18": + version: 0.1.18 + resolution: "@opeoginni/github-copilot-openai-compatible@npm:0.1.18" + dependencies: + "@ai-sdk/openai": "npm:^2.0.42" + "@ai-sdk/openai-compatible": "npm:^1.0.19" + "@ai-sdk/provider": "npm:^2.1.0-beta.4" + "@ai-sdk/provider-utils": "npm:^3.0.10" + checksum: 10c0/31b87ed150883bbdd33a0203e45831859560fdf174f0285384fdcb1d01fc4a56ca15f31d648e8d6d3a2d4d5c6e327ddecbf422543eeefaa7e8fdd7dc2f2a3b08 + languageName: node + linkType: hard + "@oxc-project/runtime@npm:0.71.0": version: 0.71.0 resolution: "@oxc-project/runtime@npm:0.71.0" @@ -8833,58 +8581,58 @@ __metadata: languageName: node linkType: hard -"@oxlint/darwin-arm64@npm:1.15.0": - version: 1.15.0 - resolution: "@oxlint/darwin-arm64@npm:1.15.0" +"@oxlint/darwin-arm64@npm:1.22.0": + version: 1.22.0 + resolution: "@oxlint/darwin-arm64@npm:1.22.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@oxlint/darwin-x64@npm:1.15.0": - version: 1.15.0 - resolution: "@oxlint/darwin-x64@npm:1.15.0" +"@oxlint/darwin-x64@npm:1.22.0": + version: 1.22.0 + resolution: "@oxlint/darwin-x64@npm:1.22.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@oxlint/linux-arm64-gnu@npm:1.15.0": - version: 1.15.0 - resolution: "@oxlint/linux-arm64-gnu@npm:1.15.0" +"@oxlint/linux-arm64-gnu@npm:1.22.0": + version: 1.22.0 + resolution: "@oxlint/linux-arm64-gnu@npm:1.22.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@oxlint/linux-arm64-musl@npm:1.15.0": - version: 1.15.0 - resolution: "@oxlint/linux-arm64-musl@npm:1.15.0" +"@oxlint/linux-arm64-musl@npm:1.22.0": + version: 1.22.0 + resolution: "@oxlint/linux-arm64-musl@npm:1.22.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@oxlint/linux-x64-gnu@npm:1.15.0": - version: 1.15.0 - resolution: "@oxlint/linux-x64-gnu@npm:1.15.0" +"@oxlint/linux-x64-gnu@npm:1.22.0": + version: 1.22.0 + resolution: "@oxlint/linux-x64-gnu@npm:1.22.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@oxlint/linux-x64-musl@npm:1.15.0": - version: 1.15.0 - resolution: "@oxlint/linux-x64-musl@npm:1.15.0" +"@oxlint/linux-x64-musl@npm:1.22.0": + version: 1.22.0 + resolution: "@oxlint/linux-x64-musl@npm:1.22.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@oxlint/win32-arm64@npm:1.15.0": - version: 1.15.0 - resolution: "@oxlint/win32-arm64@npm:1.15.0" +"@oxlint/win32-arm64@npm:1.22.0": + version: 1.22.0 + resolution: "@oxlint/win32-arm64@npm:1.22.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@oxlint/win32-x64@npm:1.15.0": - version: 1.15.0 - resolution: "@oxlint/win32-x64@npm:1.15.0" +"@oxlint/win32-x64@npm:1.22.0": + version: 1.22.0 + resolution: "@oxlint/win32-x64@npm:1.22.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -15397,6 +15145,13 @@ __metadata: languageName: node linkType: hard +"@vercel/oidc@npm:3.0.2": + version: 3.0.2 + resolution: "@vercel/oidc@npm:3.0.2" + checksum: 10c0/8d4c8553baa5aed339ab7614d775139bc124a6d443b76877ab17e98c156daa4dbeb3cf2f3bf21fabfae2ac0dd3ff462ab43b9398708e02483e5923d302a1c4c8 + languageName: node + linkType: hard + "@vimeo/player@npm:2.29.0": version: 2.29.0 resolution: "@vimeo/player@npm:2.29.0" @@ -15700,10 +15455,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.29" - "@ai-sdk/google-vertex": "npm:^3.0.33" - "@ai-sdk/mistral": "npm:^2.0.17" - "@ai-sdk/perplexity": "npm:^2.0.11" + "@ai-sdk/amazon-bedrock": "npm:^3.0.35" + "@ai-sdk/google-vertex": "npm:^3.0.40" + "@ai-sdk/mistral": "npm:^2.0.19" + "@ai-sdk/perplexity": "npm:^2.0.13" "@ant-design/v5-patch-for-react-19": "npm:^1.0.3" "@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.1#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.1-d937b73fed.patch" "@anthropic-ai/sdk": "npm:^0.41.0" @@ -15757,6 +15512,7 @@ __metadata: "@opentelemetry/sdk-trace-base": "npm:^2.0.0" "@opentelemetry/sdk-trace-node": "npm:^2.0.0" "@opentelemetry/sdk-trace-web": "npm:^2.0.0" + "@opeoginni/github-copilot-openai-compatible": "npm:0.1.18" "@playwright/test": "npm:^1.52.0" "@radix-ui/react-context-menu": "npm:^2.2.16" "@reduxjs/toolkit": "npm:^2.2.5" @@ -15825,7 +15581,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.59" + ai: "npm:^5.0.68" 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" @@ -15862,6 +15618,7 @@ __metadata: emoji-picker-element: "npm:^1.22.1" epub: "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch" eslint: "npm:^9.22.0" + eslint-plugin-import-zod: "npm:^1.2.0" eslint-plugin-oxlint: "npm:^1.15.0" eslint-plugin-react-hooks: "npm:^5.2.0" eslint-plugin-simple-import-sort: "npm:^12.1.1" @@ -15906,7 +15663,7 @@ __metadata: officeparser: "npm:^4.2.0" openai: "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch" os-proxy-config: "npm:^1.1.2" - oxlint: "npm:^1.15.0" + oxlint: "npm:^1.22.0" oxlint-tsgolint: "npm:^0.2.0" p-queue: "npm:^8.1.0" pdf-lib: "npm:^1.17.1" @@ -16088,17 +15845,17 @@ __metadata: languageName: node linkType: hard -"ai@npm:^5.0.59": - version: 5.0.59 - resolution: "ai@npm:5.0.59" +"ai@npm:^5.0.68": + version: 5.0.68 + resolution: "ai@npm:5.0.68" dependencies: - "@ai-sdk/gateway": "npm:1.0.32" + "@ai-sdk/gateway": "npm:1.0.39" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.10" + "@ai-sdk/provider-utils": "npm:3.0.12" "@opentelemetry/api": "npm:1.9.0" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/daa956e753b93fbc30afbfba5be2ebb73e3c280dae3064e13949f04d5a22c0f4ea5698cc87e24a23ed6585d9cf7febee61b915292dbbd4286dc40c449cf2b845 + checksum: 10c0/0c042cd58c7193a47b06b3074a9e62790c4d5a8134e8e12bbb750714151e9aa217c641ee60c8cbe59d9869bade52ccbb283f9fcbf6d79711ebf1f774fa3feee3 languageName: node linkType: hard @@ -19847,36 +19604,36 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0": - version: 0.25.9 - resolution: "esbuild@npm:0.25.9" +"esbuild@npm:^0.25.0": + 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 @@ -19932,173 +19689,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/aaa1284c75fcf45c82f9a1a117fe8dc5c45628e3386bda7d64916ae27730910b51c5aec7dd45a6ba19256be30ba2935e64a8f011a3f0539833071e06bf76d5b3 - languageName: node - linkType: hard - -"esbuild@npm:^0.25.4, esbuild@npm:^0.25.5, esbuild@npm:~0.25.0": - version: 0.25.8 - resolution: "esbuild@npm:0.25.8" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.8" - "@esbuild/android-arm": "npm:0.25.8" - "@esbuild/android-arm64": "npm:0.25.8" - "@esbuild/android-x64": "npm:0.25.8" - "@esbuild/darwin-arm64": "npm:0.25.8" - "@esbuild/darwin-x64": "npm:0.25.8" - "@esbuild/freebsd-arm64": "npm:0.25.8" - "@esbuild/freebsd-x64": "npm:0.25.8" - "@esbuild/linux-arm": "npm:0.25.8" - "@esbuild/linux-arm64": "npm:0.25.8" - "@esbuild/linux-ia32": "npm:0.25.8" - "@esbuild/linux-loong64": "npm:0.25.8" - "@esbuild/linux-mips64el": "npm:0.25.8" - "@esbuild/linux-ppc64": "npm:0.25.8" - "@esbuild/linux-riscv64": "npm:0.25.8" - "@esbuild/linux-s390x": "npm:0.25.8" - "@esbuild/linux-x64": "npm:0.25.8" - "@esbuild/netbsd-arm64": "npm:0.25.8" - "@esbuild/netbsd-x64": "npm:0.25.8" - "@esbuild/openbsd-arm64": "npm:0.25.8" - "@esbuild/openbsd-x64": "npm:0.25.8" - "@esbuild/openharmony-arm64": "npm:0.25.8" - "@esbuild/sunos-x64": "npm:0.25.8" - "@esbuild/win32-arm64": "npm:0.25.8" - "@esbuild/win32-ia32": "npm:0.25.8" - "@esbuild/win32-x64": "npm:0.25.8" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/43747a25e120d5dd9ce75c82f57306580d715647c8db4f4a0a84e73b04cf16c27572d3937d3cfb95d5ac3266a4d1bbd3913e3d76ae719693516289fc86f8a5fd - languageName: node - linkType: hard - -"esbuild@npm:~0.18.20": - version: 0.18.20 - resolution: "esbuild@npm:0.18.20" - dependencies: - "@esbuild/android-arm": "npm:0.18.20" - "@esbuild/android-arm64": "npm:0.18.20" - "@esbuild/android-x64": "npm:0.18.20" - "@esbuild/darwin-arm64": "npm:0.18.20" - "@esbuild/darwin-x64": "npm:0.18.20" - "@esbuild/freebsd-arm64": "npm:0.18.20" - "@esbuild/freebsd-x64": "npm:0.18.20" - "@esbuild/linux-arm": "npm:0.18.20" - "@esbuild/linux-arm64": "npm:0.18.20" - "@esbuild/linux-ia32": "npm:0.18.20" - "@esbuild/linux-loong64": "npm:0.18.20" - "@esbuild/linux-mips64el": "npm:0.18.20" - "@esbuild/linux-ppc64": "npm:0.18.20" - "@esbuild/linux-riscv64": "npm:0.18.20" - "@esbuild/linux-s390x": "npm:0.18.20" - "@esbuild/linux-x64": "npm:0.18.20" - "@esbuild/netbsd-x64": "npm:0.18.20" - "@esbuild/openbsd-x64": "npm:0.18.20" - "@esbuild/sunos-x64": "npm:0.18.20" - "@esbuild/win32-arm64": "npm:0.18.20" - "@esbuild/win32-ia32": "npm:0.18.20" - "@esbuild/win32-x64": "npm:0.18.20" - dependenciesMeta: - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/473b1d92842f50a303cf948a11ebd5f69581cd254d599dd9d62f9989858e0533f64e83b723b5e1398a5b488c0f5fd088795b4235f65ecaf4f007d4b79f04bc88 + checksum: 10c0/8ee5fdd43ed0d4092ce7f41577c63147f54049d5617763f0549c638bbe939e8adaa8f1a2728adb63417eb11df51956b7b0d8eb88ee08c27ad1d42960256158fa languageName: node linkType: hard @@ -20148,6 +19739,16 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-import-zod@npm:^1.2.0": + version: 1.2.0 + resolution: "eslint-plugin-import-zod@npm:1.2.0" + peerDependencies: + "@typescript-eslint/utils": ^8.35.1 + eslint: ">=9" + checksum: 10c0/c03f3059c4e55fa50ad43da6db989bb4c51c6466ab17bd7ec7d096421d060a20488ee1ef0df80fa61706ad0b6a4534622540ceab0638d3b6887df7d810ef1d22 + languageName: node + linkType: hard + "eslint-plugin-oxlint@npm:^1.15.0": version: 1.15.0 resolution: "eslint-plugin-oxlint@npm:1.15.0" @@ -26228,18 +25829,18 @@ __metadata: languageName: node linkType: hard -"oxlint@npm:^1.15.0": - version: 1.15.0 - resolution: "oxlint@npm:1.15.0" +"oxlint@npm:^1.22.0": + version: 1.22.0 + resolution: "oxlint@npm:1.22.0" dependencies: - "@oxlint/darwin-arm64": "npm:1.15.0" - "@oxlint/darwin-x64": "npm:1.15.0" - "@oxlint/linux-arm64-gnu": "npm:1.15.0" - "@oxlint/linux-arm64-musl": "npm:1.15.0" - "@oxlint/linux-x64-gnu": "npm:1.15.0" - "@oxlint/linux-x64-musl": "npm:1.15.0" - "@oxlint/win32-arm64": "npm:1.15.0" - "@oxlint/win32-x64": "npm:1.15.0" + "@oxlint/darwin-arm64": "npm:1.22.0" + "@oxlint/darwin-x64": "npm:1.22.0" + "@oxlint/linux-arm64-gnu": "npm:1.22.0" + "@oxlint/linux-arm64-musl": "npm:1.22.0" + "@oxlint/linux-x64-gnu": "npm:1.22.0" + "@oxlint/linux-x64-musl": "npm:1.22.0" + "@oxlint/win32-arm64": "npm:1.22.0" + "@oxlint/win32-x64": "npm:1.22.0" peerDependencies: oxlint-tsgolint: ">=0.2.0" dependenciesMeta: @@ -26265,7 +25866,7 @@ __metadata: bin: oxc_language_server: bin/oxc_language_server oxlint: bin/oxlint - checksum: 10c0/3eb2a27b972f2a02200b068345ab6a3a17f7bc29c4546c6b3478727388d8d59b94a554f9b6bb1320b71a75cc598b728de0ffee5e4e70ac27457104b8efebb257 + checksum: 10c0/652c93b9360ea66c7ee87f649a56ba2b8eddc5e32494a53a61ca86749d87ce2be354960e135a60ab7054105e6b187e9d4ec56959cdb02d517423c23d6a523894 languageName: node linkType: hard @@ -30505,15 +30106,15 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^2.0.0": - version: 2.1.3 - resolution: "tar-fs@npm:2.1.3" +"tar-fs@npm:^2.1.4": + version: 2.1.4 + resolution: "tar-fs@npm:2.1.4" dependencies: chownr: "npm:^1.1.1" mkdirp-classic: "npm:^0.5.2" pump: "npm:^3.0.0" tar-stream: "npm:^2.1.4" - checksum: 10c0/472ee0c3c862605165163113ab6924f411c07506a1fb24c51a1a80085f0d4d381d86d2fd6b189236c8d932d1cd97b69cce35016767ceb658a35f7584fe77f305 + checksum: 10c0/decb25acdc6839182c06ec83cba6136205bda1db984e120c8ffd0d80182bc5baa1d916f9b6c5c663ea3f9975b4dd49e3c6bb7b1707cbcdaba4e76042f43ec84c languageName: node linkType: hard