diff --git a/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.28-81647ffac6.patch b/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.28-81647ffac6.patch deleted file mode 100644 index 106b45a12..000000000 --- a/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.28-81647ffac6.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/markdown-loader.js b/src/markdown-loader.js -index eaf30b114a273e68abbb92c8b07018495e63f4cb..4b06519bdb51845e4693fe877da9de01c7a81039 100644 ---- a/src/markdown-loader.js -+++ b/src/markdown-loader.js -@@ -21,7 +21,7 @@ export class MarkdownLoader extends BaseLoader { - ? (await getSafe(this.filePathOrUrl, { format: 'buffer' })).body - : await streamToBuffer(fs.createReadStream(this.filePathOrUrl)); - this.debug('MarkdownLoader stream created'); -- const result = micromark(buffer, { extensions: [gfm(), mdxJsx()], htmlExtensions: [gfmHtml()] }); -+ const result = micromark(buffer, { extensions: [gfm()], htmlExtensions: [gfmHtml()] }); - this.debug('Markdown parsed...'); - const webLoader = new WebLoader({ - urlOrContent: result, diff --git a/.yarn/patches/@llm-tools-embedjs-npm-0.1.28-8e4393fa2d.patch b/.yarn/patches/@llm-tools-embedjs-npm-0.1.28-8e4393fa2d.patch deleted file mode 100644 index 153681e5e..000000000 --- a/.yarn/patches/@llm-tools-embedjs-npm-0.1.28-8e4393fa2d.patch +++ /dev/null @@ -1,158 +0,0 @@ -diff --git a/src/loaders/local-path-loader.d.ts b/src/loaders/local-path-loader.d.ts -index 48c20e68c469cd309be2dc8f28e44c1bd04a26e9..1c16d83bcbf9b7140292793d6cbb8c04281949d9 100644 ---- a/src/loaders/local-path-loader.d.ts -+++ b/src/loaders/local-path-loader.d.ts -@@ -4,8 +4,10 @@ export declare class LocalPathLoader extends BaseLoader<{ - }> { - private readonly debug; - private readonly path; -- constructor({ path }: { -+ constructor({ path, chunkSize, chunkOverlap }: { - path: string; -+ chunkSize?: number; -+ chunkOverlap?: number; - }); - getUnfilteredChunks(): AsyncGenerator<{ - metadata: { -diff --git a/src/loaders/local-path-loader.js b/src/loaders/local-path-loader.js -index 4cf8a6bd1d890244c8ec49d4a05ee3bd58861c79..ec8215b01195a21ef20f3c5d56ecc99f186bb596 100644 ---- a/src/loaders/local-path-loader.js -+++ b/src/loaders/local-path-loader.js -@@ -8,8 +8,8 @@ import { BaseLoader } from '@llm-tools/embedjs-interfaces'; - export class LocalPathLoader extends BaseLoader { - debug = createDebugMessages('embedjs:loader:LocalPathLoader'); - path; -- constructor({ path }) { -- super(`LocalPathLoader_${md5(path)}`, { path }); -+ constructor({ path, chunkSize, chunkOverlap }) { -+ super(`LocalPathLoader_${md5(path)}`, { path }, chunkSize ?? 1000, chunkOverlap ?? 0); - this.path = path; - } - async *getUnfilteredChunks() { -@@ -36,10 +36,12 @@ export class LocalPathLoader extends BaseLoader { - const extension = currentPath.split('.').pop().toLowerCase(); - if (extension === 'md' || extension === 'mdx') - mime = 'text/markdown'; -+ if (extension === 'txt') -+ mime = 'text/plain'; - this.debug(`File '${this.path}' mime type updated to 'text/markdown'`); - } - try { -- const loader = await createLoaderFromMimeType(currentPath, mime); -+ const loader = await createLoaderFromMimeType(currentPath, mime, this.chunkSize, this.chunkOverlap); - for await (const result of await loader.getUnfilteredChunks()) { - yield { - pageContent: result.pageContent, -diff --git a/src/util/mime.d.ts b/src/util/mime.d.ts -index 57f56a1b8edc98366af9f84d671676c41c2f01ca..14be3b5727cff6eb1978838045e9a788f8f53bfb 100644 ---- a/src/util/mime.d.ts -+++ b/src/util/mime.d.ts -@@ -1,2 +1,2 @@ - import { BaseLoader } from '@llm-tools/embedjs-interfaces'; --export declare function createLoaderFromMimeType(loaderData: string, mimeType: string): Promise; -+export declare function createLoaderFromMimeType(loaderData: string, mimeType: string, chunkSize?: number, chunkOverlap?: number): Promise; -diff --git a/src/util/mime.js b/src/util/mime.js -index b6426a859968e2bf6206795f70333e90ae27aeb7..16ae2adb863f8d7abfa757f1c5cc39f6bb1c44fa 100644 ---- a/src/util/mime.js -+++ b/src/util/mime.js -@@ -1,7 +1,9 @@ - import mime from 'mime'; - import createDebugMessages from 'debug'; - import { TextLoader } from '../loaders/text-loader.js'; --export async function createLoaderFromMimeType(loaderData, mimeType) { -+import fs from 'node:fs' -+ -+export async function createLoaderFromMimeType(loaderData, mimeType, chunkSize, chunkOverlap) { - createDebugMessages('embedjs:util:createLoaderFromMimeType')(`Incoming mime type '${mimeType}'`); - switch (mimeType) { - case 'application/msword': -@@ -10,7 +12,7 @@ export async function createLoaderFromMimeType(loaderData, mimeType) { - throw new Error('Package `@llm-tools/embedjs-loader-msoffice` needs to be installed to load docx files'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported DocxLoader'); -- return new DocxLoader({ filePathOrUrl: loaderData }); -+ return new DocxLoader({ filePathOrUrl: loaderData, chunkSize, chunkOverlap }); - } - case 'application/vnd.ms-excel': - case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { -@@ -18,21 +20,21 @@ export async function createLoaderFromMimeType(loaderData, mimeType) { - throw new Error('Package `@llm-tools/embedjs-loader-msoffice` needs to be installed to load excel files'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported ExcelLoader'); -- return new ExcelLoader({ filePathOrUrl: loaderData }); -+ return new ExcelLoader({ filePathOrUrl: loaderData, chunkSize, chunkOverlap }); - } - case 'application/pdf': { - const { PdfLoader } = await import('@llm-tools/embedjs-loader-pdf').catch(() => { - throw new Error('Package `@llm-tools/embedjs-loader-pdf` needs to be installed to load PDF files'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported PdfLoader'); -- return new PdfLoader({ filePathOrUrl: loaderData }); -+ return new PdfLoader({ filePathOrUrl: loaderData, chunkSize, chunkOverlap }); - } - case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': { - const { PptLoader } = await import('@llm-tools/embedjs-loader-msoffice').catch(() => { - throw new Error('Package `@llm-tools/embedjs-loader-msoffice` needs to be installed to load pptx files'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported PptLoader'); -- return new PptLoader({ filePathOrUrl: loaderData }); -+ return new PptLoader({ filePathOrUrl: loaderData, chunkSize, chunkOverlap }); - } - case 'text/plain': { - const fineType = mime.getType(loaderData); -@@ -42,24 +44,24 @@ export async function createLoaderFromMimeType(loaderData, mimeType) { - throw new Error('Package `@llm-tools/embedjs-loader-csv` needs to be installed to load CSV files'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported CsvLoader'); -- return new CsvLoader({ filePathOrUrl: loaderData }); -+ return new CsvLoader({ filePathOrUrl: loaderData, chunkSize, chunkOverlap }); - } -- else -- return new TextLoader({ text: loaderData }); -+ const content = fs.readFileSync(loaderData, 'utf-8'); -+ return new TextLoader({ text: content, chunkSize, chunkOverlap }); - } - case 'application/csv': { - const { CsvLoader } = await import('@llm-tools/embedjs-loader-csv').catch(() => { - throw new Error('Package `@llm-tools/embedjs-loader-csv` needs to be installed to load CSV files'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported CsvLoader'); -- return new CsvLoader({ filePathOrUrl: loaderData }); -+ return new CsvLoader({ filePathOrUrl: loaderData, chunkSize, chunkOverlap }); - } - case 'text/html': { - const { WebLoader } = await import('@llm-tools/embedjs-loader-web').catch(() => { - throw new Error('Package `@llm-tools/embedjs-loader-web` needs to be installed to load web documents'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported WebLoader'); -- return new WebLoader({ urlOrContent: loaderData }); -+ return new WebLoader({ urlOrContent: loaderData, chunkSize, chunkOverlap }); - } - case 'text/xml': { - const { SitemapLoader } = await import('@llm-tools/embedjs-loader-sitemap').catch(() => { -@@ -67,14 +69,14 @@ export async function createLoaderFromMimeType(loaderData, mimeType) { - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported SitemapLoader'); - if (await SitemapLoader.test(loaderData)) { -- return new SitemapLoader({ url: loaderData }); -+ return new SitemapLoader({ url: loaderData, chunkSize, chunkOverlap }); - } - //This is not a Sitemap but is still XML - const { XmlLoader } = await import('@llm-tools/embedjs-loader-xml').catch(() => { - throw new Error('Package `@llm-tools/embedjs-loader-xml` needs to be installed to load XML documents'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported XmlLoader'); -- return new XmlLoader({ filePathOrUrl: loaderData }); -+ return new XmlLoader({ filePathOrUrl: loaderData, chunkSize, chunkOverlap }); - } - case 'text/x-markdown': - case 'text/markdown': { -@@ -82,7 +84,7 @@ export async function createLoaderFromMimeType(loaderData, mimeType) { - throw new Error('Package `@llm-tools/embedjs-loader-markdown` needs to be installed to load markdown files'); - }); - createDebugMessages('embedjs:util:createLoaderFromMimeType')('Dynamically imported MarkdownLoader'); -- return new MarkdownLoader({ filePathOrUrl: loaderData }); -+ return new MarkdownLoader({ filePathOrUrl: loaderData, chunkSize, chunkOverlap }); - } - case 'image/png': - case 'image/jpeg': { diff --git a/README.md b/README.md index 660395d3f..5335146c9 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,9 @@ https://docs.cherry-ai.com # 🌈 Theme -Theme Gallery: https://cherrycss.com -Aero Theme: https://github.com/hakadao/CherryStudio-Aero +- Theme Gallery: https://cherrycss.com +- Aero Theme: https://github.com/hakadao/CherryStudio-Aero +- PaperMaterial Theme: https://github.com/rainoffallingstar/CherryStudio-PaperMaterial Welcome PR for more themes diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 7ba2b7ee9..8c9efbe68 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -12,16 +12,16 @@ export default defineConfig({ plugins: [ externalizeDepsPlugin({ exclude: [ - '@llm-tools/embedjs', - '@llm-tools/embedjs-openai', - '@llm-tools/embedjs-loader-web', - '@llm-tools/embedjs-loader-markdown', - '@llm-tools/embedjs-loader-msoffice', - '@llm-tools/embedjs-loader-xml', - '@llm-tools/embedjs-loader-pdf', - '@llm-tools/embedjs-loader-sitemap', - '@llm-tools/embedjs-libsql', - '@llm-tools/embedjs-loader-image', + '@cherrystudio/embedjs', + '@cherrystudio/embedjs-openai', + '@cherrystudio/embedjs-loader-web', + '@cherrystudio/embedjs-loader-markdown', + '@cherrystudio/embedjs-loader-msoffice', + '@cherrystudio/embedjs-loader-xml', + '@cherrystudio/embedjs-loader-pdf', + '@cherrystudio/embedjs-loader-sitemap', + '@cherrystudio/embedjs-libsql', + '@cherrystudio/embedjs-loader-image', 'p-queue', 'webdav' ] diff --git a/package.json b/package.json index 7cbf7d9de..2830bec15 100644 --- a/package.json +++ b/package.json @@ -50,21 +50,22 @@ "prepare": "husky" }, "dependencies": { + "@cherrystudio/embedjs": "^0.1.28", + "@cherrystudio/embedjs-libsql": "^0.1.28", + "@cherrystudio/embedjs-loader-csv": "^0.1.28", + "@cherrystudio/embedjs-loader-image": "^0.1.28", + "@cherrystudio/embedjs-loader-markdown": "^0.1.28", + "@cherrystudio/embedjs-loader-msoffice": "^0.1.28", + "@cherrystudio/embedjs-loader-pdf": "^0.1.28", + "@cherrystudio/embedjs-loader-sitemap": "^0.1.28", + "@cherrystudio/embedjs-loader-web": "^0.1.28", + "@cherrystudio/embedjs-loader-xml": "^0.1.28", + "@cherrystudio/embedjs-openai": "^0.1.28", "@cherrystudio/mac-system-ocr": "^0.1.0", "@electron-toolkit/utils": "^3.0.0", "@electron/notarize": "^2.5.0", "@google/generative-ai": "^0.21.0", "@langchain/community": "^0.3.36", - "@llm-tools/embedjs": "patch:@llm-tools/embedjs@npm%3A0.1.28#~/.yarn/patches/@llm-tools-embedjs-npm-0.1.28-8e4393fa2d.patch", - "@llm-tools/embedjs-libsql": "^0.1.28", - "@llm-tools/embedjs-loader-csv": "^0.1.28", - "@llm-tools/embedjs-loader-markdown": "patch:@llm-tools/embedjs-loader-markdown@npm%3A0.1.28#~/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.28-81647ffac6.patch", - "@llm-tools/embedjs-loader-msoffice": "^0.1.28", - "@llm-tools/embedjs-loader-pdf": "^0.1.28", - "@llm-tools/embedjs-loader-sitemap": "^0.1.28", - "@llm-tools/embedjs-loader-web": "^0.1.28", - "@llm-tools/embedjs-loader-xml": "^0.1.28", - "@llm-tools/embedjs-openai": "^0.1.28", "@mistralai/mistralai": "^1.5.2", "@modelcontextprotocol/sdk": "patch:@modelcontextprotocol/sdk@npm%3A1.6.1#~/.yarn/patches/@modelcontextprotocol-sdk-npm-1.6.1-b46313efe7.patch", "@notionhq/client": "^2.2.15", @@ -95,6 +96,7 @@ "@agentic/exa": "^7.3.3", "@agentic/searxng": "^7.3.3", "@agentic/tavily": "^7.3.3", + "@ant-design/v5-patch-for-react-19": "^1.0.3", "@anthropic-ai/sdk": "^0.38.0", "@electron-toolkit/eslint-config-prettier": "^3.0.0", "@electron-toolkit/eslint-config-ts": "^3.0.0", @@ -106,7 +108,6 @@ "@google/genai": "^0.4.0", "@hello-pangea/dnd": "^16.6.0", "@kangfenmao/keyv-storage": "^0.1.0", - "@llm-tools/embedjs-loader-image": "^0.1.28", "@notionhq/client": "^2.2.15", "@reduxjs/toolkit": "^2.2.5", "@tavily/core": "patch:@tavily/core@npm%3A0.3.1#~/.yarn/patches/@tavily-core-npm-0.3.1-fe69bf2bea.patch", @@ -119,8 +120,8 @@ "@types/node": "^18.19.9", "@types/pako": "^1.0.2", "@types/pdf-parse": "^1.1.4", - "@types/react": "^18.2.48", - "@types/react-dom": "^18.2.18", + "@types/react": "^19.0.12", + "@types/react-dom": "^19.0.4", "@types/react-infinite-scroll-component": "^5.0.0", "@types/tinycolor2": "^1", "@vitejs/plugin-react": "^4.2.1", @@ -154,8 +155,9 @@ "openai": "patch:openai@npm%3A4.77.3#~/.yarn/patches/openai-npm-4.77.3-59c6d42e7a.patch", "p-queue": "^8.1.0", "prettier": "^3.5.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "rc-virtual-list": "^3.18.5", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-hotkeys-hook": "^4.6.1", "react-i18next": "^14.1.2", "react-infinite-scroll-component": "^6.1.0", @@ -182,10 +184,6 @@ "uuid": "^10.0.0", "vite": "^5.0.12" }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, "resolutions": { "pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch", "@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch", diff --git a/src/main/embeddings/Embeddings.ts b/src/main/embeddings/Embeddings.ts index 1cbbce9ee..cf354450e 100644 --- a/src/main/embeddings/Embeddings.ts +++ b/src/main/embeddings/Embeddings.ts @@ -1,4 +1,4 @@ -import type { BaseEmbeddings } from '@llm-tools/embedjs-interfaces' +import type { BaseEmbeddings } from '@cherrystudio/embedjs-interfaces' import { KnowledgeBaseParams } from '@types' import EmbeddingsFactory from './EmbeddingsFactory' diff --git a/src/main/embeddings/EmbeddingsFactory.ts b/src/main/embeddings/EmbeddingsFactory.ts index 6524f9b97..5924d00d7 100644 --- a/src/main/embeddings/EmbeddingsFactory.ts +++ b/src/main/embeddings/EmbeddingsFactory.ts @@ -1,6 +1,6 @@ -import type { BaseEmbeddings } from '@llm-tools/embedjs-interfaces' -import { OpenAiEmbeddings } from '@llm-tools/embedjs-openai' -import { AzureOpenAiEmbeddings } from '@llm-tools/embedjs-openai/src/azure-openai-embeddings' +import type { BaseEmbeddings } from '@cherrystudio/embedjs-interfaces' +import { OpenAiEmbeddings } from '@cherrystudio/embedjs-openai' +import { AzureOpenAiEmbeddings } from '@cherrystudio/embedjs-openai/src/azure-openai-embeddings' import { getInstanceName } from '@main/utils' import { KnowledgeBaseParams } from '@types' diff --git a/src/main/embeddings/VoyageEmbeddings.ts b/src/main/embeddings/VoyageEmbeddings.ts index 7635c1146..ce21afe58 100644 --- a/src/main/embeddings/VoyageEmbeddings.ts +++ b/src/main/embeddings/VoyageEmbeddings.ts @@ -1,5 +1,5 @@ +import { BaseEmbeddings } from '@cherrystudio/embedjs-interfaces' import { VoyageEmbeddings as _VoyageEmbeddings } from '@langchain/community/embeddings/voyage' -import { BaseEmbeddings } from '@llm-tools/embedjs-interfaces' export default class VoyageEmbeddings extends BaseEmbeddings { private model: _VoyageEmbeddings diff --git a/src/main/loader/draftsExportLoader.ts b/src/main/loader/draftsExportLoader.ts index 4ad1806ba..5213b4370 100644 --- a/src/main/loader/draftsExportLoader.ts +++ b/src/main/loader/draftsExportLoader.ts @@ -1,6 +1,6 @@ import * as fs from 'node:fs' -import { JsonLoader } from '@llm-tools/embedjs' +import { JsonLoader } from '@cherrystudio/embedjs' /** * Drafts 应用导出的笔记文件加载器 diff --git a/src/main/loader/epubLoader.ts b/src/main/loader/epubLoader.ts index ad2e92baa..bb62cba8c 100644 --- a/src/main/loader/epubLoader.ts +++ b/src/main/loader/epubLoader.ts @@ -1,6 +1,6 @@ +import { BaseLoader } from '@cherrystudio/embedjs-interfaces' +import { cleanString } from '@cherrystudio/embedjs-utils' import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters' -import { BaseLoader } from '@llm-tools/embedjs-interfaces' -import { cleanString } from '@llm-tools/embedjs-utils' import { getTempDir } from '@main/utils/file' import Logger from 'electron-log' import EPub from 'epub' diff --git a/src/main/loader/index.ts b/src/main/loader/index.ts index 5c2896328..db837f414 100644 --- a/src/main/loader/index.ts +++ b/src/main/loader/index.ts @@ -1,8 +1,8 @@ import * as fs from 'node:fs' -import { JsonLoader, LocalPathLoader, RAGApplication, TextLoader } from '@llm-tools/embedjs' -import type { AddLoaderReturn } from '@llm-tools/embedjs-interfaces' -import { WebLoader } from '@llm-tools/embedjs-loader-web' +import { JsonLoader, LocalPathLoader, RAGApplication, TextLoader } from '@cherrystudio/embedjs' +import type { AddLoaderReturn } from '@cherrystudio/embedjs-interfaces' +import { WebLoader } from '@cherrystudio/embedjs-loader-web' import { LoaderReturn } from '@shared/config/types' import { FileType, KnowledgeBaseParams } from '@types' import Logger from 'electron-log' diff --git a/src/main/loader/odLoader.ts b/src/main/loader/odLoader.ts index 286fe34ea..7e13420b2 100644 --- a/src/main/loader/odLoader.ts +++ b/src/main/loader/odLoader.ts @@ -1,6 +1,6 @@ +import { BaseLoader } from '@cherrystudio/embedjs-interfaces' +import { cleanString } from '@cherrystudio/embedjs-utils' import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters' -import { BaseLoader } from '@llm-tools/embedjs-interfaces' -import { cleanString } from '@llm-tools/embedjs-utils' import md5 from 'md5' import { OfficeParserConfig, parseOfficeAsync } from 'officeparser' diff --git a/src/main/reranker/BaseReranker.ts b/src/main/reranker/BaseReranker.ts index d31bb40b2..543829089 100644 --- a/src/main/reranker/BaseReranker.ts +++ b/src/main/reranker/BaseReranker.ts @@ -1,4 +1,4 @@ -import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' +import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { KnowledgeBaseParams } from '@types' export default abstract class BaseReranker { diff --git a/src/main/reranker/DefaultReranker.ts b/src/main/reranker/DefaultReranker.ts index aac0c65e8..70a4d05ac 100644 --- a/src/main/reranker/DefaultReranker.ts +++ b/src/main/reranker/DefaultReranker.ts @@ -1,4 +1,4 @@ -import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' +import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { KnowledgeBaseParams } from '@types' import BaseReranker from './BaseReranker' diff --git a/src/main/reranker/JinaReranker.ts b/src/main/reranker/JinaReranker.ts index 3dfede195..ed14f4746 100644 --- a/src/main/reranker/JinaReranker.ts +++ b/src/main/reranker/JinaReranker.ts @@ -1,4 +1,4 @@ -import { ExtractChunkData } from '@llm-tools/embedjs-interfaces' +import { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { KnowledgeBaseParams } from '@types' import axios from 'axios' diff --git a/src/main/reranker/Reranker.ts b/src/main/reranker/Reranker.ts index c07b1567e..f9f37cfca 100644 --- a/src/main/reranker/Reranker.ts +++ b/src/main/reranker/Reranker.ts @@ -1,4 +1,4 @@ -import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' +import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { KnowledgeBaseParams } from '@types' import BaseReranker from './BaseReranker' diff --git a/src/main/reranker/SiliconFlowReranker.ts b/src/main/reranker/SiliconFlowReranker.ts index 1e47cca9c..bba8d5405 100644 --- a/src/main/reranker/SiliconFlowReranker.ts +++ b/src/main/reranker/SiliconFlowReranker.ts @@ -1,4 +1,4 @@ -import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' +import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { KnowledgeBaseParams } from '@types' import axios from 'axios' diff --git a/src/main/reranker/VoyageReranker.ts b/src/main/reranker/VoyageReranker.ts index 710578829..c48fbb9bc 100644 --- a/src/main/reranker/VoyageReranker.ts +++ b/src/main/reranker/VoyageReranker.ts @@ -1,4 +1,4 @@ -import { ExtractChunkData } from '@llm-tools/embedjs-interfaces' +import { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { KnowledgeBaseParams } from '@types' import axios from 'axios' diff --git a/src/main/services/KnowledgeService.ts b/src/main/services/KnowledgeService.ts index 58a63d382..d78e3f81b 100644 --- a/src/main/services/KnowledgeService.ts +++ b/src/main/services/KnowledgeService.ts @@ -16,11 +16,11 @@ import * as fs from 'node:fs' import path from 'node:path' -import { RAGApplication, RAGApplicationBuilder, TextLoader } from '@llm-tools/embedjs' -import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' -import { LibSqlDb } from '@llm-tools/embedjs-libsql' -import { SitemapLoader } from '@llm-tools/embedjs-loader-sitemap' -import { WebLoader } from '@llm-tools/embedjs-loader-web' +import { RAGApplication, RAGApplicationBuilder, TextLoader } from '@cherrystudio/embedjs' +import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' +import { LibSqlDb } from '@cherrystudio/embedjs-libsql' +import { SitemapLoader } from '@cherrystudio/embedjs-loader-sitemap' +import { WebLoader } from '@cherrystudio/embedjs-loader-web' import Embeddings from '@main/embeddings/Embeddings' import { addFileLoader } from '@main/loader' import OcrProvider from '@main/ocr/OcrProvider' diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 4c437a873..4c410e320 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -1,5 +1,6 @@ +import { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { ElectronAPI } from '@electron-toolkit/preload' -import { ExtractChunkData } from '@llm-tools/embedjs-interfaces' +import type { FileMetadataResponse, ListFilesResponse, UploadFileResponse } from '@google/generative-ai/server' import type { MCPServer, MCPTool } from '@renderer/types' import { AppInfo, diff --git a/src/preload/index.ts b/src/preload/index.ts index 455479a4f..625b0c4f3 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,5 +1,5 @@ +import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { electronAPI } from '@electron-toolkit/preload' -import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, WebDavConfig } from '@types' import { contextBridge, ipcRenderer, OpenDialogOptions, shell } from 'electron' import { CreateDirectoryOptions } from 'webdav' diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index e820866b4..c5e70a920 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -21,7 +21,7 @@ import PaintingsPage from './pages/paintings/PaintingsPage' import SettingsPage from './pages/settings/SettingsPage' import TranslatePage from './pages/translate/TranslatePage' -function App(): JSX.Element { +function App(): React.ReactElement { return ( diff --git a/src/renderer/src/components/DragableList/index.tsx b/src/renderer/src/components/DragableList/index.tsx index 05ed09c42..a5c2db91a 100644 --- a/src/renderer/src/components/DragableList/index.tsx +++ b/src/renderer/src/components/DragableList/index.tsx @@ -8,6 +8,7 @@ import { OnDragStartResponder, ResponderProvided } from '@hello-pangea/dnd' +import VirtualList from 'rc-virtual-list' import { droppableReorder } from '@renderer/utils' import { FC } from 'react' @@ -47,26 +48,28 @@ const DragableList: FC> = ({ {(provided) => (
- {list.map((item, index) => { - const id = item.id || item - return ( - - {(provided) => ( -
- {children(item, index)} -
- )} -
- ) - })} + + {(item, index) => { + const id = item.id || item + return ( + + {(provided) => ( +
+ {children(item, index)} +
+ )} +
+ ) + }} +
{provided.placeholder}
)} diff --git a/src/renderer/src/components/Scrollbar/index.tsx b/src/renderer/src/components/Scrollbar/index.tsx index 0c45f7831..670e37895 100644 --- a/src/renderer/src/components/Scrollbar/index.tsx +++ b/src/renderer/src/components/Scrollbar/index.tsx @@ -1,5 +1,5 @@ import { throttle } from 'lodash' -import { FC, forwardRef, useCallback, useEffect, useRef, useState } from 'react' +import { FC, useCallback, useEffect, useRef, useState } from 'react' import styled from 'styled-components' interface Props extends React.HTMLAttributes { @@ -7,7 +7,7 @@ interface Props extends React.HTMLAttributes { ref?: any } -const Scrollbar: FC = forwardRef((props, ref) => { +const Scrollbar: FC = ({ ref, ...props }: Props & { ref?: React.RefObject }) => { const [isScrolling, setIsScrolling] = useState(false) const timeoutRef = useRef(null) @@ -35,7 +35,7 @@ const Scrollbar: FC = forwardRef((props, ref) => { {props.children} ) -}) +} const Container = styled.div<{ isScrolling: boolean; right?: boolean }>` overflow-y: auto; diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index 47c1b3f3d..db6ed9fa7 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -1,9 +1,10 @@ import { isMac } from '@renderer/config/constant' import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor' -import { FC, PropsWithChildren } from 'react' +import type { FC, PropsWithChildren } from 'react' +import type { HTMLAttributes } from 'react' import styled from 'styled-components' -type Props = PropsWithChildren & JSX.IntrinsicElements['div'] +type Props = PropsWithChildren & HTMLAttributes export const Navbar: FC = ({ children, ...props }) => { const backgroundColor = useNavBackgroundColor() diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index 5b6de28ab..bd6bb317f 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -149,6 +149,7 @@ const visionAllowedModels = [ 'qwen-vl', 'qwen2-vl', 'qwen2.5-vl', + 'qwen2.5-omni', 'qvq', 'internvl2', 'grok-vision-beta', diff --git a/src/renderer/src/context/StyleSheetManager.tsx b/src/renderer/src/context/StyleSheetManager.tsx index 04083c799..64c506bf2 100644 --- a/src/renderer/src/context/StyleSheetManager.tsx +++ b/src/renderer/src/context/StyleSheetManager.tsx @@ -1,12 +1,12 @@ import isPropValid from '@emotion/is-prop-valid' -import { ReactNode } from 'react' +import type { ReactNode } from 'react' import { StyleSheetManager as StyledComponentsStyleSheetManager } from 'styled-components' interface StyleSheetManagerProps { children: ReactNode } -const StyleSheetManager = ({ children }: StyleSheetManagerProps): JSX.Element => { +const StyleSheetManager = ({ children }: StyleSheetManagerProps): React.ReactElement => { return ( { diff --git a/src/renderer/src/context/SyntaxHighlighterProvider.tsx b/src/renderer/src/context/SyntaxHighlighterProvider.tsx index cbdee21df..bbb4624d2 100644 --- a/src/renderer/src/context/SyntaxHighlighterProvider.tsx +++ b/src/renderer/src/context/SyntaxHighlighterProvider.tsx @@ -1,16 +1,11 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { useMermaid } from '@renderer/hooks/useMermaid' import { useSettings } from '@renderer/hooks/useSettings' -import { CodeStyleVarious, ThemeMode } from '@renderer/types' -import React, { createContext, PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react' -import { - BundledLanguage, - bundledLanguages, - BundledTheme, - bundledThemes, - createHighlighter, - HighlighterGeneric -} from 'shiki' +import { type CodeStyleVarious, ThemeMode } from '@renderer/types' +import type React from 'react' +import { createContext, type PropsWithChildren, use, useCallback, useEffect, useMemo, useState } from 'react' +import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki' +import { bundledLanguages, bundledThemes, createHighlighter } from 'shiki' interface SyntaxHighlighterContextType { codeToHtml: (code: string, language: string) => Promise @@ -51,42 +46,47 @@ export const SyntaxHighlighterProvider: React.FC = ({ childre initHighlighter() }, [highlighterTheme]) - const codeToHtml = async (code: string, language: string) => { - if (!highlighter) return '' + const codeToHtml = useCallback( + async (_code: string, language: string) => { + { + if (!highlighter) return '' - const languageMap: Record = { - vab: 'vb' - } + const languageMap: Record = { + vab: 'vb' + } - const mappedLanguage = languageMap[language] || language + const mappedLanguage = languageMap[language] || language - code = code?.trimEnd() ?? '' - const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '<', '>': '>' })[char]!) + const code = _code?.trimEnd() ?? '' + const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '<', '>': '>' })[char]!) - try { - if (!highlighter.getLoadedLanguages().includes(mappedLanguage as BundledLanguage)) { - if (mappedLanguage in bundledLanguages || mappedLanguage === 'text') { - await highlighter.loadLanguage(mappedLanguage as BundledLanguage) - } else { + try { + if (!highlighter.getLoadedLanguages().includes(mappedLanguage as BundledLanguage)) { + if (mappedLanguage in bundledLanguages || mappedLanguage === 'text') { + await highlighter.loadLanguage(mappedLanguage as BundledLanguage) + } else { + return `
${escapedCode}
` + } + } + + return highlighter.codeToHtml(code, { + lang: mappedLanguage, + theme: highlighterTheme + }) + } catch (error) { + console.warn(`Error highlighting code for language '${mappedLanguage}':`, error) return `
${escapedCode}
` } } - - return highlighter.codeToHtml(code, { - lang: mappedLanguage, - theme: highlighterTheme - }) - } catch (error) { - console.warn(`Error highlighting code for language '${mappedLanguage}':`, error) - return `
${escapedCode}
` - } - } + }, + [highlighter, highlighterTheme] + ) return {children} } export const useSyntaxHighlighter = () => { - const context = useContext(SyntaxHighlighterContext) + const context = use(SyntaxHighlighterContext) if (!context) { throw new Error('useSyntaxHighlighter must be used within a SyntaxHighlighterProvider') } diff --git a/src/renderer/src/context/ThemeProvider.tsx b/src/renderer/src/context/ThemeProvider.tsx index 5a3fd812e..113898f4f 100644 --- a/src/renderer/src/context/ThemeProvider.tsx +++ b/src/renderer/src/context/ThemeProvider.tsx @@ -1,7 +1,7 @@ import { isMac } from '@renderer/config/constant' import { useSettings } from '@renderer/hooks/useSettings' import { ThemeMode } from '@renderer/types' -import React, { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react' +import React, { createContext, PropsWithChildren, use, useEffect, useState } from 'react' interface ThemeContextType { theme: ThemeMode @@ -64,4 +64,4 @@ export const ThemeProvider: React.FC = ({ children, defaultT ) } -export const useTheme = () => useContext(ThemeContext) +export const useTheme = () => use(ThemeContext) diff --git a/src/renderer/src/hooks/useMessageOperations.ts b/src/renderer/src/hooks/useMessageOperations.ts index 304142317..05d072c7e 100644 --- a/src/renderer/src/hooks/useMessageOperations.ts +++ b/src/renderer/src/hooks/useMessageOperations.ts @@ -1,17 +1,19 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' +import { estimateMessageUsage } from '@renderer/services/TokenService' import store, { useAppDispatch, useAppSelector } from '@renderer/store' import { clearStreamMessage, clearTopicMessages, commitStreamMessage, + deleteMessageAction, resendMessage, selectDisplayCount, selectTopicLoading, selectTopicMessages, setStreamMessage, setTopicLoading, - updateMessage, - updateMessages + updateMessages, + updateMessageThunk } from '@renderer/store/messages' import type { Assistant, Message, Topic } from '@renderer/types' import { abortCompletion } from '@renderer/utils/abortController' @@ -26,17 +28,15 @@ import { TopicManager } from './useTopic' */ export function useMessageOperations(topic: Topic) { const dispatch = useAppDispatch() - const messages = useAppSelector((state) => selectTopicMessages(state, topic.id)) /** * 删除单个消息 */ const deleteMessage = useCallback( - async (message: Message) => { - const newMessages = messages.filter((m) => m.id !== message.id) - await dispatch(updateMessages(topic, newMessages)) + async (id: string) => { + await dispatch(deleteMessageAction(topic, id)) }, - [dispatch, topic, messages] + [dispatch, topic] ) /** @@ -44,10 +44,9 @@ export function useMessageOperations(topic: Topic) { */ const deleteGroupMessages = useCallback( async (askId: string) => { - const newMessages = messages.filter((m) => m.askId !== askId) - await dispatch(updateMessages(topic, newMessages)) + await dispatch(deleteMessageAction(topic, askId, 'askId')) }, - [dispatch, topic, messages] + [dispatch, topic] ) /** @@ -55,13 +54,17 @@ export function useMessageOperations(topic: Topic) { */ const editMessage = useCallback( async (messageId: string, updates: Partial) => { - await dispatch( - updateMessage({ - topicId: topic.id, - messageId, - updates - }) - ) + // 如果更新包含内容变更,重新计算 token + if ('content' in updates) { + const messages = store.getState().messages.messagesByTopic[topic.id] + const message = messages?.find((m) => m.id === messageId) + if (message) { + const updatedMessage = { ...message, ...updates } + const usage = await estimateMessageUsage(updatedMessage) + updates.usage = usage + } + } + await dispatch(updateMessageThunk(topic.id, messageId, updates)) }, [dispatch, topic.id] ) @@ -148,7 +151,6 @@ export function useMessageOperations(topic: Topic) { EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT) }, []) - const loading = useAppSelector((state) => selectTopicLoading(state, topic.id)) const displayCount = useAppSelector(selectDisplayCount) // /** // * 获取当前消息列表 @@ -200,8 +202,6 @@ export function useMessageOperations(topic: Topic) { ) return { - messages, - loading, displayCount, updateMessages: updateMessagesAction, deleteMessage, @@ -219,3 +219,13 @@ export function useMessageOperations(topic: Topic) { resumeMessage } } + +export const useTopicMessages = (topic: Topic) => { + const messages = useAppSelector((state) => selectTopicMessages(state, topic.id)) + return messages +} + +export const useTopicLoading = (topic: Topic) => { + const loading = useAppSelector((state) => selectTopicLoading(state, topic.id)) + return loading +} diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 78156ffcd..1de7892e8 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -32,7 +32,9 @@ "title": "Agents" }, "assistants": { + "title": "Assistants", "abbr": "Assistant", + "settings.title": "Assistant Settings", "clear.content": "Clearing the topic will delete all topics and files in the assistant. Are you sure you want to continue?", "clear.title": "Clear topics", "copy.title": "Copy Assistant", @@ -53,7 +55,7 @@ "settings.reasoning_effort.medium": "medium", "settings.reasoning_effort.off": "off", "settings.reasoning_effort.tip": "Only supports OpenAI o-series and Anthropic reasoning models", - "title": "Assistants" + "settings.more": "Assistant Settings" }, "auth": { "error": "API key automatically obtained failed, please get it manually", @@ -1123,7 +1125,7 @@ "docs_more_details": "for more details", "get_api_key": "Get API Key", "is_not_support_array_content": "Enable compatible mode", - "no_models": "Please add models first before checking the API connection", + "no_models_for_check": "No models available for checking (e.g. chat models)", "not_checked": "Not Checked", "remove_duplicate_keys": "Remove Duplicate Keys", "remove_invalid_keys": "Remove Invalid Keys", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index bf93b550a..f2c5826ff 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -32,7 +32,9 @@ "title": "エージェント" }, "assistants": { + "title": "アシスタント", "abbr": "アシスタント", + "settings.title": "アシスタント設定", "clear.content": "トピックをクリアすると、アシスタント内のすべてのトピックとファイルが削除されます。続行しますか?", "clear.title": "トピックをクリア", "copy.title": "アシスタントをコピー", @@ -53,7 +55,7 @@ "settings.reasoning_effort.medium": "中程度", "settings.reasoning_effort.off": "オフ", "settings.reasoning_effort.tip": "OpenAIのoシリーズとAnthropicの推論モデルのみサポートしています", - "title": "アシスタント" + "settings.more": "アシスタント設定" }, "auth": { "error": "APIキーの自動取得に失敗しました。手動で取得してください", @@ -1122,7 +1124,7 @@ "docs_more_details": "詳細を確認", "get_api_key": "APIキーを取得", "is_not_support_array_content": "互換モードを有効にする", - "no_models": "API接続をチェックする前に、モデルを追加してください", + "no_models_for_check": "チェックするモデルがありません(例:会話モデル)", "not_checked": "未チェック", "remove_duplicate_keys": "重複キーを削除", "remove_invalid_keys": "無効なキーを削除", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index d670bdea7..1de6e574f 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -32,7 +32,9 @@ "title": "Агенты" }, "assistants": { + "title": "Ассистенты", "abbr": "Ассистент", + "settings.title": "Настройки ассистента", "clear.content": "Очистка топика удалит все топики и файлы в ассистенте. Вы уверены, что хотите продолжить?", "clear.title": "Очистить топики", "copy.title": "Копировать ассистента", @@ -53,7 +55,7 @@ "settings.reasoning_effort.medium": "Средняя", "settings.reasoning_effort.off": "Выключено", "settings.reasoning_effort.tip": "Поддерживается только моделями с рассуждением OpenAI o-series и Anthropic", - "title": "Ассистенты" + "settings.more": "Настройки ассистента" }, "auth": { "error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную", @@ -1122,7 +1124,7 @@ "docs_more_details": "для получения дополнительной информации", "get_api_key": "Получить ключ API", "is_not_support_array_content": "Включить совместимый режим", - "no_models": "Пожалуйста, добавьте модели перед проверкой соединения с API", + "no_models_for_check": "Нет моделей для проверки (например, диалоговые модели)", "not_checked": "Не проверено", "remove_duplicate_keys": "Удалить дубликаты ключей", "remove_invalid_keys": "Удалить недействительные ключи", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 30a9b0ec4..a489ef139 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -32,7 +32,9 @@ "title": "智能体" }, "assistants": { + "title": "助手", "abbr": "助手", + "settings.title": "助手设置", "clear.content": "清空话题会删除助手下所有话题和文件,确定要继续吗?", "clear.title": "清空话题", "copy.title": "复制助手", @@ -53,7 +55,7 @@ "settings.reasoning_effort.medium": "中", "settings.reasoning_effort.off": "关", "settings.reasoning_effort.tip": "仅支持 OpenAI o-series 和 Anthropic 推理模型", - "title": "助手" + "settings.more": "助手设置" }, "auth": { "error": "自动获取密钥失败,请手动获取", @@ -1126,7 +1128,7 @@ "docs_more_details": "获取更多详情", "get_api_key": "点击这里获取密钥", "is_not_support_array_content": "开启兼容模式", - "no_models": "请先添加模型再检查 API 连接", + "no_models_for_check": "没有可以被检查的模型(例如对话模型)", "not_checked": "未检查", "remove_duplicate_keys": "移除重复密钥", "remove_invalid_keys": "删除无效密钥", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index d7711d845..000cc968f 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -32,7 +32,9 @@ "title": "智慧代理人" }, "assistants": { + "title": "助手", "abbr": "助手", + "settings.title": "助手設定", "clear.content": "清空話題會刪除助手下所有主題和檔案,確定要繼續嗎?", "clear.title": "清空話題", "copy.title": "複製助手", @@ -52,8 +54,8 @@ "settings.reasoning_effort.low": "短", "settings.reasoning_effort.medium": "中", "settings.reasoning_effort.off": "關", - "settings.reasoning_effort.tip": "僅支援 OpenAI o 系列和 Anthropic 推理模型", - "title": "助手" + "settings.reasoning_effort.tip": "僅支援 OpenAI o-series 和 Anthropic 推理模型", + "settings.more": "助手設定" }, "auth": { "error": "自動取得金鑰失敗,請手動取得", @@ -1122,7 +1124,7 @@ "docs_more_details": "檢視更多細節", "get_api_key": "點選這裡取得金鑰", "is_not_support_array_content": "開啟相容模式", - "no_models": "請先新增模型再檢查 API 連接", + "no_models_for_check": "沒有可以被檢查的模型(例如對話模型)", "not_checked": "未檢查", "remove_duplicate_keys": "移除重複金鑰", "remove_invalid_keys": "刪除無效金鑰", diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 4885d60bf..e9b964514 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -1,13 +1,16 @@ import './assets/styles/index.scss' +import '@ant-design/v5-patch-for-react-19' -import ReactDOM from 'react-dom/client' +import { createRoot } from 'react-dom/client' import App from './App' import MiniApp from './windows/mini/App' if (location.hash === '#/mini') { document.getElementById('spinner')?.remove() - ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render() + const root = createRoot(document.getElementById('root') as HTMLElement) + root.render() } else { - ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render() + const root = createRoot(document.getElementById('root') as HTMLElement) + root.render() } diff --git a/src/renderer/src/pages/agents/components/AgentCard.tsx b/src/renderer/src/pages/agents/components/AgentCard.tsx index 80a515990..2f64e9aa6 100644 --- a/src/renderer/src/pages/agents/components/AgentCard.tsx +++ b/src/renderer/src/pages/agents/components/AgentCard.tsx @@ -1,14 +1,20 @@ import { EllipsisOutlined } from '@ant-design/icons' -import { Agent } from '@renderer/types' +import type { Agent } from '@renderer/types' import { getLeadingEmoji } from '@renderer/utils' import { Dropdown } from 'antd' -import { FC, memo } from 'react' +import { type FC, memo } from 'react' import styled from 'styled-components' interface Props { agent: Agent onClick: () => void - contextMenu?: { label: string; onClick: () => void }[] + contextMenu?: { + key: string + label: string + icon?: React.ReactNode + danger?: boolean + onClick: () => void + }[] menuItems?: { key: string label: string @@ -58,9 +64,14 @@ const AgentCard: FC = ({ agent, onClick, contextMenu, menuItems }) => { ({ - key: item.label, - label: item.label, - onClick: () => item.onClick() + ...item, + onClick: (e) => { + e.domEvent.stopPropagation() + e.domEvent.preventDefault() + setTimeout(() => { + item.onClick() + }, 0) + } })) }} trigger={['contextMenu']}> diff --git a/src/renderer/src/pages/agents/components/MyAgents.tsx b/src/renderer/src/pages/agents/components/MyAgents.tsx index f1749b9bd..ad93a825b 100644 --- a/src/renderer/src/pages/agents/components/MyAgents.tsx +++ b/src/renderer/src/pages/agents/components/MyAgents.tsx @@ -2,7 +2,7 @@ import { DeleteOutlined, EditOutlined, PlusOutlined, SortAscendingOutlined } fro import { useAgents } from '@renderer/hooks/useAgents' import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings' import { createAssistantFromAgent } from '@renderer/services/AssistantService' -import { Agent } from '@renderer/types' +import type { Agent } from '@renderer/types' import { Col, Row } from 'antd' import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' @@ -45,7 +45,7 @@ const MyAgents: React.FC = ({ onClick, search }) => { return ( {filteredAgents.map((agent) => { - const dropdownMenuItems = [ + const menuItems = [ { key: 'edit', label: t('agents.edit.title'), @@ -73,29 +73,9 @@ const MyAgents: React.FC = ({ onClick, search }) => { } ] - const contextMenuItems = [ - { - label: t('agents.edit.title'), - onClick: () => AssistantSettingsPopup.show({ assistant: agent }) - }, - { - label: t('agents.add.button'), - onClick: () => createAssistantFromAgent(agent) - }, - { - label: t('common.delete'), - onClick: () => handleDelete(agent) - } - ] - return ( - onClick?.(agent)} - contextMenu={contextMenuItems} - menuItems={dropdownMenuItems} - /> + onClick?.(agent)} contextMenu={menuItems} menuItems={menuItems} /> ) })} diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 1dadb0545..5fe831bff 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -13,7 +13,7 @@ import TranslateButton from '@renderer/components/TranslateButton' import { isFunctionCallingModel, isGenerateImageModel, isVisionModel, isWebSearchModel } from '@renderer/config/models' import db from '@renderer/databases' import { useAssistant } from '@renderer/hooks/useAssistant' -import { useMessageOperations } from '@renderer/hooks/useMessageOperations' +import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations' import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings' import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts' @@ -83,10 +83,11 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const containerRef = useRef(null) const { searching } = useRuntime() const { isBubbleStyle } = useMessageStyle() - const { loading, pauseMessages } = useMessageOperations(topic) + const { pauseMessages } = useMessageOperations(topic) + const loading = useTopicLoading(topic) const dispatch = useAppDispatch() const [spaceClickCount, setSpaceClickCount] = useState(0) - const spaceClickTimer = useRef() + const spaceClickTimer = useRef(null) const [isTranslating, setIsTranslating] = useState(false) const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState([]) const [mentionModels, setMentionModels] = useState([]) @@ -96,7 +97,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const [textareaHeight, setTextareaHeight] = useState() const startDragY = useRef(0) const startHeight = useRef(0) - const currentMessageId = useRef() + const currentMessageId = useRef('') const isVision = useMemo(() => isVisionModel(model), [model]) const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision]) const navigate = useNavigate() diff --git a/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx b/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx index 71eb504c7..7cffbaca3 100644 --- a/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx @@ -1,6 +1,6 @@ import { PushpinOutlined } from '@ant-design/icons' import ModelTags from '@renderer/components/ModelTags' -import { getModelLogo, isEmbeddingModel } from '@renderer/config/models' +import { getModelLogo, isEmbeddingModel, isRerankModel } from '@renderer/config/models' import db from '@renderer/databases' import { useProviders } from '@renderer/hooks/useProvider' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' @@ -67,6 +67,7 @@ const MentionModelsButton: FC = ({ mentionModels, onMentionModel: onSelec .map((p) => { const filteredModels = sortBy(p.models, ['group', 'name']) .filter((m) => !isEmbeddingModel(m)) + .filter((m) => !isRerankModel(m)) // Filter out pinned models from regular groups .filter((m) => !pinnedModels.includes(getModelUniqId(m))) // Filter by search text diff --git a/src/renderer/src/pages/home/Markdown/CodeBlock.tsx b/src/renderer/src/pages/home/Markdown/CodeBlock.tsx index 764a3eee4..2975f35df 100644 --- a/src/renderer/src/pages/home/Markdown/CodeBlock.tsx +++ b/src/renderer/src/pages/home/Markdown/CodeBlock.tsx @@ -26,7 +26,7 @@ const CodeBlock: React.FC = ({ children, className }) => { const match = /language-(\w+)/.exec(className || '') || children?.includes('\n') const { codeShowLineNumbers, fontSize, codeCollapsible, codeWrappable } = useSettings() const language = match?.[1] ?? 'text' - const [html, setHtml] = useState('') + // const [html, setHtml] = useState('') const { codeToHtml } = useSyntaxHighlighter() const [isExpanded, setIsExpanded] = useState(!codeCollapsible) const [isUnwrapped, setIsUnwrapped] = useState(!codeWrappable) @@ -40,17 +40,14 @@ const CodeBlock: React.FC = ({ children, className }) => { useEffect(() => { const loadHighlightedCode = async () => { const highlightedHtml = await codeToHtml(children, language) - setHtml(highlightedHtml) + if (codeContentRef.current) { + codeContentRef.current.innerHTML = highlightedHtml + setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350) + } } loadHighlightedCode() }, [children, language, codeToHtml]) - useEffect(() => { - if (codeContentRef.current) { - setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350) - } - }, [html]) - useEffect(() => { if (!codeCollapsible) { setIsExpanded(true) @@ -112,7 +109,7 @@ const CodeBlock: React.FC = ({ children, className }) => { isShowLineNumbers={codeShowLineNumbers} isUnwrapped={isUnwrapped} isCodeWrappable={codeWrappable} - dangerouslySetInnerHTML={{ __html: html }} + // dangerouslySetInnerHTML={{ __html: html }} style={{ border: '0.5px solid var(--color-code-background)', borderTopLeftRadius: 0, diff --git a/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx b/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx index a9bf4502b..8157ed1d3 100644 --- a/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx +++ b/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx @@ -50,7 +50,7 @@ const CustomNode: FC<{ data: any }> = ({ data }) => { let title = '' let backgroundColor = 'var(--bg-color)' let gradientColor = 'rgba(0, 0, 0, 0.03)' - let avatar: JSX.Element | null = null + let avatar: React.ReactNode | null = null // 根据消息类型设置不同的样式和图标 if (nodeType === 'user') { diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index 39350721d..818511cb4 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -163,7 +163,7 @@ const MessageItem: FC = ({ isLastMessage={isLastMessage} isAssistantMessage={isAssistantMessage} isGrouped={isGrouped} - messageContainerRef={messageContainerRef} + messageContainerRef={messageContainerRef as React.RefObject} setModel={setModel} /> diff --git a/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx b/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx index be3c64faa..be70d8f4d 100644 --- a/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx +++ b/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx @@ -6,11 +6,11 @@ import { useSettings } from '@renderer/hooks/useSettings' import { getMessageModelId } from '@renderer/services/MessagesService' import { getModelName } from '@renderer/services/ModelService' import { useAppDispatch } from '@renderer/store' -import { updateMessage } from '@renderer/store/messages' -import { Message } from '@renderer/types' +import { updateMessageThunk } from '@renderer/store/messages' +import type { Message } from '@renderer/types' import { isEmoji, removeLeadingEmoji } from '@renderer/utils' import { Avatar } from 'antd' -import { FC, useCallback, useEffect, useRef, useState } from 'react' +import { type FC, useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' interface MessageLineProps { @@ -100,15 +100,9 @@ const MessageAnchorLine: FC = ({ messages }) => { (message: Message) => { const groupMessages = messages.filter((m) => m.askId === message.askId) if (groupMessages.length > 1) { - groupMessages.forEach((m) => { - dispatch( - updateMessage({ - topicId: m.topicId, - messageId: m.id, - updates: { foldSelected: m.id === message.id } - }) - ) - }) + for (const m of groupMessages) { + dispatch(updateMessageThunk(m.topicId, m.id, { foldSelected: m.id === message.id })) + } setTimeout(() => { const messageElement = document.getElementById(`message-${message.id}`) diff --git a/src/renderer/src/pages/home/Messages/MessageGroup.tsx b/src/renderer/src/pages/home/Messages/MessageGroup.tsx index 2cc7c00af..b62f6f509 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroup.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroup.tsx @@ -145,7 +145,7 @@ const MessageGroup = ({ messages, topic, hidePresetMessages }: Props) => { key={message.id} className={classNames({ 'group-message-wrapper': message.role === 'assistant' && isHorizontal && isGrouped, - [multiModelMessageStyle]: true, + [multiModelMessageStyle]: isGrouped, selected: 'foldSelected' in message ? message.foldSelected : index === 0 })}> diff --git a/src/renderer/src/pages/home/Messages/MessageGroupModelList.tsx b/src/renderer/src/pages/home/Messages/MessageGroupModelList.tsx index 315db3aaf..c81d9adc5 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroupModelList.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroupModelList.tsx @@ -1,9 +1,12 @@ import { ArrowsAltOutlined, ShrinkOutlined } from '@ant-design/icons' import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import Scrollbar from '@renderer/components/Scrollbar' +import { useSettings } from '@renderer/hooks/useSettings' +import { useAppDispatch } from '@renderer/store' +import { setFoldDisplayMode } from '@renderer/store/settings' import { Message, Model } from '@renderer/types' import { Avatar, Segmented as AntdSegmented, Tooltip } from 'antd' -import { FC, useState } from 'react' +import { FC } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -15,26 +18,27 @@ interface MessageGroupModelListProps { type DisplayMode = 'compact' | 'expanded' const MessageGroupModelList: FC = ({ messages, setSelectedMessage }) => { + const dispatch = useAppDispatch() const { t } = useTranslation() - const [displayMode, setDisplayMode] = useState('expanded') - const isCompact = displayMode === 'compact' + const { foldDisplayMode } = useSettings() + const isCompact = foldDisplayMode === 'compact' return ( - setDisplayMode(isCompact ? 'expanded' : 'compact')}> + dispatch(setFoldDisplayMode(isCompact ? 'expanded' : 'compact'))}> - {displayMode === 'compact' ? : } + {foldDisplayMode === 'compact' ? : } - - {displayMode === 'compact' ? ( + + {foldDisplayMode === 'compact' ? ( /* Compact style display */ {messages.map((message, index) => ( diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 5b5185245..220a5becb 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -17,12 +17,12 @@ import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup' import TextEditPopup from '@renderer/components/Popups/TextEditPopup' import { isReasoningModel } from '@renderer/config/models' import { TranslateLanguageOptions } from '@renderer/config/translate' -import { useMessageOperations } from '@renderer/hooks/useMessageOperations' +import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { getMessageTitle, resetAssistantMessage } from '@renderer/services/MessagesService' import { translateText } from '@renderer/services/TranslateService' -import { Message, Model } from '@renderer/types' -import { Assistant, Topic } from '@renderer/types' +import type { Message, Model } from '@renderer/types' +import type { Assistant, Topic } from '@renderer/types' import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, removeTrailingDoubleSpaces } from '@renderer/utils' import { exportMarkdownToJoplin, @@ -62,15 +62,9 @@ const MessageMenubar: FC = (props) => { const [showRegenerateTooltip, setShowRegenerateTooltip] = useState(false) const [showDeleteTooltip, setShowDeleteTooltip] = useState(false) const assistantModel = assistant?.model - const { - loading, - editMessage, - setStreamMessage, - deleteMessage, - resendMessage, - commitStreamMessage, - clearStreamMessage - } = useMessageOperations(topic) + const { editMessage, setStreamMessage, deleteMessage, resendMessage, commitStreamMessage, clearStreamMessage } = + useMessageOperations(topic) + const loading = useTopicLoading(topic) const isUserMessage = message.role === 'user' @@ -382,7 +376,7 @@ const MessageMenubar: FC = (props) => { okButtonProps={{ danger: true }} icon={} onOpenChange={(open) => open && setShowDeleteTooltip(false)} - onConfirm={() => deleteMessage(message)}> + onConfirm={() => deleteMessage(message.id)}> e.stopPropagation()}> = ({ message }) => { // Format tool responses for collapse items const getCollapseItems = () => { - const items: { key: string; label: JSX.Element; children: React.ReactNode }[] = [] + const items: { key: string; label: React.ReactNode; children: React.ReactNode }[] = [] // Add tool responses - toolResponses.forEach((toolResponse: MCPToolResponse) => { + for (const toolResponse of toolResponses) { const { id, tool, status, response } = toolResponse const isInvoking = status === 'invoking' const isDone = status === 'done' @@ -105,7 +105,7 @@ const MessageTools: FC = ({ message }) => { ) }) - }) + } return items } @@ -129,7 +129,9 @@ const MessageTools: FC = ({ message }) => { onCancel={() => setExpandedResponse(null)} footer={null} width="80%" - bodyStyle={{ maxHeight: '80vh', overflow: 'auto' }}> + styles={{ + body: { maxHeight: '80vh', overflow: 'auto' } + }}> {expandedResponse && ( void } +const computeDisplayMessages = (messages: Message[], startIndex: number, displayCount: number) => { + const reversedMessages = [...messages].reverse() + + // 如果剩余消息数量小于 displayCount,直接返回所有剩余消息 + if (reversedMessages.length - startIndex <= displayCount) { + return reversedMessages.slice(startIndex) + } + + const userIdSet = new Set() // 用户消息 id 集合 + const assistantIdSet = new Set() // 助手消息 askId 集合 + const displayMessages: Message[] = [] + + // 处理单条消息的函数 + const processMessage = (message: Message) => { + if (!message) return + + const idSet = message.role === 'user' ? userIdSet : assistantIdSet + const messageId = message.role === 'user' ? message.id : message.askId + + if (!idSet.has(messageId)) { + idSet.add(messageId) + displayMessages.push(message) + return + } + // 如果是相同 askId 的助手消息,也要显示 + displayMessages.push(message) + } + + // 遍历消息直到满足显示数量要求 + for (let i = startIndex; i < reversedMessages.length && userIdSet.size + assistantIdSet.size < displayCount; i++) { + processMessage(reversedMessages[i]) + } + + return displayMessages +} + const Messages: React.FC = ({ assistant, topic, setActiveTopic }) => { const { t } = useTranslation() const { showTopics, topicPosition, showAssistants, messageNavigation } = useSettings() @@ -48,9 +84,9 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) const [hasMore, setHasMore] = useState(false) const [isLoadingMore, setIsLoadingMore] = useState(false) const [isProcessingContext, setIsProcessingContext] = useState(false) - const { messages, displayCount, loading, updateMessages, clearTopicMessages, deleteMessage } = - useMessageOperations(topic) - + const messages = useTopicMessages(topic) + const { displayCount, updateMessages, clearTopicMessages, deleteMessage } = useMessageOperations(topic) + const loading = useTopicLoading(topic) const messagesRef = useRef(messages) useEffect(() => { @@ -58,9 +94,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) }, [messages]) useEffect(() => { - const reversedMessages = [...messages].reverse() - const newDisplayMessages = reversedMessages.slice(0, displayCount) - + const newDisplayMessages = computeDisplayMessages(messages, 0, displayCount) setDisplayMessages(newDisplayMessages) setHasMore(messages.length > displayCount) }, [messages, displayCount]) @@ -73,7 +107,15 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) }, [showAssistants, showTopics, topicPosition]) const scrollToBottom = useCallback(() => { - setTimeout(() => containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'auto' }), 50) + if (containerRef.current) { + requestAnimationFrame(() => { + if (containerRef.current) { + containerRef.current.scrollTo({ + top: containerRef.current.scrollHeight + }) + } + }) + } }, []) useEffect(() => { @@ -122,7 +164,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) const lastMessage = last(messages) if (lastMessage?.type === 'clear') { - await deleteMessage(lastMessage) + await deleteMessage(lastMessage.id) scrollToBottom() return } @@ -183,10 +225,9 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) setIsLoadingMore(true) setTimeout(() => { const currentLength = displayMessages.length - const reversedMessages = [...messages].reverse() - const moreMessages = reversedMessages.slice(currentLength, currentLength + LOAD_MORE_COUNT) + const newMessages = computeDisplayMessages(messages, currentLength, LOAD_MORE_COUNT) - setDisplayMessages((prev) => [...prev, ...moreMessages]) + setDisplayMessages((prev) => [...prev, ...newMessages]) setHasMore(currentLength + LOAD_MORE_COUNT < messages.length) setIsLoadingMore(false) }, 300) @@ -214,8 +255,8 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) next={loadMoreMessages} hasMore={hasMore} loader={null} - inverse={true} - scrollableTarget="messages"> + scrollableTarget="messages" + inverse> @@ -232,9 +273,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) - {messageNavigation === 'anchor' && } - {messageNavigation === 'buttons' && } ) diff --git a/src/renderer/src/pages/home/Tabs/AssistantItem.tsx b/src/renderer/src/pages/home/Tabs/AssistantItem.tsx index ec4f32f99..c85a13eb2 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantItem.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantItem.tsx @@ -153,7 +153,6 @@ const Container = styled.div` justify-content: space-between; padding: 7px 10px; position: relative; - margin: 0 10px; font-family: Ubuntu; border-radius: var(--list-item-border-radius); border: 0.5px solid transparent; diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 4b3312ac0..f2455a8a8 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -78,8 +78,7 @@ const Assistants: FC = ({ const Container = styled(Scrollbar)` display: flex; flex-direction: column; - padding-top: 11px; - user-select: none; + padding: 10px; ` const AssistantAddItem = styled.div` @@ -88,7 +87,6 @@ const AssistantAddItem = styled.div` justify-content: space-between; padding: 7px 12px; position: relative; - margin: 0 10px; padding-right: 35px; font-family: Ubuntu; border-radius: var(--list-item-border-radius); diff --git a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx index 70281fd4c..9a45d4959 100644 --- a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx @@ -1,4 +1,4 @@ -import { CheckOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons' +import { CheckOutlined, QuestionCircleOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons' import { HStack } from '@renderer/components/Layout' import Scrollbar from '@renderer/components/Scrollbar' import { @@ -13,6 +13,7 @@ import { codeThemes } from '@renderer/context/SyntaxHighlighterProvider' import { useAssistant } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' import { SettingDivider, SettingRow, SettingRowTitle, SettingSubtitle } from '@renderer/pages/settings' +import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings' import { getDefaultModel } from '@renderer/services/AssistantService' import { useAppDispatch } from '@renderer/store' import { @@ -37,7 +38,7 @@ import { } from '@renderer/store/settings' import { Assistant, AssistantSettings, CodeStyleVarious, ThemeMode, TranslateLanguageVarious } from '@renderer/types' import { modalConfirm } from '@renderer/utils' -import { Col, InputNumber, Row, Segmented, Select, Slider, Switch, Tooltip } from 'antd' +import { Button, Col, InputNumber, Row, Segmented, Select, Slider, Switch, Tooltip } from 'antd' import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -145,11 +146,19 @@ const SettingsTab: FC = (props) => { return ( - - {t('settings.messages.model.title')}{' '} - - - + + + {t('assistants.settings.title')}{' '} + + + + +