From d11a2cd95c4359b49047ae9e4a203c55ec087b72 Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Tue, 30 Sep 2025 18:39:48 +0800 Subject: [PATCH 01/16] chore: update dependencies and versioning across packages (#10471) - Bumped versions for several @ai-sdk packages in package.json and yarn.lock to their latest releases, including @ai-sdk/amazon-bedrock, @ai-sdk/google-vertex, @ai-sdk/mistral, and @ai-sdk/perplexity. - Updated ai package version from 5.0.44 to 5.0.59. - Updated aiCore package version from 1.0.0-alpha.18 to 1.0.1 and adjusted dependencies accordingly. - Ensured compatibility with the latest zod version in multiple packages. --- package.json | 10 +- packages/aiCore/package.json | 16 +-- yarn.lock | 216 +++++++++++++++++------------------ 3 files changed, 115 insertions(+), 127 deletions(-) diff --git a/package.json b/package.json index 2e6b6251e6..907f872009 100644 --- a/package.json +++ b/package.json @@ -97,10 +97,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", @@ -215,7 +215,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", 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/yarn.lock b/yarn.lock index 9252c911de..93029954a6 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 @@ -2325,14 +2313,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" @@ -13195,10 +13183,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/sdk": "npm:^0.41.0" "@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch" @@ -13317,7 +13305,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" @@ -13575,17 +13563,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 From 38ac42af8c52de5f85809f955835ac8cf1dea82b Mon Sep 17 00:00:00 2001 From: LeaderOnePro Date: Tue, 30 Sep 2025 23:43:19 +0800 Subject: [PATCH 02/16] feat: add GitHub Copilot CLI integration to coding tools (#10403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add GitHub Copilot CLI integration to coding tools - Add githubCopilotCli to codeTools enum - Support @github/copilot package installation - Add 'copilot' executable command mapping - Update Redux store to include GitHub Copilot CLI state - Add GitHub Copilot CLI option to UI with proper provider mapping - Implement environment variable handling for GitHub authentication - Fix model selection logic to disable model choice for GitHub Copilot CLI - Update launch validation to not require model selection for GitHub Copilot CLI - Fix prepareLaunchEnvironment and executeLaunch to handle no-model scenario This enables users to launch GitHub Copilot CLI directly from Cherry Studio's code tools interface without needing to select a model, as GitHub Copilot CLI uses GitHub's built-in models and authentication. Signed-off-by: LeaderOnePro * style: apply code formatting for GitHub Copilot CLI integration Auto-fix code style inconsistencies using project's Biome formatter. Resolves semicolon, comma, and quote style issues to match project standards. Signed-off-by: LeaderOnePro * feat: conditionally render model selector for GitHub Copilot CLI - Hide model selector component when GitHub Copilot CLI is selected - Maintain validation logic to allow GitHub Copilot CLI without model selection - Improve UX by removing empty model dropdown for GitHub Copilot CLI 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Signed-off-by: LeaderOnePro Co-authored-by: Claude --- packages/shared/config/constant.ts | 3 +- src/main/services/CodeToolsService.ts | 22 ++- src/renderer/src/hooks/useCodeTools.ts | 6 +- src/renderer/src/pages/code/CodeToolsPage.tsx | 156 ++++++++++++------ src/renderer/src/pages/code/index.ts | 10 +- src/renderer/src/store/codeTools.ts | 14 +- 6 files changed, 153 insertions(+), 58 deletions(-) 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/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/pages/code/CodeToolsPage.tsx b/src/renderer/src/pages/code/CodeToolsPage.tsx index b64833f6d6..06f2ef064b 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 531a7f5f01..06c7991039 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/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 From 932b1d529a7a351be3b58d3490be7d840411c7b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:15:14 +0800 Subject: [PATCH 03/16] ci(deps): bump actions/setup-node from 4 to 5 (#10478) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-i18n.yml | 2 +- .github/workflows/nightly-build.yml | 2 +- .github/workflows/pr-ci.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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/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 From 1e4902b267e283cc84ef58496b1a79f63fdde26b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:15:38 +0800 Subject: [PATCH 04/16] ci(deps): bump actions/checkout from 4 to 5 (#10479) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/claude-code-review.yml | 2 +- .github/workflows/claude-translator.yml | 2 +- .github/workflows/claude.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 From 74db4c46466db3e7ebbd670b99dc5d99aac6e185 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:16:02 +0800 Subject: [PATCH 05/16] ci(deps): bump actions/github-script from 7 to 8 (#10480) Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/delete-branch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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({ From f91e7da0a1fbd2ef794543b732171e5affb26619 Mon Sep 17 00:00:00 2001 From: Tristan Zhang <82869104+ABucket@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:09:11 +0800 Subject: [PATCH 06/16] feat: add notes export (#10488) * feat: add notes export * chore: fix lint error * feat: unified export interface for notes * fix: hide export reasoning when exporting notes * chore: fix lint error * chore: remove debug log --- .../src/components/ObsidianExportDialog.tsx | 17 +++--- .../components/Popups/ObsidianExportPopup.tsx | 2 + src/renderer/src/pages/notes/NotesSidebar.tsx | 53 +++++++++++++++++-- src/renderer/src/utils/export.ts | 48 +++++++++++++++++ 4 files changed, 111 insertions(+), 9 deletions(-) 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/pages/notes/NotesSidebar.tsx b/src/renderer/src/pages/notes/NotesSidebar.tsx index 4588c37611..54a3f80cdb 100644 --- a/src/renderer/src/pages/notes/NotesSidebar.tsx +++ b/src/renderer/src/pages/notes/NotesSidebar.tsx @@ -6,11 +6,13 @@ 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 { 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, @@ -21,10 +23,12 @@ import { Folder, FolderOpen, 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 { @@ -213,6 +217,7 @@ 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 [draggedNodeId, setDraggedNodeId] = useState(null) const [dragOverNodeId, setDragOverNodeId] = useState(null) @@ -525,6 +530,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 +590,7 @@ const NotesSidebar: FC = ({ return baseMenuItems }, - [t, handleStartEdit, onToggleStar, handleExportKnowledge, handleDeleteNode] + [t, handleStartEdit, onToggleStar, handleExportKnowledge, handleDeleteNode, exportMenuOptions] ) const handleDropFiles = useCallback( 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 + } +} From 53e38ed1aa8bf04d38f1d6a6cfbeb0855d376afb Mon Sep 17 00:00:00 2001 From: purefkh Date: Thu, 2 Oct 2025 17:45:42 +0800 Subject: [PATCH 07/16] feat(models): update Gemini regex (#10463) * feat(models): update Gemini regex * fix: lint * fix format --- src/renderer/src/config/models/reasoning.ts | 6 +++++- src/renderer/src/config/models/vision.ts | 1 + src/renderer/src/config/models/websearch.ts | 7 +++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 3f55f13a3a..545d759363 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 } diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index 02ed3cd6fc..85eca139c4 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', 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', From e7e5c0456f7fab4d289cd23c53cf4bc0d61c420d Mon Sep 17 00:00:00 2001 From: Tristan Zhang <82869104+ABucket@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:45:46 +0800 Subject: [PATCH 08/16] feat: allowing notes to be renamed using LLM (#10487) * feat: implement auto-renaming feature for notes * feat: motion effects for auto renaming in notes * feat: add i18n for zh-tw for auto renaming in notes * chore: lint --- src/renderer/src/i18n/locales/en-us.json | 6 + src/renderer/src/i18n/locales/zh-cn.json | 6 + src/renderer/src/i18n/locales/zh-tw.json | 6 + src/renderer/src/pages/notes/NotesSidebar.tsx | 137 +++++++++++++++++- src/renderer/src/services/ApiService.ts | 62 ++++++++ 5 files changed, 213 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 94abc7667d..0f3d2a3f24 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1697,6 +1697,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 c09c94cbbf..ce2dc5c222 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1697,6 +1697,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 a1aa1cc7fa..e4f45288ee 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1697,6 +1697,12 @@ "provider_settings": "跳轉到服務商設置界面" }, "notes": { + "auto_rename": { + "empty_note": "筆記為空,無法生成名稱", + "failed": "生成筆記名稱失敗", + "label": "生成筆記名稱", + "success": "筆記名稱生成成功" + }, "characters": "字符", "collapse": "收起", "content_placeholder": "請輸入筆記內容...", diff --git a/src/renderer/src/pages/notes/NotesSidebar.tsx b/src/renderer/src/pages/notes/NotesSidebar.tsx index 54a3f80cdb..a53fc5b5f7 100644 --- a/src/renderer/src/pages/notes/NotesSidebar.tsx +++ b/src/renderer/src/pages/notes/NotesSidebar.tsx @@ -6,6 +6,7 @@ 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 { fetchNoteSummary } from '@renderer/services/ApiService' import { RootState, useAppSelector } from '@renderer/store' import { selectSortType } from '@renderer/store/note' import { NotesSortType, NotesTreeNode } from '@renderer/types/note' @@ -22,6 +23,7 @@ import { FileSearch, Folder, FolderOpen, + Sparkles, Star, StarOff, UploadIcon @@ -54,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' @@ -76,6 +80,8 @@ const TreeNode = memo( selectedFolderId, activeNodeId, editingNodeId, + renamingNodeIds, + newlyRenamedNodeIds, draggedNodeId, dragOverNodeId, dragPosition, @@ -96,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 @@ -103,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 (
@@ -160,7 +174,7 @@ const TreeNode = memo( size="small" /> ) : ( - {node.name} + {node.name} )} @@ -177,6 +191,8 @@ const TreeNode = memo( selectedFolderId={selectedFolderId} activeNodeId={activeNodeId} editingNodeId={editingNodeId} + renamingNodeIds={renamingNodeIds} + newlyRenamedNodeIds={newlyRenamedNodeIds} draggedNodeId={draggedNodeId} dragOverNodeId={dragOverNodeId} dragPosition={dragPosition} @@ -219,6 +235,8 @@ const NotesSidebar: FC = ({ 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') @@ -341,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' @@ -495,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', @@ -512,7 +588,7 @@ const NotesSidebar: FC = ({ window.api.openPath(node.externalPath) } } - ] + ) if (node.type !== 'folder') { baseMenuItems.push( { @@ -590,7 +666,16 @@ const NotesSidebar: FC = ({ return baseMenuItems }, - [t, handleStartEdit, onToggleStar, handleExportKnowledge, handleDeleteNode, exportMenuOptions] + [ + t, + handleStartEdit, + onToggleStar, + handleExportKnowledge, + handleDeleteNode, + renamingNodeIds, + handleAutoRename, + exportMenuOptions + ] ) const handleDropFiles = useCallback( @@ -727,6 +812,8 @@ const NotesSidebar: FC = ({ selectedFolderId={selectedFolderId} activeNodeId={activeNode?.id} editingNodeId={editingNodeId} + renamingNodeIds={renamingNodeIds} + newlyRenamedNodeIds={newlyRenamedNodeIds} draggedNodeId={draggedNodeId} dragOverNodeId={dragOverNodeId} dragPosition={dragPosition} @@ -771,6 +858,8 @@ const NotesSidebar: FC = ({ selectedFolderId={selectedFolderId} activeNodeId={activeNode?.id} editingNodeId={editingNodeId} + renamingNodeIds={renamingNodeIds} + newlyRenamedNodeIds={newlyRenamedNodeIds} draggedNodeId={draggedNodeId} dragOverNodeId={dragOverNodeId} dragPosition={dragPosition} @@ -793,6 +882,8 @@ const NotesSidebar: FC = ({ selectedFolderId={selectedFolderId} activeNodeId={activeNode?.id} editingNodeId={editingNodeId} + renamingNodeIds={renamingNodeIds} + newlyRenamedNodeIds={newlyRenamedNodeIds} draggedNodeId={draggedNodeId} dragOverNodeId={dragOverNodeId} dragPosition={dragPosition} @@ -980,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) From b7e7174f3d6261cf9b9fa9b39f15d1fd91ad5cdb Mon Sep 17 00:00:00 2001 From: Tristan Zhang <82869104+ABucket@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:56:53 +0800 Subject: [PATCH 09/16] feat: add middle-click tab closing (#10498) --- src/renderer/src/components/Tab/TabContainer.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/Tab/TabContainer.tsx b/src/renderer/src/components/Tab/TabContainer.tsx index 3930819c6e..a83621f4b0 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)} From 2aedbf5702aed4df2ece6aaff2abba56c9683c44 Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:36:18 +0800 Subject: [PATCH 10/16] fix(reasoning): support deepseek v3.2, claude 4.5, glm 4.6 (#10475) * fix(reasoning): update deepseek model id regex pattern to match more variants The previous regex pattern was too restrictive and didn't account for all possible deepseek model id formats. This change expands the pattern to support more variants while maintaining the same functionality. * fix(reasoning): update deepseek model id regex pattern to match more variants * fix(reasoning): improve regex pattern for deepseek model matching Update the regex pattern to be more precise in matching deepseek model versions. Add detailed comments explaining the pattern and note future improvements. * feat(models): add GLM-4.6 model to supported list Update model configuration to include new GLM-4.6 model and add it to the supported models for thinking token functionality * feat(models): add claude sonnet 4.5 model to anthropic provider --- src/renderer/src/config/models/default.ts | 12 ++++++++++++ src/renderer/src/config/models/reasoning.ts | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) 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 545d759363..10cb64156b 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -339,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 From a436ab1d785ee102d49c5917785dd8343c6eae21 Mon Sep 17 00:00:00 2001 From: Tristan Zhang <82869104+ABucket@users.noreply.github.com> Date: Fri, 3 Oct 2025 19:23:49 +0800 Subject: [PATCH 11/16] fix(TextFilePreview): make editor read-only but can be copied (#10499) * fix(TextFilePreview): make editor read-only but can be copied * feat: add table auto-wrap feature for notes * Revert "feat: add table auto-wrap feature for notes" This reverts commit 7785f480b1f585a27e08f6fbf1829a30cad97a4a. --- src/renderer/src/components/Popups/TextFilePreview.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/Popups/TextFilePreview.tsx b/src/renderer/src/components/Popups/TextFilePreview.tsx index f5fa787d0b..9c74eef0d1 100644 --- a/src/renderer/src/components/Popups/TextFilePreview.tsx +++ b/src/renderer/src/components/Popups/TextFilePreview.tsx @@ -1,3 +1,4 @@ +import { EditorState } from '@codemirror/state' import { Modal } from 'antd' import { useState } from 'react' import styled from 'styled-components' @@ -55,12 +56,12 @@ const PopupContainer: React.FC = ({ text, title, extension, resolve }) => footer={null}> {extension !== undefined ? ( ) : ( {text} From 78eacccf6efa9c82236f9c05f9e6e2cf29f3b8f5 Mon Sep 17 00:00:00 2001 From: PP Kun Date: Sat, 4 Oct 2025 12:42:07 +0800 Subject: [PATCH 12/16] chore(build): Upgrade electron (#10525) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 electron 从 37.4.0 升级到 37.6.0 - 解决旧版本导致macOS 26卡顿问题 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 907f872009..ce586b5afe 100644 --- a/package.json +++ b/package.json @@ -238,7 +238,7 @@ "docx": "^9.0.2", "dompurify": "^3.2.6", "dotenv-cli": "^7.4.2", - "electron": "37.4.0", + "electron": "37.6.0", "electron-builder": "26.0.15", "electron-devtools-installer": "^3.2.0", "electron-store": "^8.2.0", diff --git a/yarn.lock b/yarn.lock index 93029954a6..afb54da011 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16809,16 +16809,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 From 2048f210e7fbacf71c8f761be98b63a74084a83a Mon Sep 17 00:00:00 2001 From: one Date: Sat, 4 Oct 2025 23:24:50 +0800 Subject: [PATCH 13/16] feat(CodeEditor): add a prop to enable the readOnly extension (#10516) * feat(CodeEditor): add a prop to enable the readOnly extension * feat: enable keymap for TextFilePreview --- src/renderer/src/components/CodeEditor/index.tsx | 9 ++++++++- src/renderer/src/components/Popups/TextFilePreview.tsx | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) 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/Popups/TextFilePreview.tsx b/src/renderer/src/components/Popups/TextFilePreview.tsx index 9c74eef0d1..584929401c 100644 --- a/src/renderer/src/components/Popups/TextFilePreview.tsx +++ b/src/renderer/src/components/Popups/TextFilePreview.tsx @@ -1,4 +1,3 @@ -import { EditorState } from '@codemirror/state' import { Modal } from 'antd' import { useState } from 'react' import styled from 'styled-components' @@ -56,12 +55,15 @@ const PopupContainer: React.FC = ({ text, title, extension, resolve }) => footer={null}> {extension !== undefined ? ( ) : ( {text} From fcf53f06efa8780451a4c1b2e0ec01e994a68886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=BF=E9=80=94=E9=A3=8E=E6=B5=AA?= <116719594+FLC-ytfl@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:39:32 +0800 Subject: [PATCH 14/16] fix(models vision) (#10530) --- src/renderer/src/config/models/vision.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index 85eca139c4..c7d78b90ab 100644 --- a/src/renderer/src/config/models/vision.ts +++ b/src/renderer/src/config/models/vision.ts @@ -22,7 +22,9 @@ const visionAllowedModels = [ 'qwen-vl', 'qwen2-vl', 'qwen2.5-vl', + 'qwen3-vl', 'qwen2.5-omni', + 'qwen3-omni', 'qvq', 'internvl2', 'grok-vision-beta', From 8bec7640fa7002c77130d56065cda2cf31d8b51d Mon Sep 17 00:00:00 2001 From: rebecca554owen Date: Mon, 6 Oct 2025 22:19:09 +0800 Subject: [PATCH 15/16] fix(metrics): restore first token latency reporting (#10538) --- .../src/aiCore/chunk/AiSdkToChunkAdapter.ts | 96 +++++++++++++------ 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts b/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts index fb68cedb23..6d7070ce85 100644 --- a/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts +++ b/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts @@ -22,6 +22,8 @@ export class AiSdkToChunkAdapter { private accumulate: boolean | undefined private isFirstChunk = true private enableWebSearch: boolean = false + private responseStartTimestamp: number | null = null + private firstTokenTimestamp: number | null = null constructor( private onChunk: (chunk: Chunk) => void, @@ -34,6 +36,17 @@ export class AiSdkToChunkAdapter { this.enableWebSearch = enableWebSearch || false } + 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 的流结果对象 @@ -61,6 +74,8 @@ export class AiSdkToChunkAdapter { webSearchResults: [], reasoningId: '' } + this.resetTimingState() + this.responseStartTimestamp = Date.now() // Reset link converter state at the start of stream this.isFirstChunk = true @@ -73,6 +88,7 @@ export class AiSdkToChunkAdapter { if (this.enableWebSearch) { const remainingText = flushLinkConverterBuffer() if (remainingText) { + this.markFirstTokenIfNeeded() this.onChunk({ type: ChunkType.TEXT_DELTA, text: remainingText @@ -87,6 +103,7 @@ export class AiSdkToChunkAdapter { } } finally { reader.releaseLock() + this.resetTimingState() } } @@ -137,6 +154,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 @@ -161,6 +179,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 || '' @@ -260,44 +281,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': @@ -333,6 +347,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 From d2d5064eedef411eade75e099fabf819a23e5893 Mon Sep 17 00:00:00 2001 From: Murphy <69335326+MurphyLo@users.noreply.github.com> Date: Tue, 7 Oct 2025 00:02:48 +0800 Subject: [PATCH 16/16] fix: forked topic and rename modal retaining old name after rename (#10528) fix: sync active topic metadata after rename --- src/renderer/src/hooks/useTopic.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 } }