Merge branch 'main' into local-pr-3734

This commit is contained in:
suyao
2025-03-27 14:30:33 +08:00
69 changed files with 645 additions and 762 deletions
@@ -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,
@@ -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<BaseLoader>;
+export declare function createLoaderFromMimeType(loaderData: string, mimeType: string, chunkSize?: number, chunkOverlap?: number): Promise<BaseLoader>;
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': {
+3 -2
View File
@@ -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
+10 -10
View File
@@ -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'
]
+17 -19
View File
@@ -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",
+1 -1
View File
@@ -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'
+3 -3
View File
@@ -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'
+1 -1
View File
@@ -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
+1 -1
View File
@@ -1,6 +1,6 @@
import * as fs from 'node:fs'
import { JsonLoader } from '@llm-tools/embedjs'
import { JsonLoader } from '@cherrystudio/embedjs'
/**
* Drafts 应用导出的笔记文件加载器
+2 -2
View File
@@ -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'
+3 -3
View File
@@ -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'
+2 -2
View File
@@ -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'
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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'
+5 -5
View File
@@ -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'
+2 -1
View File
@@ -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,
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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 (
<Provider store={store}>
<StyleSheetManager>
@@ -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<Props<any>> = ({
<Droppable droppableId="droppable" {...droppableProps}>
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef} style={style}>
{list.map((item, index) => {
const id = item.id || item
return (
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...listStyle,
...provided.draggableProps.style,
marginBottom: 8
}}>
{children(item, index)}
</div>
)}
</Draggable>
)
})}
<VirtualList data={list} itemKey="id">
{(item, index) => {
const id = item.id || item
return (
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...listStyle,
...provided.draggableProps.style,
marginBottom: 8
}}>
{children(item, index)}
</div>
)}
</Draggable>
)
}}
</VirtualList>
{provided.placeholder}
</div>
)}
@@ -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<HTMLDivElement> {
@@ -7,7 +7,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
ref?: any
}
const Scrollbar: FC<Props> = forwardRef<HTMLDivElement, Props>((props, ref) => {
const Scrollbar: FC<Props> = ({ ref, ...props }: Props & { ref?: React.RefObject<HTMLDivElement | null> }) => {
const [isScrolling, setIsScrolling] = useState(false)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
@@ -35,7 +35,7 @@ const Scrollbar: FC<Props> = forwardRef<HTMLDivElement, Props>((props, ref) => {
{props.children}
</Container>
)
})
}
const Container = styled.div<{ isScrolling: boolean; right?: boolean }>`
overflow-y: auto;
+3 -2
View File
@@ -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<HTMLDivElement>
export const Navbar: FC<Props> = ({ children, ...props }) => {
const backgroundColor = useNavBackgroundColor()
+1
View File
@@ -149,6 +149,7 @@ const visionAllowedModels = [
'qwen-vl',
'qwen2-vl',
'qwen2.5-vl',
'qwen2.5-omni',
'qvq',
'internvl2',
'grok-vision-beta',
@@ -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 (
<StyledComponentsStyleSheetManager
shouldForwardProp={(prop, element) => {
@@ -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<string>
@@ -51,42 +46,47 @@ export const SyntaxHighlighterProvider: React.FC<PropsWithChildren> = ({ 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<string, string> = {
vab: 'vb'
}
const languageMap: Record<string, string> = {
vab: 'vb'
}
const mappedLanguage = languageMap[language] || language
const mappedLanguage = languageMap[language] || language
code = code?.trimEnd() ?? ''
const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '&lt;', '>': '&gt;' })[char]!)
const code = _code?.trimEnd() ?? ''
const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '&lt;', '>': '&gt;' })[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 `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
}
}
return highlighter.codeToHtml(code, {
lang: mappedLanguage,
theme: highlighterTheme
})
} catch (error) {
console.warn(`Error highlighting code for language '${mappedLanguage}':`, error)
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
}
}
return highlighter.codeToHtml(code, {
lang: mappedLanguage,
theme: highlighterTheme
})
} catch (error) {
console.warn(`Error highlighting code for language '${mappedLanguage}':`, error)
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
}
}
},
[highlighter, highlighterTheme]
)
return <SyntaxHighlighterContext.Provider value={{ codeToHtml }}>{children}</SyntaxHighlighterContext.Provider>
}
export const useSyntaxHighlighter = () => {
const context = useContext(SyntaxHighlighterContext)
const context = use(SyntaxHighlighterContext)
if (!context) {
throw new Error('useSyntaxHighlighter must be used within a SyntaxHighlighterProvider')
}
+2 -2
View File
@@ -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<ThemeProviderProps> = ({ children, defaultT
)
}
export const useTheme = () => useContext(ThemeContext)
export const useTheme = () => use(ThemeContext)
+30 -20
View File
@@ -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<Message>) => {
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
}
+4 -2
View File
@@ -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",
+4 -2
View File
@@ -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": "無効なキーを削除",
+4 -2
View File
@@ -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": "Удалить недействительные ключи",
+4 -2
View File
@@ -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": "删除无效密钥",
+5 -3
View File
@@ -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": "刪除無效金鑰",
+6 -3
View File
@@ -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(<MiniApp />)
const root = createRoot(document.getElementById('root') as HTMLElement)
root.render(<MiniApp />)
} else {
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
const root = createRoot(document.getElementById('root') as HTMLElement)
root.render(<App />)
}
@@ -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<Props> = ({ agent, onClick, contextMenu, menuItems }) => {
<Dropdown
menu={{
items: contextMenu.map((item) => ({
key: item.label,
label: item.label,
onClick: () => item.onClick()
...item,
onClick: (e) => {
e.domEvent.stopPropagation()
e.domEvent.preventDefault()
setTimeout(() => {
item.onClick()
}, 0)
}
}))
}}
trigger={['contextMenu']}>
@@ -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<Props> = ({ onClick, search }) => {
return (
<Row gutter={[20, 20]}>
{filteredAgents.map((agent) => {
const dropdownMenuItems = [
const menuItems = [
{
key: 'edit',
label: t('agents.edit.title'),
@@ -73,29 +73,9 @@ const MyAgents: React.FC<Props> = ({ 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 (
<Col span={6} key={agent.id}>
<AgentCard
agent={agent}
onClick={() => onClick?.(agent)}
contextMenu={contextMenuItems}
menuItems={dropdownMenuItems}
/>
<AgentCard agent={agent} onClick={() => onClick?.(agent)} contextMenu={menuItems} menuItems={menuItems} />
</Col>
)
})}
@@ -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<Props> = ({ 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<NodeJS.Timeout>()
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
const [isTranslating, setIsTranslating] = useState(false)
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
const [mentionModels, setMentionModels] = useState<Model[]>([])
@@ -96,7 +97,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const [textareaHeight, setTextareaHeight] = useState<number>()
const startDragY = useRef<number>(0)
const startHeight = useRef<number>(0)
const currentMessageId = useRef<string>()
const currentMessageId = useRef<string>('')
const isVision = useMemo(() => isVisionModel(model), [model])
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
const navigate = useNavigate()
@@ -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<Props> = ({ 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
@@ -26,7 +26,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ 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<string>('')
// const [html, setHtml] = useState<string>('')
const { codeToHtml } = useSyntaxHighlighter()
const [isExpanded, setIsExpanded] = useState(!codeCollapsible)
const [isUnwrapped, setIsUnwrapped] = useState(!codeWrappable)
@@ -40,17 +40,14 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ 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<CodeBlockProps> = ({ 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,
@@ -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') {
@@ -163,7 +163,7 @@ const MessageItem: FC<Props> = ({
isLastMessage={isLastMessage}
isAssistantMessage={isAssistantMessage}
isGrouped={isGrouped}
messageContainerRef={messageContainerRef}
messageContainerRef={messageContainerRef as React.RefObject<HTMLDivElement>}
setModel={setModel}
/>
</MessageFooter>
@@ -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<MessageLineProps> = ({ 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}`)
@@ -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
})}>
<MessageStream {...messageProps} />
@@ -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<MessageGroupModelListProps> = ({ messages, setSelectedMessage }) => {
const dispatch = useAppDispatch()
const { t } = useTranslation()
const [displayMode, setDisplayMode] = useState<DisplayMode>('expanded')
const isCompact = displayMode === 'compact'
const { foldDisplayMode } = useSettings()
const isCompact = foldDisplayMode === 'compact'
return (
<ModelsWrapper>
<DisplayModeToggle displayMode={displayMode} onClick={() => setDisplayMode(isCompact ? 'expanded' : 'compact')}>
<DisplayModeToggle displayMode={foldDisplayMode} onClick={() => dispatch(setFoldDisplayMode(isCompact ? 'expanded' : 'compact'))}>
<Tooltip
title={
displayMode === 'compact'
foldDisplayMode === 'compact'
? t(`message.message.multi_model_style.fold.expand`)
: t('message.message.multi_model_style.fold.compress')
}
placement="top">
{displayMode === 'compact' ? <ArrowsAltOutlined /> : <ShrinkOutlined />}
{foldDisplayMode === 'compact' ? <ArrowsAltOutlined /> : <ShrinkOutlined />}
</Tooltip>
</DisplayModeToggle>
<ModelsContainer $displayMode={displayMode}>
{displayMode === 'compact' ? (
<ModelsContainer $displayMode={foldDisplayMode}>
{foldDisplayMode === 'compact' ? (
/* Compact style display */
<Avatar.Group className="avatar-group">
{messages.map((message, index) => (
@@ -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> = (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> = (props) => {
okButtonProps={{ danger: true }}
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
onOpenChange={(open) => open && setShowDeleteTooltip(false)}
onConfirm={() => deleteMessage(message)}>
onConfirm={() => deleteMessage(message.id)}>
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
<Tooltip
title={t('common.delete')}
@@ -1,6 +1,6 @@
import { CheckOutlined, ExpandOutlined, LoadingOutlined } from '@ant-design/icons'
import { useSettings } from '@renderer/hooks/useSettings'
import { MCPToolResponse, Message } from '@renderer/types'
import { Message } from '@renderer/types'
import { Collapse, message as antdMessage, Modal, Tooltip } from 'antd'
import { isEmpty } from 'lodash'
import { FC, useMemo, useState } from 'react'
@@ -42,9 +42,9 @@ const MessageTools: FC<Props> = ({ 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<Props> = ({ message }) => {
</ToolResponseContainer>
)
})
})
}
return items
}
@@ -129,7 +129,9 @@ const MessageTools: FC<Props> = ({ message }) => {
onCancel={() => setExpandedResponse(null)}
footer={null}
width="80%"
bodyStyle={{ maxHeight: '80vh', overflow: 'auto' }}>
styles={{
body: { maxHeight: '80vh', overflow: 'auto' }
}}>
{expandedResponse && (
<ExpandedResponseContainer style={{ fontFamily, fontSize }}>
<ActionButton
@@ -2,7 +2,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
import db from '@renderer/databases'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
import { useMessageOperations, useTopicLoading, useTopicMessages } from '@renderer/hooks/useMessageOperations'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic'
@@ -38,6 +38,42 @@ interface MessagesProps {
setActiveTopic: (topic: Topic) => 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<MessagesProps> = ({ assistant, topic, setActiveTopic }) => {
const { t } = useTranslation()
const { showTopics, topicPosition, showAssistants, messageNavigation } = useSettings()
@@ -48,9 +84,9 @@ const Messages: React.FC<MessagesProps> = ({ 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<Message[]>(messages)
useEffect(() => {
@@ -58,9 +94,7 @@ const Messages: React.FC<MessagesProps> = ({ 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<MessagesProps> = ({ 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<MessagesProps> = ({ 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<MessagesProps> = ({ 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<MessagesProps> = ({ assistant, topic, setActiveTopic })
next={loadMoreMessages}
hasMore={hasMore}
loader={null}
inverse={true}
scrollableTarget="messages">
scrollableTarget="messages"
inverse>
<ScrollContainer>
<LoaderContainer $loading={isLoadingMore}>
<BeatLoader size={8} color="var(--color-text-2)" />
@@ -232,9 +273,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
</InfiniteScroll>
<Prompt assistant={assistant} key={assistant.prompt} topic={topic} />
</NarrowLayout>
{messageNavigation === 'anchor' && <MessageAnchorLine messages={displayMessages} />}
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
</Container>
)
@@ -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;
@@ -78,8 +78,7 @@ const Assistants: FC<AssistantsTabProps> = ({
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);
@@ -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> = (props) => {
return (
<Container className="settings-tab">
<SettingGroup style={{ marginTop: 10 }}>
<SettingSubtitle style={{ marginTop: 0 }}>
{t('settings.messages.model.title')}{' '}
<Tooltip title={t('chat.settings.reset')}>
<ReloadOutlined onClick={onReset} style={{ cursor: 'pointer', fontSize: 12, padding: '0 3px' }} />
</Tooltip>
<SettingSubtitle style={{ marginTop: 0, display: 'flex', justifyContent: 'space-between' }}>
<HStack alignItems="center">
{t('assistants.settings.title')}{' '}
<Tooltip title={t('chat.settings.reset')}>
<ReloadOutlined onClick={onReset} style={{ cursor: 'pointer', fontSize: 12, padding: '0 3px' }} />
</Tooltip>
</HStack>
<Button
type="text"
size="small"
icon={<SettingOutlined />}
onClick={() => AssistantSettingsPopup.show({ assistant, tab: 'model' })}
/>
</SettingSubtitle>
<SettingDivider />
<Row align="middle">
@@ -56,7 +56,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
const borderRadius = showTopicTime ? 12 : 'var(--list-item-border-radius)'
const [deletingTopicId, setDeletingTopicId] = useState<string | null>(null)
const deleteTimerRef = useRef<NodeJS.Timeout>()
const deleteTimerRef = useRef<NodeJS.Timeout>(null)
const pendingTopics = useMemo(() => {
return new Set<string>()
@@ -410,14 +410,11 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
const Container = styled(Scrollbar)`
display: flex;
flex-direction: column;
padding-top: 11px;
user-select: none;
padding: 10px;
`
const TopicListItem = styled.div`
padding: 7px 12px;
margin-left: 10px;
margin-right: 4px;
border-radius: var(--list-item-border-radius);
font-family: Ubuntu;
font-size: 13px;
@@ -429,6 +426,7 @@ const TopicListItem = styled.div`
cursor: pointer;
border: 0.5px solid transparent;
position: relative;
width: calc(var(--assistants-width) - 20px);
.menu {
opacity: 0;
color: var(--color-text-3);
@@ -1,4 +1,4 @@
import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import { TopView } from '@renderer/components/TopView'
import { DEFAULT_KNOWLEDGE_THRESHOLD } from '@renderer/config/constant'
import { getFileFromUrl, getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
@@ -23,11 +23,12 @@ import { translateText } from '@renderer/services/TranslateService'
import { useAppDispatch } from '@renderer/store'
import { DEFAULT_PAINTING } from '@renderer/store/paintings'
import { setGenerating } from '@renderer/store/runtime'
import { FileType, Painting } from '@renderer/types'
import type { FileType, Painting } from '@renderer/types'
import { getErrorMessage } from '@renderer/utils'
import { Button, Input, InputNumber, Radio, Select, Slider, Switch, Tooltip } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import { FC, useEffect, useRef, useState } from 'react'
import type { FC } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@@ -243,7 +244,7 @@ const PaintingsPage: FC = () => {
const { autoTranslateWithSpace } = useSettings()
const [spaceClickCount, setSpaceClickCount] = useState(0)
const [isTranslating, setIsTranslating] = useState(false)
const spaceClickTimer = useRef<NodeJS.Timeout>()
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
const translate = async () => {
if (isTranslating) {
@@ -16,16 +16,19 @@ import AssistantPromptSettings from './AssistantPromptSettings'
interface AssistantSettingPopupShowParams {
assistant: Assistant
tab?: AssistantSettingPopupTab
}
type AssistantSettingPopupTab = 'prompt' | 'model' | 'messages' | 'knowledge_base'
interface Props extends AssistantSettingPopupShowParams {
resolve: (assistant: Assistant) => void
}
const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, ...props }) => {
const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, tab, ...props }) => {
const [open, setOpen] = useState(true)
const { t } = useTranslation()
const [menu, setMenu] = useState('prompt')
const [menu, setMenu] = useState<AssistantSettingPopupTab>(tab || 'prompt')
const _useAssistant = useAssistant(props.assistant.id)
const _useAgent = useAgent(props.assistant.id)
@@ -94,10 +97,10 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, ...props })
<LeftMenu>
<Menu
style={{ width: 220, padding: 5, background: 'transparent' }}
defaultSelectedKeys={['prompt']}
defaultSelectedKeys={[tab || 'prompt']}
mode="vertical"
items={items}
onSelect={({ key }) => setMenu(key as string)}
onSelect={({ key }) => setMenu(key as AssistantSettingPopupTab)}
/>
</LeftMenu>
<Settings>
@@ -37,7 +37,7 @@ const DataSettings: FC = () => {
const [appInfo, setAppInfo] = useState<AppInfo>()
const { size, removeAllFiles } = useKnowledgeFiles()
const { theme } = useTheme()
const [menu, setMenu] = useState<string>('common')
const [menu, setMenu] = useState<string>('data')
//joplin icon needs to be updated into iconfont
const JoplinIcon = () => (
@@ -152,7 +152,12 @@ const PopupContainer: React.FC<Props> = ({ server, create, resolve }) => {
width={600}
transitionName="ant-move-down"
centered
bodyStyle={{ maxHeight: '70vh', overflowY: 'auto' }}>
styles={{
body: {
maxHeight: '70vh',
overflowY: 'auto'
}
}}>
<Form form={form} layout="vertical">
<Form.Item
name="name"
@@ -1,6 +1,7 @@
import { CheckOutlined, ExportOutlined, HeartOutlined, LoadingOutlined, SettingOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
import OAuthButton from '@renderer/components/OAuth/OAuthButton'
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
import { PROVIDER_CONFIG } from '@renderer/config/providers'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useProvider } from '@renderer/hooks/useProvider'
@@ -91,12 +92,14 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
const onUpdateApiVersion = () => updateProvider({ ...provider, apiVersion })
const onHealthCheck = async () => {
if (isEmpty(models)) {
const modelsToCheck = models.filter((model) => !isRerankModel(model))
if (isEmpty(modelsToCheck)) {
window.message.error({
key: 'no-models',
style: { marginTop: '3vh' },
duration: 5,
content: t('settings.provider.no_models')
content: t('settings.provider.no_models_for_check')
})
return
}
@@ -124,7 +127,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
}
// Prepare the list of models to be checked
const initialStatuses = models.map((model) => ({
const initialStatuses = modelsToCheck.map((model) => ({
model,
checking: true,
status: undefined
@@ -135,7 +138,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
const checkResults = await checkModelsHealth(
{
provider: { ...provider, apiHost },
models,
models: modelsToCheck,
apiKeys: result.apiKeys,
isConcurrent: result.isConcurrent
},
@@ -180,12 +183,14 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
}
const onCheckApi = async () => {
if (isEmpty(models)) {
const modelsToCheck = models.filter((model) => !isEmbeddingModel(model) && !isRerankModel(model))
if (isEmpty(modelsToCheck)) {
window.message.error({
key: 'no-models',
style: { marginTop: '3vh' },
duration: 5,
content: t('settings.provider.no_models')
content: t('settings.provider.no_models_for_check')
})
return
}
@@ -1,5 +1,5 @@
import { TopView } from '@renderer/components/TopView'
import { isEmbeddingModel } from '@renderer/config/models'
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
import i18n from '@renderer/i18n'
import { Provider } from '@renderer/types'
import { Modal, Select } from 'antd'
@@ -16,7 +16,7 @@ interface Props extends ShowParams {
}
const PopupContainer: React.FC<Props> = ({ provider, resolve, reject }) => {
const models = orderBy(provider.models, 'group').filter((i) => !isEmbeddingModel(i))
const models = orderBy(provider.models, 'group').filter((i) => !isEmbeddingModel(i) && !isRerankModel(i))
const [open, setOpen] = useState(true)
const [model, setModel] = useState(first(models))
@@ -280,7 +280,11 @@ const ShortcutSettings: FC = () => {
<HStack alignItems="center" style={{ position: 'relative' }}>
{isEditing ? (
<ShortcutInput
ref={(el) => el && (inputRefs.current[record.key] = el)}
ref={(el) => {
if (el) {
inputRefs.current[record.key] = el
}
}}
value={formatShortcut(shortcut)}
placeholder={t('settings.shortcuts.press_shortcut')}
onKeyDown={(e) => handleKeyDown(e, record)}
@@ -1,4 +1,4 @@
import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT, DEFAULT_KNOWLEDGE_THRESHOLD } from '@renderer/config/constant'
import { getEmbeddingMaxContext } from '@renderer/config/embedings'
import AiProvider from '@renderer/providers/AiProvider'
@@ -175,6 +175,7 @@ export function getAssistantMessage({ assistant, topic }: { assistant: Assistant
export function getGroupedMessages(messages: Message[]): { [key: string]: (Message & { index: number })[] } {
const groups: { [key: string]: (Message & { index: number })[] } = {}
messages.forEach((message, index) => {
const key = message.askId ? 'assistant' + message.askId : 'user' + message.id
if (key && !groups[key]) {
@@ -182,6 +183,7 @@ export function getGroupedMessages(messages: Message[]): { [key: string]: (Messa
}
groups[key].unshift({ ...message, index })
})
return groups
}
+44 -21
View File
@@ -6,9 +6,9 @@ import { fetchChatCompletion } from '@renderer/services/ApiService'
import { getAssistantMessage, resetAssistantMessage } from '@renderer/services/MessagesService'
import type { AppDispatch, RootState } from '@renderer/store'
import type { Assistant, Message, Topic } from '@renderer/types'
import { Model } from '@renderer/types'
import type { Model } from '@renderer/types'
import { clearTopicQueue, getTopicQueue, waitForTopicQueue } from '@renderer/utils/queue'
import { cloneDeep, isEmpty, throttle } from 'lodash'
import { isEmpty, throttle } from 'lodash'
export interface MessagesState {
messagesByTopic: Record<string, Message[]>
@@ -113,14 +113,10 @@ const messagesSlice = createSlice({
) => {
const { topicId, messageId, updates } = action.payload
const topicMessages = state.messagesByTopic[topicId]
if (topicMessages) {
const message = topicMessages.find((msg) => msg.id === messageId)
if (message) {
Object.assign(message, updates)
db.topics.update(topicId, {
messages: topicMessages.map((m) => (m.id === message.id ? cloneDeep(message) : cloneDeep(m)))
})
}
}
},
@@ -255,7 +251,7 @@ export const sendMessage =
const isGroupedMessage = messageToReset.length > 1
const resetMessage = resetAssistantMessage(m, isGroupedMessage ? m.model : assistant.model)
// 更新状态
dispatch(updateMessage({ topicId: topic.id, messageId: m.id, updates: resetMessage }))
dispatch(updateMessageThunk(topic.id, m.id, resetMessage))
// 使用重置后的消息
return resetMessage
})
@@ -263,7 +259,7 @@ export const sendMessage =
const { model, id } = messageToReset
const resetMessage = resetAssistantMessage(messageToReset, model)
// 更新状态
dispatch(updateMessage({ topicId: topic.id, messageId: id, updates: resetMessage }))
dispatch(updateMessageThunk(topic.id, id, resetMessage))
// 使用重置后的消息
assistantMessages.push(resetMessage)
}
@@ -396,10 +392,9 @@ export const sendMessage =
} catch (error: any) {
console.error('Error in chat completion:', error)
dispatch(
updateMessage({
topicId: topic.id,
messageId: assistantMessage.id,
updates: { status: 'error', error: { message: error.message } }
updateMessageThunk(topic.id, assistantMessage.id, {
status: 'error',
error: { message: error.message }
})
)
dispatch(clearStreamMessage({ topicId: topic.id, messageId: assistantMessage.id }))
@@ -448,10 +443,9 @@ export const resendMessage =
const userMessage = topicMessages.find((m) => m.id === message.askId && m.role === 'user')
if (!userMessage) {
dispatch(
updateMessage({
topicId: topic.id,
messageId: message.id,
updates: { status: 'error', error: { message: i18n.t('error.user_message_not_found') } }
updateMessageThunk(topic.id, message.id, {
status: 'error',
error: { message: i18n.t('error.user_message_not_found') }
})
)
console.error(i18n.t('error.user_message_not_found'))
@@ -521,6 +515,14 @@ export const clearTopicMessagesThunk = (topic: Topic) => async (dispatch: AppDis
}
}
export const deleteMessageAction =
(topic: Topic, id: string, idType: 'id' | 'askId' = 'id') =>
async (dispatch: AppDispatch, getState: () => RootState) => {
const messages = getState().messages.messagesByTopic[topic.id] || []
const newMessages = messages.filter((m) => m[idType] !== id)
await dispatch(updateMessages(topic, newMessages))
}
// 修改的 updateMessages thunk,同时更新缓存
export const updateMessages = (topic: Topic, messages: Message[]) => async (dispatch: AppDispatch) => {
try {
@@ -534,6 +536,28 @@ export const updateMessages = (topic: Topic, messages: Message[]) => async (disp
}
}
// 新增一个 thunk 来处理消息更新
export const updateMessageThunk =
(topicId: string, messageId: string, updates: Partial<Message>) =>
async (dispatch: AppDispatch, getState: () => RootState) => {
try {
// 先更新 Redux 状态
dispatch(updateMessage({ topicId, messageId, updates }))
// 然后同步到数据库
const state = getState()
const topicMessages = state.messages.messagesByTopic[topicId]
if (topicMessages) {
await db.topics.update(topicId, {
messages: topicMessages
})
}
} catch (error) {
console.error('Failed to update message:', error)
dispatch(setError(error instanceof Error ? error.message : 'Failed to update message'))
}
}
// Selectors
export const selectCurrentTopicId = (state: RootState): string | null => {
const messagesState = state.messages
@@ -546,11 +570,10 @@ export const selectTopicMessages = createSelector(
)
// 获取特定话题的loading状态
export const selectTopicLoading = (state: RootState, topicId?: string): boolean => {
const messagesState = state.messages as MessagesState
const currentTopicId = topicId || messagesState.currentTopic?.id || ''
return currentTopicId ? (messagesState.loadingByTopic[currentTopicId] ?? false) : false
}
export const selectTopicLoading = createSelector(
[(state: RootState) => state.messages.loadingByTopic, (_, topicId?: string) => topicId],
(loadingByTopic, topicId) => (topicId ? (loadingByTopic[topicId] ?? false) : false)
)
export const selectDisplayCount = (state: RootState): number => {
const messagesState = state.messages as MessagesState
+1
View File
@@ -805,6 +805,7 @@ const migrateConfig = {
state.settings.autoCheckUpdate = !state.settings.manualUpdateCheck
// @ts-ignore eslint-disable-next-line
delete state.settings.manualUpdateCheck
state.settings.gridPopoverTrigger = 'click'
return state
},
'86': (state: RootState) => {
+7 -1
View File
@@ -53,6 +53,7 @@ export interface SettingsState {
mathEngine: 'MathJax' | 'KaTeX'
messageStyle: 'plain' | 'bubble'
codeStyle: CodeStyleVarious
foldDisplayMode: 'expanded' | 'compact'
gridColumns: number
gridPopoverTrigger: 'hover' | 'click'
messageNavigation: 'none' | 'buttons' | 'anchor'
@@ -135,8 +136,9 @@ const initialState: SettingsState = {
mathEngine: 'KaTeX',
messageStyle: 'plain',
codeStyle: 'auto',
foldDisplayMode: 'expanded',
gridColumns: 2,
gridPopoverTrigger: 'hover',
gridPopoverTrigger: 'click',
messageNavigation: 'none',
webdavHost: '',
webdavUser: '',
@@ -295,6 +297,9 @@ const settingsSlice = createSlice({
setMathEngine: (state, action: PayloadAction<'MathJax' | 'KaTeX'>) => {
state.mathEngine = action.payload
},
setFoldDisplayMode: (state, action: PayloadAction<'expanded' | 'compact'>) => {
state.foldDisplayMode = action.payload
},
setGridColumns: (state, action: PayloadAction<number>) => {
state.gridColumns = action.payload
},
@@ -446,6 +451,7 @@ export const {
setCodeCollapsible,
setCodeWrappable,
setMathEngine,
setFoldDisplayMode,
setGridColumns,
setGridPopoverTrigger,
setMessageStyle,
+6 -3
View File
@@ -306,7 +306,7 @@ export async function captureDiv(divRef: React.RefObject<HTMLDivElement>) {
return Promise.resolve(undefined)
}
export const captureScrollableDiv = async (divRef: React.RefObject<HTMLDivElement>) => {
export const captureScrollableDiv = async (divRef: React.RefObject<HTMLDivElement | null>) => {
if (divRef.current) {
try {
const div = divRef.current
@@ -392,7 +392,7 @@ export const captureScrollableDiv = async (divRef: React.RefObject<HTMLDivElemen
return Promise.resolve(undefined)
}
export const captureScrollableDivAsDataURL = async (divRef: React.RefObject<HTMLDivElement>) => {
export const captureScrollableDivAsDataURL = async (divRef: React.RefObject<HTMLDivElement | null>) => {
return captureScrollableDiv(divRef).then((canvas) => {
if (canvas) {
return canvas.toDataURL('image/png')
@@ -401,7 +401,10 @@ export const captureScrollableDivAsDataURL = async (divRef: React.RefObject<HTML
})
}
export const captureScrollableDivAsBlob = async (divRef: React.RefObject<HTMLDivElement>, func: BlobCallback) => {
export const captureScrollableDivAsBlob = async (
divRef: React.RefObject<HTMLDivElement | null>,
func: BlobCallback
) => {
await captureScrollableDiv(divRef).then((canvas) => {
canvas?.toBlob(func, 'image/png')
})
+1 -1
View File
@@ -9,7 +9,7 @@ import { SyntaxHighlighterProvider } from '../../context/SyntaxHighlighterProvid
import { ThemeProvider } from '../../context/ThemeProvider'
import HomeWindow from './home/HomeWindow'
function MiniWindow(): JSX.Element {
function MiniWindow(): React.ReactElement {
return (
<Provider store={store}>
<ThemeProvider>
+220 -274
View File
@@ -165,6 +165,17 @@ __metadata:
languageName: node
linkType: hard
"@ant-design/v5-patch-for-react-19@npm:^1.0.3":
version: 1.0.3
resolution: "@ant-design/v5-patch-for-react-19@npm:1.0.3"
peerDependencies:
antd: ">=5.22.6"
react: ">=19.0.0"
react-dom: ">=19.0.0"
checksum: 10c0/e3848c929b01a0a29a41e2886f489932e54d9665dd990c60c4b505e21740da2ef0db0d08f4dc7591651fa1f21b15227e7bb40f40280742225b4da74fd18cc899
languageName: node
linkType: hard
"@anthropic-ai/sdk@npm:^0.38.0":
version: 0.38.0
resolution: "@anthropic-ai/sdk@npm:0.38.0"
@@ -451,6 +462,176 @@ __metadata:
languageName: unknown
linkType: soft
"@cherrystudio/embedjs-interfaces@npm:0.1.29":
version: 0.1.29
resolution: "@cherrystudio/embedjs-interfaces@npm:0.1.29"
dependencies:
"@langchain/core": "npm:^0.3.26"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
uuid: "npm:^11.0.3"
checksum: 10c0/df7ac19aecae137e5af427bb1605232993ef1510e3d41875cb8dd604e7a692778758e5f640a6ab839aaa4193c21012a50b7dfb0aa42c434e668f99faa0fd04d7
languageName: node
linkType: hard
"@cherrystudio/embedjs-libsql@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-libsql@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-utils": "npm:0.1.29"
"@libsql/client": "npm:^0.14.0"
debug: "npm:^4.4.0"
checksum: 10c0/655da13d5e192bb0d46e36cbe1ee444b290f6d98d39ad4ac72004fa0aa1a2f5c6dc7a41dd2a9631cc80014501e3423fbd3a822f9fcb331617ab0a43db9692410
languageName: node
linkType: hard
"@cherrystudio/embedjs-loader-csv@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-loader-csv@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-utils": "npm:0.1.29"
csv-parse: "npm:^5.6.0"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
checksum: 10c0/7c54791fe836839bf3b6a882a9e4c5656b4f30e54aa4e8967b380bc858fd76e03b2ca39b050bc5c06ffbc9e0c722d91d5dd0acf9edf576ff279805d718dfd437
languageName: node
linkType: hard
"@cherrystudio/embedjs-loader-image@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-loader-image@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-utils": "npm:0.1.29"
"@langchain/core": "npm:^0.3.26"
debug: "npm:^4.4.0"
exifremove: "npm:^1.0.1"
md5: "npm:^2.3.0"
mime: "npm:^4.0.6"
stream-mime-type: "npm:^2.0.0"
checksum: 10c0/cbc43bf0be38ccd231a35ee06f160fee4628267a912f222c2c326e6d383a0477e1faad1910c9cd485ef8857b63d4404a9797c7e6a9661773345a4cca1ce956cf
languageName: node
linkType: hard
"@cherrystudio/embedjs-loader-markdown@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-loader-markdown@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-loader-web": "npm:0.1.29"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
micromark: "npm:^4.0.1"
micromark-extension-gfm: "npm:^3.0.0"
micromark-extension-mdx-jsx: "npm:^3.0.1"
checksum: 10c0/068393c00321a03a7b7881bb9b4b3b115440a91496b4fef43ad9b7f352c763aa59a6a33f69c9db39f2ed4c0a727f7e1dcd28448f7a446a5ec6a62edbd035f5a8
languageName: node
linkType: hard
"@cherrystudio/embedjs-loader-msoffice@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-loader-msoffice@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-utils": "npm:0.1.29"
"@langchain/textsplitters": "npm:^0.1.0"
md5: "npm:^2.3.0"
office-text-extractor: "npm:^3.0.3"
checksum: 10c0/be97eb2278d29f06b569b0aa2fd2b3640caf43207268f5c5cbe16fb77776fe026e4e0d5a9c6360f61c4af439561022f6f4becbe97c2b903d1d446021ab3bf38f
languageName: node
linkType: hard
"@cherrystudio/embedjs-loader-pdf@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-loader-pdf@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-utils": "npm:0.1.29"
"@langchain/textsplitters": "npm:^0.1.0"
md5: "npm:^2.3.0"
office-text-extractor: "npm:^3.0.3"
checksum: 10c0/cd45963f9405cd1b817f9539ad876dd32e214d21b651459822fc9f829105dc4934daf1aded9cc7084efd9dc914901b4b72fc52a7c5aa9fb550454b0e465844cf
languageName: node
linkType: hard
"@cherrystudio/embedjs-loader-sitemap@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-loader-sitemap@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-loader-web": "npm:0.1.29"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
sitemapper: "npm:^3.2.20"
checksum: 10c0/2cb5fba68f3e89026b08274f3d286b46c44192f3e8b499d72984e63f36d174bdc7da6c8122c922b8fd5660fa0bc1fbbdbaecc37dae134467d2a501fd1642f0d2
languageName: node
linkType: hard
"@cherrystudio/embedjs-loader-web@npm:0.1.29, @cherrystudio/embedjs-loader-web@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-loader-web@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-utils": "npm:0.1.29"
"@langchain/textsplitters": "npm:^0.1.0"
debug: "npm:^4.4.0"
html-to-text: "npm:^9.0.5"
md5: "npm:^2.3.0"
checksum: 10c0/a15529e45e309993644db7ee8546f970c0d94fd54baf5044d650a0af66cddb9729af5f3a3fb115c13cd7b7b2ab38bb3809cc088d4c6346e9ca33e478845820f3
languageName: node
linkType: hard
"@cherrystudio/embedjs-loader-xml@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-loader-xml@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
debug: "npm:^4.4.0"
fast-xml-parser: "npm:^4.5.1"
md5: "npm:^2.3.0"
checksum: 10c0/cf24dc1b48d55197f3773a9f7490c521461c6cade86869a333bac6c05dae10529ecdbaa03dbfce0994e07215fe343c9801b81356a6141965aa10d50fe2e6c858
languageName: node
linkType: hard
"@cherrystudio/embedjs-openai@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs-openai@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@langchain/core": "npm:^0.3.26"
"@langchain/openai": "npm:^0.3.16"
debug: "npm:^4.4.0"
checksum: 10c0/10451eb9e0c8f613ea3829b478120890ee44e2a9c7707a48797c21cbd4f4479ad56f86bd38099762900ddf17d42758dba938325eaaa9fae66f71e033c6b64dd5
languageName: node
linkType: hard
"@cherrystudio/embedjs-utils@npm:0.1.29":
version: 0.1.29
resolution: "@cherrystudio/embedjs-utils@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
checksum: 10c0/1b8d8e38207e7588ee134c316bc566dfb68d56023887be61793480cdeac0fc5a2dcc3c72f7727daae801abcaf16e8518ab9dcb7b0b0f7d3a16473a8830ba9dff
languageName: node
linkType: hard
"@cherrystudio/embedjs@npm:^0.1.28":
version: 0.1.29
resolution: "@cherrystudio/embedjs@npm:0.1.29"
dependencies:
"@cherrystudio/embedjs-interfaces": "npm:0.1.29"
"@cherrystudio/embedjs-utils": "npm:0.1.29"
"@langchain/textsplitters": "npm:^0.1.0"
debug: "npm:^4.4.0"
langchain: "npm:^0.3.8"
md5: "npm:^2.3.0"
mime: "npm:^4.0.6"
stream-mime-type: "npm:^2.0.0"
checksum: 10c0/01487ab886e7c5c260fc65dee0d67407988e58fa82a1f0fdc4a332548a570cc63f5d39cc87878a7e3272be547a306fee5ec6caa805614eb4e139ce1259b7f6c9
languageName: node
linkType: hard
"@cherrystudio/mac-system-ocr@npm:^0.1.0":
version: 0.1.0
resolution: "@cherrystudio/mac-system-ocr@npm:0.1.0"
@@ -2178,207 +2359,6 @@ __metadata:
languageName: node
linkType: hard
"@llm-tools/embedjs-interfaces@npm:0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-interfaces@npm:0.1.28"
dependencies:
"@langchain/core": "npm:^0.3.26"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
uuid: "npm:^11.0.3"
checksum: 10c0/51c89f2254da60aa01d911a22e8fe852564609525b95090f71385e653e42ef87b99af8d6450debfd0d5a4baa43ebeef8136109fefb09f19551a969e397d43dc1
languageName: node
linkType: hard
"@llm-tools/embedjs-libsql@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-libsql@npm:0.1.28"
dependencies:
"@libsql/client": "npm:^0.14.0"
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-utils": "npm:0.1.28"
debug: "npm:^4.4.0"
checksum: 10c0/ec2c50d89a9d601618d153839fc1545bef38a8a7475f2b6ed21a5d876d7964b958950872df64719a3a851a6d48a3a613233c854a091726ebdf2d992c1fdadf7f
languageName: node
linkType: hard
"@llm-tools/embedjs-loader-csv@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-loader-csv@npm:0.1.28"
dependencies:
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-utils": "npm:0.1.28"
csv-parse: "npm:^5.6.0"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
checksum: 10c0/dfa0f6825e0d3da9b703381d7e1c9765aea6b99cea2663fc0134e77b2b8bf331f8d8718f812e4efc31416e21450d67b2bf7fe3138a48146b871ab646aa3fd87e
languageName: node
linkType: hard
"@llm-tools/embedjs-loader-image@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-loader-image@npm:0.1.28"
dependencies:
"@langchain/core": "npm:^0.3.26"
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-utils": "npm:0.1.28"
debug: "npm:^4.4.0"
exifremove: "npm:^1.0.1"
md5: "npm:^2.3.0"
mime: "npm:^4.0.6"
stream-mime-type: "npm:^2.0.0"
checksum: 10c0/e9414dde0d10afe1541bfe464b80f5cb61ec523f28dce62ab931a3f4ffde93c5589fde8e7a7f5751e7dc20d68fe2d28883925ba65e2542ee9d43002591568af1
languageName: node
linkType: hard
"@llm-tools/embedjs-loader-markdown@npm:0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-loader-markdown@npm:0.1.28"
dependencies:
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-loader-web": "npm:0.1.28"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
micromark: "npm:^4.0.1"
micromark-extension-gfm: "npm:^3.0.0"
micromark-extension-mdx-jsx: "npm:^3.0.1"
checksum: 10c0/deb86848c57cdaf1aa89cd3382505aa4cc53c170d68105a97da1f6ebaff508ed6db1f164004ae1e0426266c29e15435a5bc092eb37ca4fb81ee574940daf1c0e
languageName: node
linkType: hard
"@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":
version: 0.1.28
resolution: "@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::version=0.1.28&hash=9c7dea"
dependencies:
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-loader-web": "npm:0.1.28"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
micromark: "npm:^4.0.1"
micromark-extension-gfm: "npm:^3.0.0"
micromark-extension-mdx-jsx: "npm:^3.0.1"
checksum: 10c0/4be7354294c9cc1ee5b93e0bf49a218894e5a0ad63f344300a9277751fb7517d7ff9ab51594eb63f548d303cec0c747507a5df29bd92deb506bf9829f1675f67
languageName: node
linkType: hard
"@llm-tools/embedjs-loader-msoffice@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-loader-msoffice@npm:0.1.28"
dependencies:
"@langchain/textsplitters": "npm:^0.1.0"
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-utils": "npm:0.1.28"
md5: "npm:^2.3.0"
office-text-extractor: "npm:^3.0.3"
checksum: 10c0/dbc511938c16ebfcfe9cfedfe68b64b15e2290474092c3600cf290ccb3da5a116a7bd2d469a7f26ee24882cc6835260ad3129ddd54c0f50cc96ef1fa0b142abe
languageName: node
linkType: hard
"@llm-tools/embedjs-loader-pdf@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-loader-pdf@npm:0.1.28"
dependencies:
"@langchain/textsplitters": "npm:^0.1.0"
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-utils": "npm:0.1.28"
md5: "npm:^2.3.0"
office-text-extractor: "npm:^3.0.3"
checksum: 10c0/3ac930561c137b2cc91dc22782c83857e60441a15710d3913351c69f4960dd50ad31a79a1c5f48c2784daf400fd86a03483b80ae3c375e085b1d42a759c9efbf
languageName: node
linkType: hard
"@llm-tools/embedjs-loader-sitemap@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-loader-sitemap@npm:0.1.28"
dependencies:
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-loader-web": "npm:0.1.28"
debug: "npm:^4.4.0"
md5: "npm:^2.3.0"
sitemapper: "npm:^3.2.20"
checksum: 10c0/007f45d02e1200b7cca0bd2f21514910e4b0243126b553404f55c80196b82e673ce09ef1c77f515ab6a6a9397f06ee58799ac8c5f885635ffa759fcd14076fc2
languageName: node
linkType: hard
"@llm-tools/embedjs-loader-web@npm:0.1.28, @llm-tools/embedjs-loader-web@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-loader-web@npm:0.1.28"
dependencies:
"@langchain/textsplitters": "npm:^0.1.0"
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-utils": "npm:0.1.28"
debug: "npm:^4.4.0"
html-to-text: "npm:^9.0.5"
md5: "npm:^2.3.0"
checksum: 10c0/2abf61ebfa8e2f145730cc2676cf49f5f84b32b5f1c90b5dc58c820b13ab02c10f31bca9891fdb675a7f66a421bb407a0a192d069ea48184eb2b4b4b995382bd
languageName: node
linkType: hard
"@llm-tools/embedjs-loader-xml@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-loader-xml@npm:0.1.28"
dependencies:
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
debug: "npm:^4.4.0"
fast-xml-parser: "npm:^4.5.1"
md5: "npm:^2.3.0"
checksum: 10c0/20595caa970a1f715f462a66f949e5f9d931431171cd6a55b70f6aeb9166a84233b397d688594de3764974d9d94c066d104f0e3b0c2b79967404cf452b1f7a93
languageName: node
linkType: hard
"@llm-tools/embedjs-openai@npm:^0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-openai@npm:0.1.28"
dependencies:
"@langchain/core": "npm:^0.3.26"
"@langchain/openai": "npm:^0.3.16"
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
debug: "npm:^4.4.0"
checksum: 10c0/cc8a703acf447a568f30b218ccb1935529a956177a8adac08f7a67fc4f6257f2a61ed21218341835250c9610566e3219bc8cf866a04423f3099631a85f29c32c
languageName: node
linkType: hard
"@llm-tools/embedjs-utils@npm:0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs-utils@npm:0.1.28"
dependencies:
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
checksum: 10c0/7d97b36be831d894102fed34a7e7078eade509b4fbd78d991160f60b2d2d8f3136e6ad93edcf64a3d2b7be346c83cdae9b0ecb63d35aa02eb38dfb4f9cff6117
languageName: node
linkType: hard
"@llm-tools/embedjs@npm:0.1.28":
version: 0.1.28
resolution: "@llm-tools/embedjs@npm:0.1.28"
dependencies:
"@langchain/textsplitters": "npm:^0.1.0"
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-utils": "npm:0.1.28"
debug: "npm:^4.4.0"
langchain: "npm:^0.3.8"
md5: "npm:^2.3.0"
mime: "npm:^4.0.6"
stream-mime-type: "npm:^2.0.0"
checksum: 10c0/0ade20a97b987c5b24175e1e46c7f0917e91b0acde712e9a759fb9fe7f48b04a79fca28c2e4cc8702a25e6cd7fba8a9351bf1f7e5bbf1373f0f60de047703d7a
languageName: node
linkType: hard
"@llm-tools/embedjs@patch:@llm-tools/embedjs@npm%3A0.1.28#~/.yarn/patches/@llm-tools-embedjs-npm-0.1.28-8e4393fa2d.patch":
version: 0.1.28
resolution: "@llm-tools/embedjs@patch:@llm-tools/embedjs@npm%3A0.1.28#~/.yarn/patches/@llm-tools-embedjs-npm-0.1.28-8e4393fa2d.patch::version=0.1.28&hash=51ffc6"
dependencies:
"@langchain/textsplitters": "npm:^0.1.0"
"@llm-tools/embedjs-interfaces": "npm:0.1.28"
"@llm-tools/embedjs-utils": "npm:0.1.28"
debug: "npm:^4.4.0"
langchain: "npm:^0.3.8"
md5: "npm:^2.3.0"
mime: "npm:^4.0.6"
stream-mime-type: "npm:^2.0.0"
checksum: 10c0/2af2bcd2f6476eaf02ed9ff5e189ecb03d663146cd5229c3c32b45fef8a37fd84a48193f8d067eaf9339e89a5035502c4ff48d2370dfd655294daa8b4e365841
languageName: node
linkType: hard
"@malept/cross-spawn-promise@npm:^1.1.0":
version: 1.1.1
resolution: "@malept/cross-spawn-promise@npm:1.1.1"
@@ -3534,19 +3514,12 @@ __metadata:
languageName: node
linkType: hard
"@types/prop-types@npm:*":
version: 15.7.14
resolution: "@types/prop-types@npm:15.7.14"
checksum: 10c0/1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1
languageName: node
linkType: hard
"@types/react-dom@npm:^18.2.18":
version: 18.3.5
resolution: "@types/react-dom@npm:18.3.5"
"@types/react-dom@npm:^19.0.4":
version: 19.0.4
resolution: "@types/react-dom@npm:19.0.4"
peerDependencies:
"@types/react": ^18.0.0
checksum: 10c0/b163d35a6b32a79f5782574a7aeb12a31a647e248792bf437e6d596e2676961c394c5e3c6e91d1ce44ae90441dbaf93158efb4f051c0d61e2612f1cb04ce4faa
"@types/react": ^19.0.0
checksum: 10c0/4e71853919b94df9e746a4bd73f8180e9ae13016333ce9c543dcba9f4f4c8fe6e28b038ca6ee61c24e291af8e03ca3bc5ded17c46dee938fcb32d71186fda7a3
languageName: node
linkType: hard
@@ -3559,7 +3532,7 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:*":
"@types/react@npm:*, @types/react@npm:^19.0.12":
version: 19.0.12
resolution: "@types/react@npm:19.0.12"
dependencies:
@@ -3568,16 +3541,6 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:^18.2.48":
version: 18.3.19
resolution: "@types/react@npm:18.3.19"
dependencies:
"@types/prop-types": "npm:*"
csstype: "npm:^3.0.2"
checksum: 10c0/236bfe0c4748ada1a640f13573eca3e0fc7c9d847b442947adb352b0718d6d285357fd84c33336c8ffb8cbfabc0d58a43a647c7fd79857fecd61fb58ab6f7918
languageName: node
linkType: hard
"@types/responselike@npm:^1.0.0":
version: 1.0.3
resolution: "@types/responselike@npm:1.0.3"
@@ -3845,7 +3808,19 @@ __metadata:
"@agentic/exa": "npm:^7.3.3"
"@agentic/searxng": "npm:^7.3.3"
"@agentic/tavily": "npm:^7.3.3"
"@ant-design/v5-patch-for-react-19": "npm:^1.0.3"
"@anthropic-ai/sdk": "npm:^0.38.0"
"@cherrystudio/embedjs": "npm:^0.1.28"
"@cherrystudio/embedjs-libsql": "npm:^0.1.28"
"@cherrystudio/embedjs-loader-csv": "npm:^0.1.28"
"@cherrystudio/embedjs-loader-image": "npm:^0.1.28"
"@cherrystudio/embedjs-loader-markdown": "npm:^0.1.28"
"@cherrystudio/embedjs-loader-msoffice": "npm:^0.1.28"
"@cherrystudio/embedjs-loader-pdf": "npm:^0.1.28"
"@cherrystudio/embedjs-loader-sitemap": "npm:^0.1.28"
"@cherrystudio/embedjs-loader-web": "npm:^0.1.28"
"@cherrystudio/embedjs-loader-xml": "npm:^0.1.28"
"@cherrystudio/embedjs-openai": "npm:^0.1.28"
"@cherrystudio/mac-system-ocr": "npm:^0.1.0"
"@electron-toolkit/eslint-config-prettier": "npm:^3.0.0"
"@electron-toolkit/eslint-config-ts": "npm:^3.0.0"
@@ -3861,17 +3836,6 @@ __metadata:
"@hello-pangea/dnd": "npm:^16.6.0"
"@kangfenmao/keyv-storage": "npm:^0.1.0"
"@langchain/community": "npm:^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": "npm:^0.1.28"
"@llm-tools/embedjs-loader-csv": "npm:^0.1.28"
"@llm-tools/embedjs-loader-image": "npm:^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": "npm:^0.1.28"
"@llm-tools/embedjs-loader-pdf": "npm:^0.1.28"
"@llm-tools/embedjs-loader-sitemap": "npm:^0.1.28"
"@llm-tools/embedjs-loader-web": "npm:^0.1.28"
"@llm-tools/embedjs-loader-xml": "npm:^0.1.28"
"@llm-tools/embedjs-openai": "npm:^0.1.28"
"@mistralai/mistralai": "npm:^1.5.2"
"@modelcontextprotocol/sdk": "patch:@modelcontextprotocol/sdk@npm%3A1.6.1#~/.yarn/patches/@modelcontextprotocol-sdk-npm-1.6.1-b46313efe7.patch"
"@notionhq/client": "npm:^2.2.15"
@@ -3886,8 +3850,8 @@ __metadata:
"@types/node": "npm:^18.19.9"
"@types/pako": "npm:^1.0.2"
"@types/pdf-parse": "npm:^1.1.4"
"@types/react": "npm:^18.2.48"
"@types/react-dom": "npm:^18.2.18"
"@types/react": "npm:^19.0.12"
"@types/react-dom": "npm:^19.0.4"
"@types/react-infinite-scroll-component": "npm:^5.0.0"
"@types/tinycolor2": "npm:^1"
"@vitejs/plugin-react": "npm:^4.2.1"
@@ -3937,8 +3901,9 @@ __metadata:
pdf-to-img: "npm:^4.4.0"
prettier: "npm:^3.5.3"
proxy-agent: "npm:^6.5.0"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
rc-virtual-list: "npm:^3.18.5"
react: "npm:^19.0.0"
react-dom: "npm:^19.0.0"
react-hotkeys-hook: "npm:^4.6.1"
react-i18next: "npm:^14.1.2"
react-infinite-scroll-component: "npm:^6.1.0"
@@ -3968,9 +3933,6 @@ __metadata:
vite: "npm:^5.0.12"
webdav: "npm:^5.8.0"
zipread: "npm:^1.3.3"
peerDependencies:
react: ^17.0.0 || ^18.0.0
react-dom: ^17.0.0 || ^18.0.0
languageName: unknown
linkType: soft
@@ -9333,7 +9295,7 @@ __metadata:
languageName: node
linkType: hard
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
"js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed
@@ -9941,17 +9903,6 @@ __metadata:
languageName: node
linkType: hard
"loose-envify@npm:^1.1.0":
version: 1.4.0
resolution: "loose-envify@npm:1.4.0"
dependencies:
js-tokens: "npm:^3.0.0 || ^4.0.0"
bin:
loose-envify: cli.js
checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e
languageName: node
linkType: hard
"lop@npm:^0.4.1":
version: 0.4.2
resolution: "lop@npm:0.4.2"
@@ -13493,7 +13444,7 @@ __metadata:
languageName: node
linkType: hard
"rc-virtual-list@npm:^3.14.2, rc-virtual-list@npm:^3.5.1, rc-virtual-list@npm:^3.5.2":
"rc-virtual-list@npm:^3.14.2, rc-virtual-list@npm:^3.18.5, rc-virtual-list@npm:^3.5.1, rc-virtual-list@npm:^3.5.2":
version: 3.18.5
resolution: "rc-virtual-list@npm:3.18.5"
dependencies:
@@ -13522,15 +13473,14 @@ __metadata:
languageName: node
linkType: hard
"react-dom@npm:^18.2.0":
version: 18.3.1
resolution: "react-dom@npm:18.3.1"
"react-dom@npm:^19.0.0":
version: 19.0.0
resolution: "react-dom@npm:19.0.0"
dependencies:
loose-envify: "npm:^1.1.0"
scheduler: "npm:^0.23.2"
scheduler: "npm:^0.25.0"
peerDependencies:
react: ^18.3.1
checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85
react: ^19.0.0
checksum: 10c0/a36ce7ab507b237ae2759c984cdaad4af4096d8199fb65b3815c16825e5cfeb7293da790a3fc2184b52bfba7ba3ff31c058c01947aff6fd1a3701632aabaa6a9
languageName: node
linkType: hard
@@ -13701,12 +13651,10 @@ __metadata:
languageName: node
linkType: hard
"react@npm:^18.2.0":
version: 18.3.1
resolution: "react@npm:18.3.1"
dependencies:
loose-envify: "npm:^1.1.0"
checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3
"react@npm:^19.0.0":
version: 19.0.0
resolution: "react@npm:19.0.0"
checksum: 10c0/9cad8f103e8e3a16d15cb18a0d8115d8bd9f9e1ce3420310aea381eb42aa0a4f812cf047bb5441349257a05fba8a291515691e3cb51267279b2d2c3253f38471
languageName: node
linkType: hard
@@ -14404,12 +14352,10 @@ __metadata:
languageName: node
linkType: hard
"scheduler@npm:^0.23.2":
version: 0.23.2
resolution: "scheduler@npm:0.23.2"
dependencies:
loose-envify: "npm:^1.1.0"
checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78
"scheduler@npm:^0.25.0":
version: 0.25.0
resolution: "scheduler@npm:0.25.0"
checksum: 10c0/a4bb1da406b613ce72c1299db43759526058fdcc413999c3c3e0db8956df7633acf395cb20eb2303b6a65d658d66b6585d344460abaee8080b4aa931f10eaafe
languageName: node
linkType: hard