diff --git a/.github/workflows/auto-i18n.yml b/.github/workflows/auto-i18n.yml index 140d6208fc..204d8ac437 100644 --- a/.github/workflows/auto-i18n.yml +++ b/.github/workflows/auto-i18n.yml @@ -26,7 +26,7 @@ jobs: ref: ${{ github.event.pull_request.head.ref }} - name: 📦 Setting Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 20 diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 5ed414b1fe..cc6d28817f 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 1 diff --git a/.github/workflows/claude-translator.yml b/.github/workflows/claude-translator.yml index ff317f8532..c474afeb8e 100644 --- a/.github/workflows/claude-translator.yml +++ b/.github/workflows/claude-translator.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 1 diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index ba1fcb97aa..82c7b4393b 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -37,7 +37,7 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 1 diff --git a/.github/workflows/delete-branch.yml b/.github/workflows/delete-branch.yml index fae32c7477..033ab4bfa0 100644 --- a/.github/workflows/delete-branch.yml +++ b/.github/workflows/delete-branch.yml @@ -12,7 +12,7 @@ jobs: if: github.event.pull_request.merged == true && github.event.pull_request.head.repo.full_name == github.repository steps: - name: Delete merged branch - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | github.rest.git.deleteRef({ diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 42d0d66150..9e1608b13e 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -56,7 +56,7 @@ jobs: ref: main - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 20 diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 4f462db95c..9108d71fc1 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v5 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 20 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ca1eb0146..c54504de07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: npm version "$VERSION" --no-git-tag-version --allow-same-version - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 20 diff --git a/package.json b/package.json index c97a40ddf1..22862da335 100644 --- a/package.json +++ b/package.json @@ -99,10 +99,10 @@ "@agentic/exa": "^7.3.3", "@agentic/searxng": "^7.3.3", "@agentic/tavily": "^7.3.3", - "@ai-sdk/amazon-bedrock": "^3.0.21", - "@ai-sdk/google-vertex": "^3.0.27", - "@ai-sdk/mistral": "^2.0.14", - "@ai-sdk/perplexity": "^2.0.9", + "@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", "@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", @@ -219,7 +219,7 @@ "@viz-js/lang-dot": "^1.0.5", "@viz-js/viz": "^3.14.0", "@xyflow/react": "^12.4.4", - "ai": "^5.0.44", + "ai": "^5.0.59", "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", @@ -244,7 +244,7 @@ "dotenv-cli": "^7.4.2", "drizzle-kit": "^0.31.4", "drizzle-orm": "^0.44.5", - "electron": "37.4.0", + "electron": "37.6.0", "electron-builder": "26.0.15", "electron-devtools-installer": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index 93bf7b6414..7210dcebb9 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -1,6 +1,6 @@ { "name": "@cherrystudio/ai-core", - "version": "1.0.0-alpha.18", + "version": "1.0.1", "description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK", "main": "dist/index.js", "module": "dist/index.mjs", @@ -36,14 +36,14 @@ "ai": "^5.0.26" }, "dependencies": { - "@ai-sdk/anthropic": "^2.0.17", - "@ai-sdk/azure": "^2.0.30", - "@ai-sdk/deepseek": "^1.0.17", - "@ai-sdk/openai": "^2.0.30", - "@ai-sdk/openai-compatible": "^1.0.17", + "@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/provider": "^2.0.0", - "@ai-sdk/provider-utils": "^3.0.9", - "@ai-sdk/xai": "^2.0.18", + "@ai-sdk/provider-utils": "^3.0.10", + "@ai-sdk/xai": "^2.0.23", "zod": "^4.1.5" }, "devDependencies": { diff --git a/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index 3ffe88f08a..3b38592005 100644 --- a/packages/shared/config/constant.ts +++ b/packages/shared/config/constant.ts @@ -217,7 +217,8 @@ export enum codeTools { claudeCode = 'claude-code', geminiCli = 'gemini-cli', openaiCodex = 'openai-codex', - iFlowCli = 'iflow-cli' + iFlowCli = 'iflow-cli', + githubCopilotCli = 'github-copilot-cli' } export enum terminalApps { diff --git a/src/main/services/CodeToolsService.ts b/src/main/services/CodeToolsService.ts index 486e58c212..d6eea8a9e6 100644 --- a/src/main/services/CodeToolsService.ts +++ b/src/main/services/CodeToolsService.ts @@ -31,7 +31,10 @@ interface VersionInfo { class CodeToolsService { private versionCache: Map = new Map() - private terminalsCache: { terminals: TerminalConfig[]; timestamp: number } | null = null + private terminalsCache: { + terminals: TerminalConfig[] + timestamp: number + } | null = null private customTerminalPaths: Map = new Map() // Store user-configured terminal paths private readonly CACHE_DURATION = 1000 * 60 * 30 // 30 minutes cache private readonly TERMINALS_CACHE_DURATION = 1000 * 60 * 5 // 5 minutes cache for terminals @@ -82,6 +85,8 @@ class CodeToolsService { return '@qwen-code/qwen-code' case codeTools.iFlowCli: return '@iflow-ai/iflow-cli' + case codeTools.githubCopilotCli: + return '@github/copilot' default: throw new Error(`Unsupported CLI tool: ${cliTool}`) } @@ -99,6 +104,8 @@ class CodeToolsService { return 'qwen' case codeTools.iFlowCli: return 'iflow' + case codeTools.githubCopilotCli: + return 'copilot' default: throw new Error(`Unsupported CLI tool: ${cliTool}`) } @@ -144,7 +151,9 @@ class CodeToolsService { case terminalApps.powershell: // Check for PowerShell in PATH try { - await execAsync('powershell -Command "Get-Host"', { timeout: 3000 }) + await execAsync('powershell -Command "Get-Host"', { + timeout: 3000 + }) return terminal } catch { try { @@ -384,7 +393,9 @@ class CodeToolsService { const binDir = path.join(os.homedir(), '.cherrystudio', 'bin') const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : '')) - const { stdout } = await execAsync(`"${executablePath}" --version`, { timeout: 10000 }) + const { stdout } = await execAsync(`"${executablePath}" --version`, { + timeout: 10000 + }) // Extract version number from output (format may vary by tool) const versionMatch = stdout.trim().match(/\d+\.\d+\.\d+/) installedVersion = versionMatch ? versionMatch[0] : stdout.trim().split(' ')[0] @@ -425,7 +436,10 @@ class CodeToolsService { logger.info(`${packageName} latest version: ${latestVersion}`) // Cache the result - this.versionCache.set(cacheKey, { version: latestVersion!, timestamp: now }) + this.versionCache.set(cacheKey, { + version: latestVersion!, + timestamp: now + }) logger.debug(`Cached latest version for ${packageName}`) } catch (error) { logger.warn(`Failed to get latest version for ${packageName}:`, error as Error) diff --git a/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts b/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts index 1419bd62b4..5ea1ddf966 100644 --- a/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts +++ b/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts @@ -24,6 +24,8 @@ export class AiSdkToChunkAdapter { private isFirstChunk = true private enableWebSearch: boolean = false private onSessionUpdate?: (sessionId: string) => void + private responseStartTimestamp: number | null = null + private firstTokenTimestamp: number | null = null constructor( private onChunk: (chunk: Chunk) => void, @@ -38,6 +40,17 @@ export class AiSdkToChunkAdapter { this.onSessionUpdate = onSessionUpdate } + private markFirstTokenIfNeeded() { + if (this.firstTokenTimestamp === null && this.responseStartTimestamp !== null) { + this.firstTokenTimestamp = Date.now() + } + } + + private resetTimingState() { + this.responseStartTimestamp = null + this.firstTokenTimestamp = null + } + /** * 处理 AI SDK 流结果 * @param aiSdkResult AI SDK 的流结果对象 @@ -65,6 +78,8 @@ export class AiSdkToChunkAdapter { webSearchResults: [], reasoningId: '' } + this.resetTimingState() + this.responseStartTimestamp = Date.now() // Reset link converter state at the start of stream this.isFirstChunk = true @@ -77,6 +92,7 @@ export class AiSdkToChunkAdapter { if (this.enableWebSearch) { const remainingText = flushLinkConverterBuffer() if (remainingText) { + this.markFirstTokenIfNeeded() this.onChunk({ type: ChunkType.TEXT_DELTA, text: remainingText @@ -91,6 +107,7 @@ export class AiSdkToChunkAdapter { } } finally { reader.releaseLock() + this.resetTimingState() } } @@ -152,6 +169,7 @@ export class AiSdkToChunkAdapter { // Only emit chunk if there's text to send if (finalText) { + this.markFirstTokenIfNeeded() this.onChunk({ type: ChunkType.TEXT_DELTA, text: this.accumulate ? final.text : finalText @@ -176,6 +194,9 @@ export class AiSdkToChunkAdapter { break case 'reasoning-delta': final.reasoningContent += chunk.text || '' + if (chunk.text) { + this.markFirstTokenIfNeeded() + } this.onChunk({ type: ChunkType.THINKING_DELTA, text: final.reasoningContent || '' @@ -275,44 +296,37 @@ export class AiSdkToChunkAdapter { break } - case 'finish': + case 'finish': { + const usage = { + completion_tokens: chunk.totalUsage?.outputTokens || 0, + prompt_tokens: chunk.totalUsage?.inputTokens || 0, + total_tokens: chunk.totalUsage?.totalTokens || 0 + } + const metrics = this.buildMetrics(chunk.totalUsage) + const baseResponse = { + text: final.text || '', + reasoning_content: final.reasoningContent || '' + } + this.onChunk({ type: ChunkType.BLOCK_COMPLETE, response: { - text: final.text || '', - reasoning_content: final.reasoningContent || '', - usage: { - completion_tokens: chunk.totalUsage.outputTokens || 0, - prompt_tokens: chunk.totalUsage.inputTokens || 0, - total_tokens: chunk.totalUsage.totalTokens || 0 - }, - metrics: chunk.totalUsage - ? { - completion_tokens: chunk.totalUsage.outputTokens || 0, - time_completion_millsec: 0 - } - : undefined + ...baseResponse, + usage: { ...usage }, + metrics: metrics ? { ...metrics } : undefined } }) this.onChunk({ type: ChunkType.LLM_RESPONSE_COMPLETE, response: { - text: final.text || '', - reasoning_content: final.reasoningContent || '', - usage: { - completion_tokens: chunk.totalUsage.outputTokens || 0, - prompt_tokens: chunk.totalUsage.inputTokens || 0, - total_tokens: chunk.totalUsage.totalTokens || 0 - }, - metrics: chunk.totalUsage - ? { - completion_tokens: chunk.totalUsage.outputTokens || 0, - time_completion_millsec: 0 - } - : undefined + ...baseResponse, + usage: { ...usage }, + metrics: metrics ? { ...metrics } : undefined } }) + this.resetTimingState() break + } // === 源和文件相关事件 === case 'source': @@ -348,6 +362,34 @@ export class AiSdkToChunkAdapter { default: } } + + private buildMetrics(totalUsage?: { + inputTokens?: number | null + outputTokens?: number | null + totalTokens?: number | null + }) { + if (!totalUsage) { + return undefined + } + + const completionTokens = totalUsage.outputTokens ?? 0 + const now = Date.now() + const start = this.responseStartTimestamp ?? now + const firstToken = this.firstTokenTimestamp + const timeFirstToken = Math.max(firstToken != null ? firstToken - start : 0, 0) + const baseForCompletion = firstToken ?? start + let timeCompletion = Math.max(now - baseForCompletion, 0) + + if (timeCompletion === 0 && completionTokens > 0) { + timeCompletion = 1 + } + + return { + completion_tokens: completionTokens, + time_first_token_millsec: timeFirstToken, + time_completion_millsec: timeCompletion + } + } } export default AiSdkToChunkAdapter diff --git a/src/renderer/src/components/CodeEditor/index.tsx b/src/renderer/src/components/CodeEditor/index.tsx index 4304ec324e..64c387ffd0 100644 --- a/src/renderer/src/components/CodeEditor/index.tsx +++ b/src/renderer/src/components/CodeEditor/index.tsx @@ -75,10 +75,15 @@ export interface CodeEditorProps { /** CSS class name appended to the default `code-editor` class. */ className?: string /** - * Whether the editor is editable. + * Whether the editor view is editable. * @default true */ editable?: boolean + /** + * Set the editor state to read only but keep some user interactions, e.g., keymaps. + * @default false + */ + readOnly?: boolean /** * Whether the editor is expanded. * If true, the height and maxHeight props are ignored. @@ -114,6 +119,7 @@ const CodeEditor = ({ style, className, editable = true, + readOnly = false, expanded = true, wrapped = true }: CodeEditorProps) => { @@ -189,6 +195,7 @@ const CodeEditor = ({ maxHeight={expanded ? undefined : maxHeight} minHeight={minHeight} editable={editable} + readOnly={readOnly} // @ts-ignore 强制使用,见 react-codemirror 的 Example.tsx theme={activeCmTheme} extensions={customExtensions} diff --git a/src/renderer/src/components/ObsidianExportDialog.tsx b/src/renderer/src/components/ObsidianExportDialog.tsx index 0c3d1c0038..b55105c599 100644 --- a/src/renderer/src/components/ObsidianExportDialog.tsx +++ b/src/renderer/src/components/ObsidianExportDialog.tsx @@ -38,6 +38,7 @@ interface PopupContainerProps { message?: Message messages?: Message[] topic?: Topic + rawContent?: string } // 转换文件信息数组为树形结构 @@ -140,7 +141,8 @@ const PopupContainer: React.FC = ({ resolve, message, messages, - topic + topic, + rawContent }) => { const defaultObsidianVault = store.getState().settings.defaultObsidianVault const [state, setState] = useState({ @@ -229,7 +231,9 @@ const PopupContainer: React.FC = ({ return } let markdown = '' - if (topic) { + if (rawContent) { + markdown = rawContent + } else if (topic) { markdown = await topicToMarkdown(topic, exportReasoning) } else if (messages && messages.length > 0) { markdown = messagesToMarkdown(messages, exportReasoning) @@ -299,7 +303,6 @@ const PopupContainer: React.FC = ({ } } } - return ( = ({ - - - + {!rawContent && ( + + + + )} ) diff --git a/src/renderer/src/components/Popups/ObsidianExportPopup.tsx b/src/renderer/src/components/Popups/ObsidianExportPopup.tsx index aec5fcbaa8..9a00e311cf 100644 --- a/src/renderer/src/components/Popups/ObsidianExportPopup.tsx +++ b/src/renderer/src/components/Popups/ObsidianExportPopup.tsx @@ -9,6 +9,7 @@ interface ObsidianExportOptions { topic?: Topic message?: Message messages?: Message[] + rawContent?: string } export default class ObsidianExportPopup { @@ -24,6 +25,7 @@ export default class ObsidianExportPopup { topic={options.topic} message={options.message} messages={options.messages} + rawContent={options.rawContent} obsidianTags={''} open={true} resolve={(v) => { diff --git a/src/renderer/src/components/Popups/TextFilePreview.tsx b/src/renderer/src/components/Popups/TextFilePreview.tsx index f5fa787d0b..584929401c 100644 --- a/src/renderer/src/components/Popups/TextFilePreview.tsx +++ b/src/renderer/src/components/Popups/TextFilePreview.tsx @@ -55,12 +55,15 @@ const PopupContainer: React.FC = ({ text, title, extension, resolve }) => footer={null}> {extension !== undefined ? ( ) : ( {text} diff --git a/src/renderer/src/components/Tab/TabContainer.tsx b/src/renderer/src/components/Tab/TabContainer.tsx index 18b4b04682..6747066c6e 100644 --- a/src/renderer/src/components/Tab/TabContainer.tsx +++ b/src/renderer/src/components/Tab/TabContainer.tsx @@ -237,7 +237,17 @@ const TabsContainer: React.FC = ({ children }) => { onSortEnd={onSortEnd} className="tabs-sortable" renderItem={(tab) => ( - handleTabClick(tab)}> + handleTabClick(tab)} + onAuxClick={(e) => { + if (e.button === 1 && tab.id !== 'home') { + e.preventDefault() + e.stopPropagation() + closeTab(tab.id) + } + }}> {tab.id && {getTabIcon(tab.id, minapps, minAppsCache)}} {getTabTitle(tab.id)} diff --git a/src/renderer/src/config/models/default.ts b/src/renderer/src/config/models/default.ts index abcb38f9b3..4fa60ed548 100644 --- a/src/renderer/src/config/models/default.ts +++ b/src/renderer/src/config/models/default.ts @@ -430,6 +430,12 @@ export const SYSTEM_MODELS: Record = } ], anthropic: [ + { + id: 'claude-sonnet-4-5-20250929', + provider: 'anthropic', + name: 'Claude Sonnet 4.5', + group: 'Claude 4.5' + }, { id: 'claude-sonnet-4-20250514', provider: 'anthropic', @@ -698,6 +704,12 @@ export const SYSTEM_MODELS: Record = name: 'GLM-4.5-Flash', group: 'GLM-4.5' }, + { + id: 'glm-4.6', + provider: 'zhipu', + name: 'GLM-4.6', + group: 'GLM-4.6' + }, { id: 'glm-4.5', provider: 'zhipu', diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 3f55f13a3a..10cb64156b 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -178,9 +178,13 @@ export function isGeminiReasoningModel(model?: Model): boolean { return false } +// Gemini 支持思考模式的模型正则 +export const GEMINI_THINKING_MODEL_REGEX = + /gemini-(?:2\.5.*(?:-latest)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\w-]+)*$/i + export const isSupportedThinkingTokenGeminiModel = (model: Model): boolean => { const modelId = getLowerBaseModelName(model.id, '/') - if (modelId.includes('gemini-2.5')) { + if (GEMINI_THINKING_MODEL_REGEX.test(modelId)) { if (modelId.includes('image') || modelId.includes('tts')) { return false } @@ -335,14 +339,20 @@ export const isSupportedReasoningEffortPerplexityModel = (model: Model): boolean export const isSupportedThinkingTokenZhipuModel = (model: Model): boolean => { const modelId = getLowerBaseModelName(model.id, '/') - return modelId.includes('glm-4.5') + return ['glm-4.5', 'glm-4.6'].some((id) => modelId.includes(id)) } export const isDeepSeekHybridInferenceModel = (model: Model) => { const modelId = getLowerBaseModelName(model.id) // deepseek官方使用chat和reasoner做推理控制,其他provider需要单独判断,id可能会有所差别 // openrouter: deepseek/deepseek-chat-v3.1 不知道会不会有其他provider仿照ds官方分出一个同id的作为非思考模式的模型,这里有风险 - return /deepseek-v3(?:\.1|-1-\d+)/.test(modelId) || modelId.includes('deepseek-chat-v3.1') + // Matches: "deepseek-v3" followed by ".digit" or "-digit". + // Optionally, this can be followed by ".alphanumeric_sequence" or "-alphanumeric_sequence" + // until the end of the string. + // Examples: deepseek-v3.1, deepseek-v3-1, deepseek-v3.1.2, deepseek-v3.1-alpha + // Does NOT match: deepseek-v3.123 (missing separator after '1'), deepseek-v3.x (x isn't a digit) + // TODO: move to utils and add test cases + return /deepseek-v3(?:\.\d|-\d)(?:(\.|-)\w+)?$/.test(modelId) || modelId.includes('deepseek-chat-v3.1') } export const isSupportedThinkingTokenDeepSeekModel = isDeepSeekHybridInferenceModel diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index 02ed3cd6fc..c7d78b90ab 100644 --- a/src/renderer/src/config/models/vision.ts +++ b/src/renderer/src/config/models/vision.ts @@ -12,6 +12,7 @@ const visionAllowedModels = [ 'gemini-1\\.5', 'gemini-2\\.0', 'gemini-2\\.5', + 'gemini-(flash|pro|flash-lite)-latest', 'gemini-exp', 'claude-3', 'claude-sonnet-4', @@ -21,7 +22,9 @@ const visionAllowedModels = [ 'qwen-vl', 'qwen2-vl', 'qwen2.5-vl', + 'qwen3-vl', 'qwen2.5-omni', + 'qwen3-omni', 'qvq', 'internvl2', 'grok-vision-beta', diff --git a/src/renderer/src/config/models/websearch.ts b/src/renderer/src/config/models/websearch.ts index 4acc8a7836..ecdf1bec37 100644 --- a/src/renderer/src/config/models/websearch.ts +++ b/src/renderer/src/config/models/websearch.ts @@ -11,9 +11,12 @@ export const CLAUDE_SUPPORTED_WEBSEARCH_REGEX = new RegExp( 'i' ) -export const GEMINI_FLASH_MODEL_REGEX = new RegExp('gemini-.*-flash.*$') +export const GEMINI_FLASH_MODEL_REGEX = new RegExp('gemini.*-flash.*$') -export const GEMINI_SEARCH_REGEX = new RegExp('gemini-2\\..*', 'i') +export const GEMINI_SEARCH_REGEX = new RegExp( + 'gemini-(?:2.*(?:-latest)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\\w-]+)*$', + 'i' +) export const PERPLEXITY_SEARCH_MODELS = [ 'sonar-pro', diff --git a/src/renderer/src/hooks/useCodeTools.ts b/src/renderer/src/hooks/useCodeTools.ts index 4d1527ed98..44ffd29d9c 100644 --- a/src/renderer/src/hooks/useCodeTools.ts +++ b/src/renderer/src/hooks/useCodeTools.ts @@ -108,7 +108,11 @@ export const useCodeTools = () => { const environmentVariables = codeToolsState?.environmentVariables?.[codeToolsState.selectedCliTool] || '' // 检查是否可以启动(所有必需字段都已填写) - const canLaunch = Boolean(codeToolsState.selectedCliTool && selectedModel && codeToolsState.currentDirectory) + const canLaunch = Boolean( + codeToolsState.selectedCliTool && + codeToolsState.currentDirectory && + (codeToolsState.selectedCliTool === codeTools.githubCopilotCli || selectedModel) + ) return { // 状态 diff --git a/src/renderer/src/hooks/useTopic.ts b/src/renderer/src/hooks/useTopic.ts index d2a71622fd..990cb12db8 100644 --- a/src/renderer/src/hooks/useTopic.ts +++ b/src/renderer/src/hooks/useTopic.ts @@ -48,6 +48,17 @@ export function useActiveTopic(assistantId: string, topic?: Topic) { } }, [activeTopic?.id, assistant]) + useEffect(() => { + if (!assistant?.topics?.length || !activeTopic) { + return + } + + const latestTopic = assistant.topics.find((item) => item.id === activeTopic.id) + if (latestTopic && latestTopic !== activeTopic) { + setActiveTopic(latestTopic) + } + }, [assistant?.topics, activeTopic]) + return { activeTopic, setActiveTopic } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 12d65f4903..3797240caa 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1920,6 +1920,12 @@ "provider_settings": "Go to provider settings" }, "notes": { + "auto_rename": { + "empty_note": "Note is empty, cannot generate name", + "failed": "Failed to generate note name", + "label": "Generate Note Name", + "success": "Note name generated successfully" + }, "characters": "Characters", "collapse": "Collapse", "content_placeholder": "Please enter the note content...", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 44f495bb68..df724a40a0 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1920,6 +1920,12 @@ "provider_settings": "跳转到服务商设置界面" }, "notes": { + "auto_rename": { + "empty_note": "笔记为空,无法生成名称", + "failed": "生成笔记名称失败", + "label": "生成笔记名称", + "success": "笔记名称生成成功" + }, "characters": "字符", "collapse": "收起", "content_placeholder": "请输入笔记内容...", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 73b7765094..c4bd47373e 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1920,6 +1920,12 @@ "provider_settings": "跳轉到服務商設置界面" }, "notes": { + "auto_rename": { + "empty_note": "筆記為空,無法生成名稱", + "failed": "生成筆記名稱失敗", + "label": "生成筆記名稱", + "success": "筆記名稱生成成功" + }, "characters": "字符", "collapse": "收起", "content_placeholder": "請輸入筆記內容...", diff --git a/src/renderer/src/pages/code/CodeToolsPage.tsx b/src/renderer/src/pages/code/CodeToolsPage.tsx index a70f8397dc..ff63510e69 100644 --- a/src/renderer/src/pages/code/CodeToolsPage.tsx +++ b/src/renderer/src/pages/code/CodeToolsPage.tsx @@ -98,6 +98,10 @@ const CodeToolsPage: FC = () => { return m.id.includes('openai') || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(m.provider) } + if (selectedCliTool === codeTools.githubCopilotCli) { + return false + } + if (selectedCliTool === codeTools.qwenCode || selectedCliTool === codeTools.iFlowCli) { if (m.supported_endpoint_types) { return ['openai', 'openai-response'].some((type) => @@ -196,7 +200,7 @@ const CodeToolsPage: FC = () => { } } - if (!selectedModel) { + if (!selectedModel && selectedCliTool !== codeTools.githubCopilotCli) { return { isValid: false, message: t('code.model_required') } } @@ -205,6 +209,11 @@ const CodeToolsPage: FC = () => { // 准备启动环境 const prepareLaunchEnvironment = async (): Promise | null> => { + if (selectedCliTool === codeTools.githubCopilotCli) { + const userEnv = parseEnvironmentVariables(environmentVariables) + return userEnv + } + if (!selectedModel) return null const modelProvider = getProviderByModel(selectedModel) @@ -229,7 +238,9 @@ const CodeToolsPage: FC = () => { // 执行启动操作 const executeLaunch = async (env: Record) => { - window.api.codeTools.run(selectedCliTool, selectedModel?.id!, currentDirectory, env, { + const modelId = selectedCliTool === codeTools.githubCopilotCli ? '' : selectedModel?.id! + + window.api.codeTools.run(selectedCliTool, modelId, currentDirectory, env, { autoUpdateToLatest, terminal: selectedTerminal }) @@ -316,7 +327,12 @@ const CodeToolsPage: FC = () => { banner style={{ borderRadius: 'var(--list-item-border-radius)' }} message={ -
+
{t('code.bun_required_message')}
+ + + )}
{t('code.working_directory')}
@@ -403,11 +437,27 @@ const CodeToolsPage: FC = () => { options={directories.map((dir) => ({ value: dir, label: ( -
- {dir} +
+ + {dir} + handleRemoveDirectory(dir, e)} />
@@ -429,7 +479,14 @@ const CodeToolsPage: FC = () => { rows={2} style={{ fontFamily: 'monospace' }} /> -
{t('code.env_vars_help')}
+
+ {t('code.env_vars_help')} +
{/* 终端选择 (macOS 和 Windows) */} @@ -464,7 +521,12 @@ const CodeToolsPage: FC = () => { selectedTerminal !== terminalApps.cmd && selectedTerminal !== terminalApps.powershell && selectedTerminal !== terminalApps.windowsTerminal && ( -
+
{terminalCustomPaths[selectedTerminal] ? `${t('code.custom_path')}: ${terminalCustomPaths[selectedTerminal]}` : t('code.custom_path_required')} diff --git a/src/renderer/src/pages/code/index.ts b/src/renderer/src/pages/code/index.ts index 39067e1c1e..1529043738 100644 --- a/src/renderer/src/pages/code/index.ts +++ b/src/renderer/src/pages/code/index.ts @@ -20,7 +20,8 @@ export const CLI_TOOLS = [ { value: codeTools.qwenCode, label: 'Qwen Code' }, { value: codeTools.geminiCli, label: 'Gemini CLI' }, { value: codeTools.openaiCodex, label: 'OpenAI Codex' }, - { value: codeTools.iFlowCli, label: 'iFlow CLI' } + { value: codeTools.iFlowCli, label: 'iFlow CLI' }, + { value: codeTools.githubCopilotCli, label: 'GitHub Copilot CLI' } ] export const GEMINI_SUPPORTED_PROVIDERS = ['aihubmix', 'dmxapi', 'new-api', 'cherryin'] @@ -43,7 +44,8 @@ export const CLI_TOOL_PROVIDER_MAP: Record Pr [codeTools.qwenCode]: (providers) => providers.filter((p) => p.type.includes('openai')), [codeTools.openaiCodex]: (providers) => providers.filter((p) => p.id === 'openai' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id)), - [codeTools.iFlowCli]: (providers) => providers.filter((p) => p.type.includes('openai')) + [codeTools.iFlowCli]: (providers) => providers.filter((p) => p.type.includes('openai')), + [codeTools.githubCopilotCli]: () => [] } export const getCodeToolsApiBaseUrl = (model: Model, type: EndpointType) => { @@ -158,6 +160,10 @@ export const generateToolEnvironment = ({ env.IFLOW_BASE_URL = baseUrl env.IFLOW_MODEL_NAME = model.id break + + case codeTools.githubCopilotCli: + env.GITHUB_TOKEN = apiKey || '' + break } return env diff --git a/src/renderer/src/pages/notes/NotesSidebar.tsx b/src/renderer/src/pages/notes/NotesSidebar.tsx index 4588c37611..a53fc5b5f7 100644 --- a/src/renderer/src/pages/notes/NotesSidebar.tsx +++ b/src/renderer/src/pages/notes/NotesSidebar.tsx @@ -6,11 +6,14 @@ import { useInPlaceEdit } from '@renderer/hooks/useInPlaceEdit' import { useKnowledgeBases } from '@renderer/hooks/useKnowledge' import { useActiveNode } from '@renderer/hooks/useNotesQuery' import NotesSidebarHeader from '@renderer/pages/notes/NotesSidebarHeader' -import { useAppSelector } from '@renderer/store' +import { fetchNoteSummary } from '@renderer/services/ApiService' +import { RootState, useAppSelector } from '@renderer/store' import { selectSortType } from '@renderer/store/note' import { NotesSortType, NotesTreeNode } from '@renderer/types/note' +import { exportNote } from '@renderer/utils/export' import { useVirtualizer } from '@tanstack/react-virtual' import { Dropdown, Input, InputRef, MenuProps } from 'antd' +import { ItemType, MenuItemType } from 'antd/es/menu/interface' import { ChevronDown, ChevronRight, @@ -20,11 +23,14 @@ import { FileSearch, Folder, FolderOpen, + Sparkles, Star, - StarOff + StarOff, + UploadIcon } from 'lucide-react' import { FC, memo, Ref, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' import styled from 'styled-components' interface NotesSidebarProps { @@ -50,6 +56,8 @@ interface TreeNodeProps { selectedFolderId?: string | null activeNodeId?: string editingNodeId: string | null + renamingNodeIds: Set + newlyRenamedNodeIds: Set draggedNodeId: string | null dragOverNodeId: string | null dragPosition: 'before' | 'inside' | 'after' @@ -72,6 +80,8 @@ const TreeNode = memo( selectedFolderId, activeNodeId, editingNodeId, + renamingNodeIds, + newlyRenamedNodeIds, draggedNodeId, dragOverNodeId, dragPosition, @@ -92,6 +102,8 @@ const TreeNode = memo( ? node.type === 'folder' && node.id === selectedFolderId : node.id === activeNodeId const isEditing = editingNodeId === node.id && inPlaceEdit.isEditing + const isRenaming = renamingNodeIds.has(node.id) + const isNewlyRenamed = newlyRenamedNodeIds.has(node.id) const hasChildren = node.children && node.children.length > 0 const isDragging = draggedNodeId === node.id const isDragOver = dragOverNodeId === node.id @@ -99,6 +111,12 @@ const TreeNode = memo( const isDragInside = isDragOver && dragPosition === 'inside' const isDragAfter = isDragOver && dragPosition === 'after' + const getNodeNameClassName = () => { + if (isRenaming) return 'shimmer' + if (isNewlyRenamed) return 'typing' + return '' + } + return (
@@ -156,7 +174,7 @@ const TreeNode = memo( size="small" /> ) : ( - {node.name} + {node.name} )} @@ -173,6 +191,8 @@ const TreeNode = memo( selectedFolderId={selectedFolderId} activeNodeId={activeNodeId} editingNodeId={editingNodeId} + renamingNodeIds={renamingNodeIds} + newlyRenamedNodeIds={newlyRenamedNodeIds} draggedNodeId={draggedNodeId} dragOverNodeId={dragOverNodeId} dragPosition={dragPosition} @@ -213,7 +233,10 @@ const NotesSidebar: FC = ({ const { bases } = useKnowledgeBases() const { activeNode } = useActiveNode(notesTree) const sortType = useAppSelector(selectSortType) + const exportMenuOptions = useSelector((state: RootState) => state.settings.exportMenuOptions) const [editingNodeId, setEditingNodeId] = useState(null) + const [renamingNodeIds, setRenamingNodeIds] = useState>(new Set()) + const [newlyRenamedNodeIds, setNewlyRenamedNodeIds] = useState>(new Set()) const [draggedNodeId, setDraggedNodeId] = useState(null) const [dragOverNodeId, setDragOverNodeId] = useState(null) const [dragPosition, setDragPosition] = useState<'before' | 'inside' | 'after'>('inside') @@ -336,6 +359,49 @@ const NotesSidebar: FC = ({ [bases.length, t] ) + const handleAutoRename = useCallback( + async (note: NotesTreeNode) => { + if (note.type !== 'file') return + + setRenamingNodeIds((prev) => new Set(prev).add(note.id)) + try { + const content = await window.api.file.readExternal(note.externalPath) + if (!content || content.trim().length === 0) { + window.toast.warning(t('notes.auto_rename.empty_note')) + return + } + + const summaryText = await fetchNoteSummary({ content }) + if (summaryText) { + onRenameNode(note.id, summaryText) + window.toast.success(t('notes.auto_rename.success')) + } else { + window.toast.error(t('notes.auto_rename.failed')) + } + } catch (error) { + window.toast.error(t('notes.auto_rename.failed')) + logger.error(`Failed to auto-rename note: ${error}`) + } finally { + setRenamingNodeIds((prev) => { + const next = new Set(prev) + next.delete(note.id) + return next + }) + + setNewlyRenamedNodeIds((prev) => new Set(prev).add(note.id)) + + setTimeout(() => { + setNewlyRenamedNodeIds((prev) => { + const next = new Set(prev) + next.delete(note.id) + return next + }) + }, 700) + } + }, + [onRenameNode, t] + ) + const handleDragStart = useCallback((e: React.DragEvent, node: NotesTreeNode) => { setDraggedNodeId(node.id) e.dataTransfer.effectAllowed = 'move' @@ -490,7 +556,22 @@ const NotesSidebar: FC = ({ const getMenuItems = useCallback( (node: NotesTreeNode) => { - const baseMenuItems: MenuProps['items'] = [ + const baseMenuItems: MenuProps['items'] = [] + + // only show auto rename for file for now + if (node.type !== 'folder') { + baseMenuItems.push({ + label: t('notes.auto_rename.label'), + key: 'auto-rename', + icon: , + disabled: renamingNodeIds.has(node.id), + onClick: () => { + handleAutoRename(node) + } + }) + } + + baseMenuItems.push( { label: t('notes.rename'), key: 'rename', @@ -507,7 +588,7 @@ const NotesSidebar: FC = ({ window.api.openPath(node.externalPath) } } - ] + ) if (node.type !== 'folder') { baseMenuItems.push( { @@ -525,6 +606,48 @@ const NotesSidebar: FC = ({ onClick: () => { handleExportKnowledge(node) } + }, + { + label: t('chat.topics.export.title'), + key: 'export', + icon: , + children: [ + exportMenuOptions.markdown && { + label: t('chat.topics.export.md.label'), + key: 'markdown', + onClick: () => exportNote({ node, platform: 'markdown' }) + }, + exportMenuOptions.docx && { + label: t('chat.topics.export.word'), + key: 'word', + onClick: () => exportNote({ node, platform: 'docx' }) + }, + exportMenuOptions.notion && { + label: t('chat.topics.export.notion'), + key: 'notion', + onClick: () => exportNote({ node, platform: 'notion' }) + }, + exportMenuOptions.yuque && { + label: t('chat.topics.export.yuque'), + key: 'yuque', + onClick: () => exportNote({ node, platform: 'yuque' }) + }, + exportMenuOptions.obsidian && { + label: t('chat.topics.export.obsidian'), + key: 'obsidian', + onClick: () => exportNote({ node, platform: 'obsidian' }) + }, + exportMenuOptions.joplin && { + label: t('chat.topics.export.joplin'), + key: 'joplin', + onClick: () => exportNote({ node, platform: 'joplin' }) + }, + exportMenuOptions.siyuan && { + label: t('chat.topics.export.siyuan'), + key: 'siyuan', + onClick: () => exportNote({ node, platform: 'siyuan' }) + } + ].filter(Boolean) as ItemType[] } ) } @@ -543,7 +666,16 @@ const NotesSidebar: FC = ({ return baseMenuItems }, - [t, handleStartEdit, onToggleStar, handleExportKnowledge, handleDeleteNode] + [ + t, + handleStartEdit, + onToggleStar, + handleExportKnowledge, + handleDeleteNode, + renamingNodeIds, + handleAutoRename, + exportMenuOptions + ] ) const handleDropFiles = useCallback( @@ -680,6 +812,8 @@ const NotesSidebar: FC = ({ selectedFolderId={selectedFolderId} activeNodeId={activeNode?.id} editingNodeId={editingNodeId} + renamingNodeIds={renamingNodeIds} + newlyRenamedNodeIds={newlyRenamedNodeIds} draggedNodeId={draggedNodeId} dragOverNodeId={dragOverNodeId} dragPosition={dragPosition} @@ -724,6 +858,8 @@ const NotesSidebar: FC = ({ selectedFolderId={selectedFolderId} activeNodeId={activeNode?.id} editingNodeId={editingNodeId} + renamingNodeIds={renamingNodeIds} + newlyRenamedNodeIds={newlyRenamedNodeIds} draggedNodeId={draggedNodeId} dragOverNodeId={dragOverNodeId} dragPosition={dragPosition} @@ -746,6 +882,8 @@ const NotesSidebar: FC = ({ selectedFolderId={selectedFolderId} activeNodeId={activeNode?.id} editingNodeId={editingNodeId} + renamingNodeIds={renamingNodeIds} + newlyRenamedNodeIds={newlyRenamedNodeIds} draggedNodeId={draggedNodeId} dragOverNodeId={dragOverNodeId} dragPosition={dragPosition} @@ -933,6 +1071,44 @@ const NodeName = styled.div` text-overflow: ellipsis; font-size: 13px; color: var(--color-text); + position: relative; + will-change: background-position, width; + + --color-shimmer-mid: var(--color-text-1); + --color-shimmer-end: color-mix(in srgb, var(--color-text-1) 25%, transparent); + + &.shimmer { + background: linear-gradient(to left, var(--color-shimmer-end), var(--color-shimmer-mid), var(--color-shimmer-end)); + background-size: 200% 100%; + background-clip: text; + color: transparent; + animation: shimmer 3s linear infinite; + } + + &.typing { + display: block; + white-space: nowrap; + overflow: hidden; + animation: typewriter 0.5s steps(40, end); + } + + @keyframes shimmer { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } + } + + @keyframes typewriter { + from { + width: 0; + } + to { + width: 100%; + } + } ` const EditInput = styled(Input)` diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index 64e2c1ae31..6ab07662cb 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -251,6 +251,68 @@ export async function fetchMessagesSummary({ messages, assistant }: { messages: } } +export async function fetchNoteSummary({ content, assistant }: { content: string; assistant?: Assistant }) { + let prompt = (getStoreSetting('topicNamingPrompt') as string) || i18n.t('prompts.title') + const resolvedAssistant = assistant || getDefaultAssistant() + const model = getQuickModel() || resolvedAssistant.model || getDefaultModel() + + if (prompt && containsSupportedVariables(prompt)) { + prompt = await replacePromptVariables(prompt, model.name) + } + + const provider = getProviderByModel(model) + + if (!hasApiKey(provider)) { + return null + } + + const AI = new AiProviderNew(model) + + // only 2000 char and no images + const truncatedContent = content.substring(0, 2000) + const purifiedContent = purifyMarkdownImages(truncatedContent) + + const summaryAssistant = { + ...resolvedAssistant, + settings: { + ...resolvedAssistant.settings, + reasoning_effort: undefined, + qwenThinkMode: false + }, + prompt, + model + } + + const llmMessages = { + system: prompt, + prompt: purifiedContent + } + + const middlewareConfig: AiSdkMiddlewareConfig = { + streamOutput: false, + enableReasoning: false, + isPromptToolUse: false, + isSupportedToolUse: false, + isImageGenerationEndpoint: false, + enableWebSearch: false, + enableGenerateImage: false, + enableUrlContext: false, + mcpTools: [] + } + + try { + const { getText } = await AI.completions(model.id, llmMessages, { + ...middlewareConfig, + assistant: summaryAssistant, + callType: 'summary' + }) + const text = getText() + return removeSpecialCharactersForTopicName(text) || null + } catch (error: any) { + return null + } +} + // export async function fetchSearchSummary({ messages, assistant }: { messages: Message[]; assistant: Assistant }) { // const model = getQuickModel() || assistant.model || getDefaultModel() // const provider = getProviderByModel(model) diff --git a/src/renderer/src/store/codeTools.ts b/src/renderer/src/store/codeTools.ts index 471a31113e..fd23d9fff8 100644 --- a/src/renderer/src/store/codeTools.ts +++ b/src/renderer/src/store/codeTools.ts @@ -26,12 +26,17 @@ export const initialState: CodeToolsState = { [codeTools.qwenCode]: null, [codeTools.claudeCode]: null, [codeTools.geminiCli]: null, - [codeTools.openaiCodex]: null + [codeTools.openaiCodex]: null, + [codeTools.iFlowCli]: null, + [codeTools.githubCopilotCli]: null }, environmentVariables: { 'qwen-code': '', 'claude-code': '', - 'gemini-cli': '' + 'gemini-cli': '', + 'openai-codex': '', + 'iflow-cli': '', + 'github-copilot-cli': '' }, directories: [], currentDirectory: '', @@ -63,7 +68,10 @@ const codeToolsSlice = createSlice({ state.environmentVariables = { 'qwen-code': '', 'claude-code': '', - 'gemini-cli': '' + 'gemini-cli': '', + 'openai-codex': '', + 'iflow-cli': '', + 'github-copilot-cli': '' } } state.environmentVariables[state.selectedCliTool] = action.payload diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index f3ce321d63..d50b5b0e5d 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -1082,3 +1082,51 @@ export const exportTopicToNotes = async (topic: Topic, folderPath: string): Prom throw error } } + +const exportNoteAsMarkdown = async (noteName: string, content: string): Promise => { + const markdown = `# ${noteName}\n\n${content}` + const fileName = removeSpecialCharactersForFileName(noteName) + '.md' + const result = await window.api.file.save(fileName, markdown) + if (result) { + window.toast.success(i18n.t('message.success.markdown.export.specified')) + } +} + +interface NoteExportOptions { + node: { name: string; externalPath: string } + platform: 'markdown' | 'docx' | 'notion' | 'yuque' | 'obsidian' | 'joplin' | 'siyuan' +} + +export const exportNote = async ({ node, platform }: NoteExportOptions): Promise => { + try { + const content = await window.api.file.readExternal(node.externalPath) + + switch (platform) { + case 'markdown': + return await exportNoteAsMarkdown(node.name, content) + case 'docx': + window.api.export.toWord(`# ${node.name}\n\n${content}`, removeSpecialCharactersForFileName(node.name)) + return + case 'notion': + await exportMessageToNotion(node.name, content) + return + case 'yuque': + await exportMarkdownToYuque(node.name, `# ${node.name}\n\n${content}`) + return + case 'obsidian': { + const { default: ObsidianExportPopup } = await import('@renderer/components/Popups/ObsidianExportPopup') + await ObsidianExportPopup.show({ title: node.name, processingMethod: '1', rawContent: content }) + return + } + case 'joplin': + await exportMarkdownToJoplin(node.name, content) + return + case 'siyuan': + await exportMarkdownToSiyuan(node.name, `# ${node.name}\n\n${content}`) + return + } + } catch (error) { + logger.error(`Failed to export note to ${platform}:`, error as Error) + throw error + } +} diff --git a/yarn.lock b/yarn.lock index db06d7d17a..19bb0f1cc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -74,169 +74,157 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/amazon-bedrock@npm:^3.0.21": - version: 3.0.21 - resolution: "@ai-sdk/amazon-bedrock@npm:3.0.21" +"@ai-sdk/amazon-bedrock@npm:^3.0.29": + version: 3.0.29 + resolution: "@ai-sdk/amazon-bedrock@npm:3.0.29" dependencies: - "@ai-sdk/anthropic": "npm:2.0.17" + "@ai-sdk/anthropic": "npm:2.0.22" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" "@smithy/eventstream-codec": "npm:^4.0.1" "@smithy/util-utf8": "npm:^4.0.0" aws4fetch: "npm:^1.0.20" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/2d15baaad53e389666cede9673e2b43f5299e2cedb70f5b7afc656b7616e73775a9108c2cc1beee4644ff4c66ad41c8dd0b412373dd05caa4fc3d477c4343ea8 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/7add02e6c13774943929bb5d568b3110f6badc6d95cb56c6d3011cafc45778e27c0133417dd7fe835e7f0b1ae7767c22a7d5e3d39f725e2aa44e2b6e47d95fb7 languageName: node linkType: hard -"@ai-sdk/anthropic@npm:2.0.17, @ai-sdk/anthropic@npm:^2.0.17": - version: 2.0.17 - resolution: "@ai-sdk/anthropic@npm:2.0.17" +"@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" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/783b6a953f3854c4303ad7c30dd56d4706486c7d1151adb17071d87933418c59c26bce53d5c26d34c4d4728eaac4a856ce49a336caed26a7216f982fea562814 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/d922d2ff606b2429fb14c099628ba6734ef7c9b0e9225635f3faaf2d067362dea6ae0e920a35c05ccf15a01c59fef93ead5f147a9609dd3dd8c3ac18a3123b85 languageName: node linkType: hard -"@ai-sdk/azure@npm:^2.0.30": - version: 2.0.30 - resolution: "@ai-sdk/azure@npm:2.0.30" +"@ai-sdk/azure@npm:^2.0.42": + version: 2.0.42 + resolution: "@ai-sdk/azure@npm:2.0.42" dependencies: - "@ai-sdk/openai": "npm:2.0.30" + "@ai-sdk/openai": "npm:2.0.42" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/22af450e28026547badc891a627bcb3cfa2d030864089947172506810f06cfa4c74c453aabd6a0d5c05ede5ffdee381b9278772ce781eca0c7c826c7d7ae3dc3 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/14d3d6edac691df57879a9a7efc46d5d00b6bde5b64cd62a67a7668455c341171119ae90a431e57ac37009bced19add50b3da26998376b7e56e080bc2c997c00 languageName: node linkType: hard -"@ai-sdk/deepseek@npm:^1.0.17": - version: 1.0.17 - resolution: "@ai-sdk/deepseek@npm:1.0.17" +"@ai-sdk/deepseek@npm:^1.0.20": + version: 1.0.20 + resolution: "@ai-sdk/deepseek@npm:1.0.20" dependencies: - "@ai-sdk/openai-compatible": "npm:1.0.17" + "@ai-sdk/openai-compatible": "npm:1.0.19" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/c408701343bb28ed0b3e034b8789e6de1dfd6cfc6a9b53feb68f155889e29a9fbbcf05bd99e63f60809cf05ee4b158abaccdf1cbcd9df92c0987094220a61d08 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/e66ece8cf6371c2bac5436ed82cd1e2bb5c367fae6df60090f91cff62bf241f4df0abded99c33558013f8dc0bcc7d962f2126086eba8587ba929da50afd3d806 languageName: node linkType: hard -"@ai-sdk/gateway@npm:1.0.23": - version: 1.0.23 - resolution: "@ai-sdk/gateway@npm:1.0.23" +"@ai-sdk/gateway@npm:1.0.32": + version: 1.0.32 + resolution: "@ai-sdk/gateway@npm:1.0.32" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/b1e1a6ab63b9191075eed92c586cd927696f8997ad24f056585aee3f5fffd283d981aa6b071a2560ecda4295445b80a4cfd321fa63c06e7ac54a06bc4c84887f + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/82c98db6e4e8e235e1ff66410318ebe77cc1518ebf06d8d4757b4f30aaa3bf7075d3028816438551fef2f89e2d4c8c26e4efcd9913a06717aee1308dad3ddc30 languageName: node linkType: hard -"@ai-sdk/google-vertex@npm:^3.0.27": - version: 3.0.27 - resolution: "@ai-sdk/google-vertex@npm:3.0.27" +"@ai-sdk/google-vertex@npm:^3.0.33": + version: 3.0.33 + resolution: "@ai-sdk/google-vertex@npm:3.0.33" dependencies: - "@ai-sdk/anthropic": "npm:2.0.17" - "@ai-sdk/google": "npm:2.0.14" + "@ai-sdk/anthropic": "npm:2.0.22" + "@ai-sdk/google": "npm:2.0.17" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" google-auth-library: "npm:^9.15.0" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/7017838aef9c04c18ce9acec52eb602ee0a38d68a7496977a3898411f1ac235b2d7776011fa686084b90b0881e65c69596014e5465b8ed0d0e313b5db1f967a7 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/d440e46f702385985a34f2260074eb41cf2516036598039c8c72d6155825114452942c3c012a181da7661341bee9a38958e5f9a53bba145b9c5dc4446411a651 languageName: node linkType: hard -"@ai-sdk/google@npm:2.0.14": - version: 2.0.14 - resolution: "@ai-sdk/google@npm:2.0.14" +"@ai-sdk/google@npm:2.0.17": + version: 2.0.17 + resolution: "@ai-sdk/google@npm:2.0.17" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/2c04839cf58c33514a54c9de8190c363b5cacfbfc8404fea5d2ec36ad0af5ced4fc571f978e7aa35876bd9afae138f4c700d2bc1f64a78a37d0401f6797bf8f3 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/174bcde507e5bf4bf95f20dbe4eaba73870715b13779e320f3df44995606e4d7ccd1e1f4b759d224deaf58bdfc6aa2e43a24dcbe5fa335ddfe91df1b06114218 languageName: node linkType: hard -"@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.14#~/.yarn/patches/@ai-sdk-google-npm-2.0.14-376d8b03cc.patch": - version: 2.0.14 - resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.14#~/.yarn/patches/@ai-sdk-google-npm-2.0.14-376d8b03cc.patch::version=2.0.14&hash=351f1a" +"@ai-sdk/mistral@npm:^2.0.17": + version: 2.0.17 + resolution: "@ai-sdk/mistral@npm:2.0.17" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/1ed5a0732a82b981d51f63c6241ed8ee94d5c29a842764db770305cfc2f49ab6e528cac438b5357fc7b02194104c7b76d4390a1dc1d019ace9c174b0849e0da6 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/58a129357c93cc7f2b15b2ba6ccfb9df3fb72e06163641602ea41c858f835cd76985d66665a56e4ed3fa1eb19ca75a83ae12986d466ec41942e9bf13d558c441 languageName: node linkType: hard -"@ai-sdk/mistral@npm:^2.0.14": - version: 2.0.14 - resolution: "@ai-sdk/mistral@npm:2.0.14" +"@ai-sdk/openai-compatible@npm:1.0.19, @ai-sdk/openai-compatible@npm:^1.0.19": + version: 1.0.19 + resolution: "@ai-sdk/openai-compatible@npm:1.0.19" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/420be3a039095830aaf59b6f82c1f986ff4800ba5b9438e1dd85530026a42c9454a6e632b6a1a1839816609f4752d0a19140d8943ad78bb976fb5d6a37714e16 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/5b7b21fb515e829c3d8a499a5760ffc035d9b8220695996110e361bd79e9928859da4ecf1ea072735bcbe4977c6dd0661f543871921692e86f8b5bfef14fe0e5 languageName: node linkType: hard -"@ai-sdk/openai-compatible@npm:1.0.17, @ai-sdk/openai-compatible@npm:^1.0.17": - version: 1.0.17 - resolution: "@ai-sdk/openai-compatible@npm:1.0.17" +"@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" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/53ab6111e0f44437a2e268a51fb747600844d85b0cd0d170fb87a7b68af3eb21d7728d7bbf14d71c9fcf36e7a0f94ad75f0ad6b1070e473c867ab08ef84f6564 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/b1ab158aafc86735e53c4621ffe125d469bc1732c533193652768a9f66ecd4d169303ce7ca59069b7baf725da49e55bcf81210848f09f66deaf2a8335399e6d7 languageName: node linkType: hard -"@ai-sdk/openai@npm:2.0.30, @ai-sdk/openai@npm:^2.0.30": - version: 2.0.30 - resolution: "@ai-sdk/openai@npm:2.0.30" +"@ai-sdk/perplexity@npm:^2.0.11": + version: 2.0.11 + resolution: "@ai-sdk/perplexity@npm:2.0.11" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/90a57c1b10dac46c0bbe7e16cf9202557fb250d9f0e94a2a5fb7d95b5ea77815a56add78b00238d3823f0313c9b2c42abe865478d28a6196f72b341d32dd40af + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/a8722b68f529b3d1baaa1ba4624c61efe732f22b24dfc20e27afae07bb25d72532bcb62d022191ab5e49df24496af619eabc092a4e6ad293b3fe231ef61b6467 languageName: node linkType: hard -"@ai-sdk/perplexity@npm:^2.0.9": - version: 2.0.9 - resolution: "@ai-sdk/perplexity@npm:2.0.9" - dependencies: - "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" - peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/2023aadc26c41430571c4897df79074e7a95a12f2238ad57081355484066bcf9e8dfde1da60fa6af12fc9fb2a195899326f753c69f4913dc005a33367f150349 - languageName: node - linkType: hard - -"@ai-sdk/provider-utils@npm:3.0.9, @ai-sdk/provider-utils@npm:^3.0.9": - version: 3.0.9 - resolution: "@ai-sdk/provider-utils@npm:3.0.9" +"@ai-sdk/provider-utils@npm:3.0.10, @ai-sdk/provider-utils@npm:^3.0.10": + version: 3.0.10 + resolution: "@ai-sdk/provider-utils@npm:3.0.10" dependencies: "@ai-sdk/provider": "npm:2.0.0" "@standard-schema/spec": "npm:^1.0.0" eventsource-parser: "npm:^3.0.5" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/f8b659343d7e22ae099f7b6fc514591c0408012eb0aa00f7a912798b6d7d7305cafa8f18a07c7adec0bb5d39d9b6256b76d65c5393c3fc843d1361c52f1f8080 + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/d2c16abdb84ba4ef48c9f56190b5ffde224b9e6ae5147c5c713d2623627732d34b96aa9aef2a2ea4b0c49e1b863cc963c7d7ff964a1dc95f0f036097aaaaaa98 languageName: node linkType: hard @@ -249,16 +237,16 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/xai@npm:^2.0.18": - version: 2.0.18 - resolution: "@ai-sdk/xai@npm:2.0.18" +"@ai-sdk/xai@npm:^2.0.23": + version: 2.0.23 + resolution: "@ai-sdk/xai@npm:2.0.23" dependencies: - "@ai-sdk/openai-compatible": "npm:1.0.17" + "@ai-sdk/openai-compatible": "npm:1.0.19" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/7134501a2d315ec13605558aa24d7f5662885fe8b0491a634abefeb0c5c88517149677d1beff0c8abeec78a6dcd14573a2f57d96fa54a1d63d03820ac7ff827a + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/4cf6b3bc71024797d1b2e37b57fb746f7387f9a7c1da530fd040aad1a840603a1a86fb7df7e428c723eba9b1547f89063d68f84e6e08444d2d4f152dee321dc3 languageName: node linkType: hard @@ -2379,14 +2367,14 @@ __metadata: version: 0.0.0-use.local resolution: "@cherrystudio/ai-core@workspace:packages/aiCore" dependencies: - "@ai-sdk/anthropic": "npm:^2.0.17" - "@ai-sdk/azure": "npm:^2.0.30" - "@ai-sdk/deepseek": "npm:^1.0.17" - "@ai-sdk/openai": "npm:^2.0.30" - "@ai-sdk/openai-compatible": "npm:^1.0.17" + "@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/provider": "npm:^2.0.0" - "@ai-sdk/provider-utils": "npm:^3.0.9" - "@ai-sdk/xai": "npm:^2.0.18" + "@ai-sdk/provider-utils": "npm:^3.0.10" + "@ai-sdk/xai": "npm:^2.0.23" tsdown: "npm:^0.12.9" typescript: "npm:^5.0.0" vitest: "npm:^3.2.4" @@ -14193,10 +14181,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.21" - "@ai-sdk/google-vertex": "npm:^3.0.27" - "@ai-sdk/mistral": "npm:^2.0.14" - "@ai-sdk/perplexity": "npm:^2.0.9" + "@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" "@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" @@ -14318,7 +14306,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.44" + ai: "npm:^5.0.59" 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" @@ -14581,17 +14569,17 @@ __metadata: languageName: node linkType: hard -"ai@npm:^5.0.44": - version: 5.0.44 - resolution: "ai@npm:5.0.44" +"ai@npm:^5.0.59": + version: 5.0.59 + resolution: "ai@npm:5.0.59" dependencies: - "@ai-sdk/gateway": "npm:1.0.23" + "@ai-sdk/gateway": "npm:1.0.32" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.9" + "@ai-sdk/provider-utils": "npm:3.0.10" "@opentelemetry/api": "npm:1.9.0" peerDependencies: - zod: ^3.25.76 || ^4 - checksum: 10c0/528c7e165f75715194204051ce0aa341d8dca7d5536c2abcf3df83ccda7399ed5d91deaa45a81340f93d2461b1c2fc5f740f7804dfd396927c71b0667403569b + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/daa956e753b93fbc30afbfba5be2ebb73e3c280dae3064e13949f04d5a22c0f4ea5698cc87e24a23ed6585d9cf7febee61b915292dbbd4286dc40c449cf2b845 languageName: node linkType: hard @@ -17990,16 +17978,16 @@ __metadata: languageName: node linkType: hard -"electron@npm:37.4.0": - version: 37.4.0 - resolution: "electron@npm:37.4.0" +"electron@npm:37.6.0": + version: 37.6.0 + resolution: "electron@npm:37.6.0" dependencies: "@electron/get": "npm:^2.0.0" "@types/node": "npm:^22.7.7" extract-zip: "npm:^2.0.1" bin: electron: cli.js - checksum: 10c0/92a0c41190e234d302bc612af6cce9af08cd07f6699c1ff21a9365297e73dc9d88c6c4c25ddabf352447e3e555878d2ab0f2f31a14e210dda6de74d2787ff323 + checksum: 10c0/d67b7f0ff902f9184c2a7445507746343f8b39f3616d9d26128e7515e0184252cfc8ac97a3f1458f9ea9b4af6ab5b3208282014e8d91c0e1505ff21f5fa57ce6 languageName: node linkType: hard