├── .cursor └── rules │ ├── project.mdc │ └── tool.mdc ├── .editorconfig ├── .erb ├── configs │ ├── .eslintrc │ ├── postcss.config.js │ ├── webpack.config.base.ts │ ├── webpack.config.eslint.ts │ ├── webpack.config.main.dev.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.renderer.dev.ts │ ├── webpack.config.renderer.prod.ts │ └── webpack.paths.ts ├── img │ ├── erb-banner.svg │ ├── erb-logo.png │ └── palette-sponsor-banner.svg ├── mocks │ └── fileMock.js └── scripts │ ├── .eslintrc │ ├── check-build-exists.ts │ ├── check-native-dep.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── clean.js │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── link-modules.ts │ └── notarize.js ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .readthedocs.yaml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── NOTICE ├── README.md ├── assets ├── 69306179-3673-459c-85ab-3c2562e492af.png ├── assets.d.ts ├── entitlements.mac.plist ├── file-drag.png ├── icon.icns ├── icon.ico ├── icon.png ├── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png ├── model-logos │ ├── anthropic.png │ ├── azure_openai.png │ ├── baidu.png │ ├── deepseek.png │ ├── deepspeed.png │ ├── google.png │ ├── groq.png │ ├── lmstudio.png │ ├── minimax.png │ ├── ollama.png │ ├── openai.png │ ├── openrouter.png │ ├── siliconflow.png │ ├── togetherai.png │ ├── tongyi.png │ ├── volcanoengine.png │ └── zhipu.png └── prompts │ ├── manus_planner_prompt.md │ ├── manus_system_prompt.md │ └── manus_system_prompt_en.md ├── docs └── assets │ ├── 20250307003357.png │ ├── 20250307003718.png │ ├── 20250307003731.png │ ├── 20250307003750.png │ └── icon.png ├── package-lock.json ├── package.json ├── release └── app │ ├── package-lock.json │ └── package.json ├── src ├── __tests__ │ ├── Agent.test.ts │ ├── App.test.tsx │ └── Chat.test.ts ├── entity │ ├── Agent.ts │ ├── Chat.ts │ ├── CheckPoints.ts │ ├── KnowledgeBase.ts │ ├── Memoy.ts │ ├── Plugins.ts │ ├── Prompt.ts │ ├── Providers.ts │ ├── Settings.ts │ └── Tools.ts ├── i18n │ ├── index.ts │ └── locales │ │ ├── en-us.json │ │ └── zh-cn.json ├── lib │ └── utils.ts ├── main │ ├── agents │ │ ├── BaseAgent.ts │ │ ├── RagAgent.ts │ │ ├── ToolsAgent.ts │ │ ├── a2a │ │ │ └── Agent2Agent.ts │ │ ├── data_masking │ │ │ └── DataMaskingAgent.ts │ │ ├── document_modify │ │ │ └── DocumentModify.ts │ │ ├── extract │ │ │ ├── ExtractAgent.ts │ │ │ └── prompt.ts │ │ ├── index.ts │ │ ├── manus │ │ │ ├── Actions.ts │ │ │ ├── ManusAgent.ts │ │ │ ├── Memory.ts │ │ │ └── prompt.ts │ │ ├── message_manager │ │ │ └── index.ts │ │ ├── nodes │ │ │ ├── HumanNode.ts │ │ │ ├── LongMemonyNode.ts │ │ │ ├── PlannerNode.ts │ │ │ ├── RouteNode.ts │ │ │ ├── SolverNode.ts │ │ │ └── Summary.ts │ │ ├── plan_execute │ │ │ └── PlanExecuteAgent.ts │ │ ├── planner │ │ │ └── PlannerAgent.ts │ │ ├── rag │ │ │ └── AdvancedRagAgent.ts │ │ ├── react │ │ │ ├── ReActAgent.ts │ │ │ └── messagesStateReducer.ts │ │ ├── rewoo │ │ │ └── ReWOOAgent.ts │ │ ├── script_assistant │ │ │ └── ScriptAssistant.ts │ │ ├── storm │ │ │ └── StormAgent.ts │ │ ├── summary │ │ │ └── SummaryAgent.ts │ │ ├── task │ │ │ └── TaskAgent.ts │ │ ├── tool_callling │ │ │ └── ToolCallingAgent.ts │ │ ├── translate │ │ │ └── TranslateAgent.ts │ │ └── utils.ts │ ├── app │ │ ├── AppManager.tsx │ │ └── NotificationManager.tsx │ ├── chat │ │ ├── ChatResponse.ts │ │ ├── index.ts │ │ └── runAgent.ts │ ├── db │ │ ├── TypeormSaver.ts │ │ ├── index.ts │ │ └── vectorstores │ │ │ ├── LanceDBStore.ts │ │ │ └── index.ts │ ├── embeddings │ │ ├── HuggingFaceTransformersEmbeddings.ts │ │ └── index.ts │ ├── explores │ │ └── index.ts │ ├── ipc │ │ └── ipcListener.ts │ ├── knowledgebase │ │ ├── RerankDocumentCompressor.ts │ │ └── index.ts │ ├── llm │ │ └── index.ts │ ├── loaders │ │ ├── ExcelLoader.ts │ │ ├── ImageLoader.ts │ │ └── index.ts │ ├── main.ts │ ├── memory │ │ └── index.ts │ ├── menu.ts │ ├── plugins │ │ └── PluginsManager.ts │ ├── preload.ts │ ├── prompts │ │ └── index.ts │ ├── providers │ │ ├── BaseProvider.ts │ │ ├── MinimaxProvider.ts │ │ ├── OllamaProvider.ts │ │ ├── VolcanoEngineProvider.ts │ │ └── index.ts │ ├── reranker │ │ └── index.ts │ ├── server │ │ └── serverManager.ts │ ├── settings │ │ ├── LocalModels.json │ │ └── index.ts │ ├── supabase │ │ └── supabaseClient.ts │ ├── tools │ │ ├── Arxiv.ts │ │ ├── AskHuman.ts │ │ ├── BaseTool.ts │ │ ├── BrowserUse.ts │ │ ├── Chartjs.ts │ │ ├── ComfyuiTool.ts │ │ ├── DallE.ts │ │ ├── DateTimeTool.ts │ │ ├── DuckDuckGoSearch.ts │ │ ├── ExcelTool.ts │ │ ├── FileSystemTool.ts │ │ ├── FileToText.ts │ │ ├── FireCrawl.ts │ │ ├── Ideogram.ts │ │ ├── ImageToSvg.ts │ │ ├── KnowledgeBaseQuery.ts │ │ ├── KnowledgeBaseSave.ts │ │ ├── Midjourney.ts │ │ ├── NodejsVM.ts │ │ ├── PythonInterpreter.ts │ │ ├── RapidOcr.ts │ │ ├── RemoveBackground.ts │ │ ├── SearxngSearchTool.ts │ │ ├── Sleep.ts │ │ ├── SocialMediaSearch.ts │ │ ├── SpeechToText.ts │ │ ├── Suro.ts │ │ ├── TavilySearch.ts │ │ ├── TerminalTool.ts │ │ ├── TextToSpeech.ts │ │ ├── Translate.ts │ │ ├── UnixTimestamp.ts │ │ ├── VectorizerAI.ts │ │ ├── Vidu.ts │ │ ├── Vision.ts │ │ ├── WeatherTool.ts │ │ ├── WebLoader.ts │ │ ├── WebSearchTool.ts │ │ ├── XhsTool.ts │ │ ├── index.ts │ │ ├── mcp │ │ │ └── StdioClientTransport.ts │ │ └── websearch │ │ │ └── BraveSearch.ts │ ├── util.ts │ └── utils │ │ ├── common.ts │ │ ├── document_transformers.ts │ │ ├── exec.ts │ │ ├── is.ts │ │ ├── jsonSchemaToZod.ts │ │ ├── messages.ts │ │ ├── path.ts │ │ ├── providerUtil.ts │ │ ├── systemProxy.ts │ │ ├── tokenCounter.ts │ │ └── transformers.ts ├── renderer │ ├── App.css │ ├── App.tsx │ ├── components │ │ ├── Login.tsx │ │ ├── agents │ │ │ └── AgentSelect.tsx │ │ ├── chat │ │ │ ├── ChatAttachment.tsx │ │ │ ├── ChatList.tsx │ │ │ ├── ChatMessageBox.css │ │ │ ├── ChatMessageBox.tsx │ │ │ ├── ChatOption.tsx │ │ │ ├── ChatQuickInput.tsx │ │ │ ├── ChatToolView.tsx │ │ │ └── tool-views │ │ │ │ ├── CodeSandboxView.tsx │ │ │ │ ├── CodeView.tsx │ │ │ │ ├── FileView.tsx │ │ │ │ └── WebSearchView.tsx │ │ ├── common │ │ │ ├── DocumentView.tsx │ │ │ ├── Editor.tsx │ │ │ ├── FileDropZone.css │ │ │ ├── FileDropZone.tsx │ │ │ ├── List.tsx │ │ │ ├── ListItem.tsx │ │ │ ├── Markdown.tsx │ │ │ ├── ProviderIcon.tsx │ │ │ └── ResponseCard.tsx │ │ ├── form │ │ │ ├── BasicForm.tsx │ │ │ ├── FileSelect.tsx │ │ │ ├── FormItem.tsx │ │ │ └── JsonEditor.tsx │ │ ├── layout │ │ │ ├── Content.tsx │ │ │ ├── ShowcaseLayout.tsx │ │ │ ├── Sidebar.css │ │ │ └── Sidebar.tsx │ │ ├── modals │ │ │ └── FormModal.tsx │ │ ├── providers │ │ │ └── ProviderSelect.tsx │ │ ├── theme │ │ │ ├── ThemeProvider.tsx │ │ │ └── ThemeToggle.tsx │ │ ├── tools │ │ │ └── ToolSelect.tsx │ │ └── ui │ │ │ ├── ShinyText │ │ │ ├── ShinyText.css │ │ │ └── ShinyText.tsx │ │ │ └── scroll-area.tsx │ ├── context │ │ ├── AgentsModal.tsx │ │ ├── GlobalContext.tsx │ │ ├── KnowledgeBaseModal.tsx │ │ ├── PromptsModal.tsx │ │ └── ToolsModal.tsx │ ├── index.ejs │ ├── index.tsx │ ├── pages │ │ ├── Agent │ │ │ ├── AgentConfigDrawer.tsx │ │ │ ├── AgentContent.tsx │ │ │ └── AgentPage.tsx │ │ ├── Chat │ │ │ ├── ChatContent.tsx │ │ │ ├── ChatFileContent.tsx │ │ │ ├── ChatOptionsDrawer.tsx │ │ │ ├── ChatPage.tsx │ │ │ └── ChatPlannerContent.tsx │ │ ├── Home.tsx │ │ ├── KnowledgeBase │ │ │ ├── KnowledgeBaseContent.tsx │ │ │ └── KnowledgeBasePage.tsx │ │ ├── Prompts │ │ │ └── PromptsPage.tsx │ │ ├── Providers.tsx │ │ ├── Settings │ │ │ ├── AboutPage.tsx │ │ │ ├── DefaultModelSettings.tsx │ │ │ ├── DefaultWenSearchEnginSettings.tsx │ │ │ ├── GeneralSettings.tsx │ │ │ ├── LocalModelManager.tsx │ │ │ └── index.tsx │ │ └── Tools.tsx │ ├── preload.d.ts │ ├── store │ │ ├── index.ts │ │ ├── providerSlice.ts │ │ └── settingsSlice.ts │ └── styles │ │ └── globals.css └── types │ ├── agent.ts │ ├── chat.ts │ ├── form.ts │ ├── formItem.ts │ ├── index.ts │ ├── notification.ts │ └── webSearchEngine.ts ├── tailwind.config.js └── tsconfig.json /.cursor/rules/project.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | 本项目是使用 @electron-react-boilerplate 为框架开发的本地桌面AI Chat软件 7 | 8 | 技术栈包括 9 | - react 10 | - electron 11 | - langchainjs 12 | - langgraphjs 13 | - typescript 14 | - antd 15 | 16 | 17 | -------------------------------------------------------------------------------- /.cursor/rules/tool.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.erb/configs/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | const autoprefixer = require('autoprefixer'); 3 | 4 | module.exports = { 5 | plugins: [tailwindcss, autoprefixer], 6 | }; 7 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin'; 7 | import webpackPaths from './webpack.paths'; 8 | import { dependencies as externals } from '../../release/app/package.json'; 9 | 10 | const configuration: webpack.Configuration = { 11 | externals: [...Object.keys(externals || {})], 12 | 13 | stats: 'errors-only', 14 | 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.[jt]sx?$/, 19 | exclude: /node_modules/, 20 | use: { 21 | loader: 'ts-loader', 22 | options: { 23 | // Remove this line to enable type checking in webpack builds 24 | transpileOnly: true, 25 | compilerOptions: { 26 | module: 'esnext', 27 | }, 28 | }, 29 | }, 30 | }, 31 | { 32 | test: /\.node$/, 33 | loader: 'node-loader', 34 | }, 35 | ], 36 | }, 37 | 38 | output: { 39 | path: webpackPaths.srcPath, 40 | // https://github.com/webpack/webpack/issues/1114 41 | library: { 42 | type: 'commonjs2', 43 | }, 44 | }, 45 | 46 | /** 47 | * Determine the array of extensions that should be used to resolve modules. 48 | */ 49 | resolve: { 50 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '.node'], 51 | modules: [webpackPaths.srcPath, 'node_modules'], 52 | // There is no need to add aliases here, the paths in tsconfig get mirrored 53 | plugins: [new TsconfigPathsPlugins()], 54 | }, 55 | 56 | plugins: [ 57 | new webpack.EnvironmentPlugin({ 58 | NODE_ENV: 'production', 59 | }), 60 | ], 61 | }; 62 | 63 | export default configuration; 64 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.dev.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for development electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 8 | import { merge } from 'webpack-merge'; 9 | import checkNodeEnv from '../scripts/check-node-env'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import SpeedMeasurePlugin from 'speed-measure-webpack-plugin'; 13 | 14 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 15 | // at the dev webpack config is not accidentally run in a production environment 16 | if (process.env.NODE_ENV === 'production') { 17 | checkNodeEnv('development'); 18 | } 19 | 20 | const configuration: webpack.Configuration = { 21 | devtool: 'inline-source-map', 22 | 23 | mode: 'development', 24 | 25 | target: 'electron-main', 26 | 27 | entry: { 28 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 29 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 30 | }, 31 | 32 | output: { 33 | path: webpackPaths.dllPath, 34 | filename: '[name].bundle.dev.js', 35 | library: { 36 | type: 'umd', 37 | }, 38 | }, 39 | 40 | plugins: [ 41 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 42 | // @ts-ignore 43 | new BundleAnalyzerPlugin({ 44 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 45 | analyzerPort: 8888, 46 | }), 47 | 48 | new webpack.DefinePlugin({ 49 | 'process.type': '"browser"', 50 | }), 51 | ], 52 | 53 | /** 54 | * Disables webpack processing of __dirname and __filename. 55 | * If you run the bundle in node.js it falls back to these values of node.js. 56 | * https://github.com/webpack/webpack/issues/2010 57 | */ 58 | node: { 59 | __dirname: false, 60 | __filename: false, 61 | }, 62 | }; 63 | //const smp = new SpeedMeasurePlugin(); 64 | export default merge(baseConfig, configuration); 65 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const configuration: webpack.Configuration = { 19 | devtool: 'source-map', 20 | 21 | mode: 'production', 22 | 23 | target: 'electron-main', 24 | 25 | entry: { 26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.distMainPath, 32 | filename: '[name].js', 33 | library: { 34 | type: 'umd', 35 | }, 36 | }, 37 | 38 | optimization: { 39 | minimizer: [ 40 | new TerserPlugin({ 41 | parallel: true, 42 | }), 43 | ], 44 | }, 45 | 46 | plugins: [ 47 | new BundleAnalyzerPlugin({ 48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 49 | analyzerPort: 8888, 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'production', 63 | DEBUG_PROD: false, 64 | START_MINIMIZED: false, 65 | }), 66 | 67 | new webpack.DefinePlugin({ 68 | 'process.type': '"browser"', 69 | }), 70 | ], 71 | 72 | /** 73 | * Disables webpack processing of __dirname and __filename. 74 | * If you run the bundle in node.js it falls back to these values of node.js. 75 | * https://github.com/webpack/webpack/issues/2010 76 | */ 77 | node: { 78 | __dirname: false, 79 | __filename: false, 80 | }, 81 | }; 82 | 83 | export default merge(baseConfig, configuration); 84 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | library: { 28 | type: 'umd', 29 | }, 30 | }, 31 | 32 | plugins: [ 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 35 | }), 36 | 37 | /** 38 | * Create global constants which can be configured at compile time. 39 | * 40 | * Useful for allowing different behaviour between development builds and 41 | * release builds 42 | * 43 | * NODE_ENV should be production so that modules do not perform certain 44 | * development checks 45 | * 46 | * By default, use 'development' as NODE_ENV. This can be overriden with 47 | * 'staging', for example, by changing the ENV variables in the npm scripts 48 | */ 49 | new webpack.EnvironmentPlugin({ 50 | NODE_ENV: 'development', 51 | }), 52 | 53 | new webpack.LoaderOptionsPlugin({ 54 | debug: true, 55 | }), 56 | ], 57 | 58 | /** 59 | * Disables webpack processing of __dirname and __filename. 60 | * If you run the bundle in node.js it falls back to these values of node.js. 61 | * https://github.com/webpack/webpack/issues/2010 62 | */ 63 | node: { 64 | __dirname: false, 65 | __filename: false, 66 | }, 67 | 68 | watch: true, 69 | }; 70 | 71 | export default merge(baseConfig, configuration); 72 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const erbPath = path.join(__dirname, '..'); 6 | const erbNodeModulesPath = path.join(erbPath, 'node_modules'); 7 | 8 | const dllPath = path.join(__dirname, '../dll'); 9 | 10 | const srcPath = path.join(rootPath, 'src'); 11 | const srcMainPath = path.join(srcPath, 'main'); 12 | const srcRendererPath = path.join(srcPath, 'renderer'); 13 | 14 | const releasePath = path.join(rootPath, 'release'); 15 | const appPath = path.join(releasePath, 'app'); 16 | const appPackagePath = path.join(appPath, 'package.json'); 17 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 18 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 19 | 20 | const distPath = path.join(appPath, 'dist'); 21 | const distMainPath = path.join(distPath, 'main'); 22 | const distRendererPath = path.join(distPath, 'renderer'); 23 | 24 | const buildPath = path.join(releasePath, 'build'); 25 | 26 | export default { 27 | rootPath, 28 | erbNodeModulesPath, 29 | dllPath, 30 | srcPath, 31 | srcMainPath, 32 | srcRendererPath, 33 | releasePath, 34 | appPath, 35 | appPackagePath, 36 | appNodeModulesPath, 37 | srcNodeModulesPath, 38 | distPath, 39 | distMainPath, 40 | distRendererPath, 41 | buildPath, 42 | }; 43 | -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"', 14 | ), 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"', 22 | ), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | // Find the reason for why the depen 9 | const nativeDeps = fs 10 | .readdirSync('node_modules') 11 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 12 | if (nativeDeps.length === 0) { 13 | process.exit(0); 14 | } 15 | try { 16 | // Find the reason for why the dependency is installed. If it is installed 17 | // because of a devDependency then that is okay. Warn when it is installed 18 | // because of a dependency 19 | const { dependencies: dependenciesObject } = JSON.parse( 20 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString(), 21 | ); 22 | const rootDependencies = Object.keys(dependenciesObject); 23 | 24 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 25 | dependenciesKeys.includes(rootDependency), 26 | ); 27 | if (filteredRootDependencies.length > 0) { 28 | const plural = filteredRootDependencies.length > 1; 29 | console.log(` 30 | ${chalk.whiteBright.bgYellow.bold( 31 | 'Webpack does not work with native dependencies.', 32 | )} 33 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 34 | plural ? 'are native dependencies' : 'is a native dependency' 35 | } and should be installed inside of the "./release/app" folder. 36 | First, uninstall the packages from "./package.json": 37 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 38 | ${chalk.bold( 39 | 'Then, instead of installing the package to the root "./package.json":', 40 | )} 41 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 42 | ${chalk.bold('Install the package to "./release/app/package.json"')} 43 | ${chalk.whiteBright.bgGreen.bold( 44 | 'cd ./release/app && npm install your-package', 45 | )} 46 | Read more about native dependencies at: 47 | ${chalk.bold( 48 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure', 49 | )} 50 | `); 51 | process.exit(1); 52 | } 53 | } catch (e) { 54 | console.log('Native dependencies could not be checked'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`, 12 | ), 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = '1212'; 5 | 6 | detectPort(port, (_err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`, 11 | ), 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const foldersToRemove = [ 5 | webpackPaths.distPath, 6 | webpackPaths.buildPath, 7 | webpackPaths.dllPath, 8 | ]; 9 | 10 | console.log('Removing folders:', foldersToRemove); 11 | 12 | for (const folder of foldersToRemove) { 13 | if (fs.existsSync(folder)) { 14 | fs.rmSync(folder, { recursive: true, force: true }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { rimrafSync } from 'rimraf'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | export default function deleteSourceMaps() { 7 | if (fs.existsSync(webpackPaths.distMainPath)) 8 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), { 9 | glob: true, 10 | }); 11 | if (fs.existsSync(webpackPaths.distRendererPath)) 12 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), { 13 | glob: true, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath, appNodeModulesPath, erbNodeModulesPath } = 5 | webpackPaths; 6 | 7 | if (fs.existsSync(appNodeModulesPath)) { 8 | if (!fs.existsSync(srcNodeModulesPath)) { 9 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 10 | } 11 | if (!fs.existsSync(erbNodeModulesPath)) { 12 | fs.symlinkSync(appNodeModulesPath, erbNodeModulesPath, 'junction'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (process.env.CI !== 'true') { 11 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 12 | return; 13 | } 14 | 15 | if ( 16 | !( 17 | 'APPLE_ID' in process.env && 18 | 'APPLE_ID_PASS' in process.env && 19 | 'APPLE_TEAM_ID' in process.env 20 | ) 21 | ) { 22 | console.warn( 23 | 'Skipping notarizing step. APPLE_ID, APPLE_ID_PASS, and APPLE_TEAM_ID env variables must be set', 24 | ); 25 | return; 26 | } 27 | 28 | const appName = context.packager.appInfo.productFilename; 29 | 30 | await notarize({ 31 | tool: 'notarytool', 32 | appBundleId: build.appId, 33 | appPath: `${appOutDir}/${appName}.app`, 34 | appleId: process.env.APPLE_ID, 35 | appleIdPassword: process.env.APPLE_ID_PASS, 36 | teamId: process.env.APPLE_TEAM_ID, 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | 35 | .data 36 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | plugins: ['@typescript-eslint'], 4 | rules: { 5 | // A temporary hack related to IDE not resolving correct package.json 6 | 'no-console': 'off', 7 | 'no-unused-vars': 'off', 8 | 9 | 'import/no-extraneous-dependencies': 'off', 10 | 'import/prefer-default-export': 'off', 11 | 'react/react-in-jsx-scope': 'off', 12 | 'react/jsx-filename-extension': 'off', 13 | 'import/extensions': 'off', 14 | 'import/no-unresolved': 'off', 15 | 'import/no-import-module-exports': 'off', 16 | 'no-shadow': 'off', 17 | '@typescript-eslint/no-shadow': 'off', 18 | 'max-classes-per-file': 'off', 19 | '@typescript-eslint/no-unused-vars': 'off', 20 | camelcase: 'off', 21 | 'generator-star-spacing': 'off', 22 | 'no-tabs': 'off', 23 | 'no-irregular-whitespace': 'off', 24 | 'no-debugger': 'off', 25 | 'no-restricted-syntax': 'off', 26 | 'no-await-in-loop': 'off', 27 | 'no-void': 'off', 28 | eqeqeq: 'off', 29 | 'no-unused-expressions': 'off', 30 | 'no-underscore-dangle': 'off', 31 | 'no-return-await': 'off', 32 | 'no-plusplus': 'off', 33 | 'class-methods-use-this': 'off', 34 | 'no-else-return': 'off', 35 | 'object-shorthand': 'off', 36 | 'import/order': 'off', 37 | 'spaced-comment': 'off', 38 | 'no-new-func': 'off', 39 | 'no-useless-constructor': 'off', 40 | 'dot-notation': 'off', 41 | 'react/require-default-props': 'off', 42 | 'react/jsx-props-no-spreading': 'off', 43 | 'no-use-before-define': 'off', 44 | 'react/self-closing-comp': 'off', 45 | }, 46 | parserOptions: { 47 | ecmaVersion: 2022, 48 | sourceType: 'module', 49 | }, 50 | settings: { 51 | 'import/resolver': { 52 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 53 | node: { 54 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 55 | moduleDirectory: ['node_modules', 'src/'], 56 | }, 57 | webpack: { 58 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 59 | }, 60 | typescript: {}, 61 | }, 62 | 'import/parsers': { 63 | '@typescript-eslint/parser': ['.ts', '.tsx'], 64 | }, 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | 32 | models 33 | .data/ 34 | data/ 35 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/.readthedocs.yaml -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | "files.watcherExclude": { 8 | ".data/**": true 9 | }, 10 | 11 | "eslint.validate": [ 12 | "javascript", 13 | "javascriptreact", 14 | "html", 15 | "typescriptreact" 16 | ], 17 | 18 | "javascript.validate.enable": false, 19 | "javascript.format.enable": false, 20 | "typescript.format.enable": false, 21 | 22 | "search.exclude": { 23 | ".git": true, 24 | ".eslintcache": true, 25 | ".erb/dll": true, 26 | "release/{build,app/dist}": true, 27 | "node_modules": true, 28 | "npm-debug.log.*": true, 29 | "test/**/__snapshots__": true, 30 | "package-lock.json": true, 31 | "*.{css,sass,scss}.d.ts": true 32 | }, 33 | "i18n-ally.localesPaths": [ 34 | "src/i18n/locales" 35 | ], 36 | "i18next.i18nPaths": "src/i18n/locales", 37 | "i18n-ally.keystyle": "nested", 38 | "i18n-ally.sourceLanguage": "en-us" 39 | } 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.6 (2025.05.07) 2 | ### 优化 3 | - 文件拖入和拖出支持 4 | - 文件显示优化 5 | - 增加模型供应商(baidu,lmstudio) 6 | - think显示 7 | - mcp启动优化 8 | 9 | # 0.1.4 (2025.03.23) 10 | ### 新增 11 | - mac支持 12 | - 更改data path路径 13 | 14 | # 0.1.3 (2025.03.19) 15 | ### 优化 16 | - 优化mcp工具调用能力 17 | - 优化agent界面 18 | - 模型查询 19 | - 删除普通chat模式下的agents选择功能 20 | - 暂时清除file chat模式 21 | - 优化几个tools 22 | - 更新extract agent 23 | - 新增一个planner(来自langmanus) 24 | - 图片导出改用dom-to-image 25 | - 添加一个Logo 26 | 27 | # 0.1.2 (2025.03.15) 28 | ### 修复 29 | - 消息显示bug 30 | 31 | # 0.1.1 (2025.03.14) 32 | ### 新增 33 | - **MCP** 支持MCP工具调用能力 34 | - **ReAct** 支持langgraph的ReAct Agent能力 35 | - **Supervisor** 支持langgraph的Supervisor Agent能力 36 | - **本地TTS** 添加kokoro-multi-lang-v1_1 37 | - **其他模型** 支持rmbg-2.0 38 | 39 | #### 特点 40 | - **LLM能力** 支持LLM能力 41 | - **工具调用能力** 一些本地的tool_calls能力 42 | - **Agents** 支持Agents能力 43 | 44 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Aime Box 2 | Copyright 2025 Aime Box 3 | 4 | This product includes software developed by: 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Aime Box 3 | 4 | 5 | ### 一款基于 langchain + electron开发的多平台桌面端 Chat 客户端 6 | 支持本地知识库,tool调用,多个智能agent调用 7 | 8 | 目标尽量实现全离线本地可执行的智能agent 9 | 10 | ### 🖼 **截图** 11 | ![image](docs/assets/20250307003357.png) 12 | ![image](docs/assets/20250307003718.png) 13 | ![image](docs/assets/20250307003731.png) 14 | ![image](docs/assets/20250307003750.png) 15 | 16 | ### 开始 17 | 1. 添加模型提供商(如Ollama) 18 | 2. 点击管理模型(启用需要的模型) 19 | 3. 聊天页面即可对话聊天 20 | 4. 下载一些本地模型 设置-本地模型管理(如国内调整HuggingfaceUrl为hf-mirror) 21 | - 推介下载 22 | - bge-m3 用于知识库检索 23 | - bge-reranker-large 用于知识库重排序 24 | - RapidOCR-json_v0.2.0 用于win环境下的ocr 25 | - matcha-icefall-zh-baker 用于语音生成 26 | - sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17 用于语音识别 27 | - silero_vad 用于分离说话场景 28 | - sherpa-onnx-pyannote-segmentation-3-0、3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k 用于说话人识别 29 | - rmbg-1.4 用于背景消除 30 | 31 | ### 🌟 **特点** 32 | - 多个模型供应商支持 ollama、openai、deepseek、anthropic等等 33 | - 多种工具调用支持 webloader、websearch、stt、tts、ocr等等 34 | - 本地知识库引擎,使用lancedb 进行本地知识库检索,支持image、docx、doc、text、url做为输入源 35 | - 多agent,目前有数据提取agent、代码助手、翻译助手 36 | - 提示词保存功能 37 | - MCP支持,可接入供模型调用 38 | 39 | 40 | ### 📝 **Todo** 41 | - [x] mac平台兼容 42 | - [ ] 多语言支持 43 | - [ ] 文档对话功能类似于ChatDoc 支持docx、pdf、txt、url、mp4、音频文件等格式的对话 44 | - [x] 浏览器browser-use 45 | - [ ] Tool自动创建工具,输入url或说明文档自动创建工具供模型调用 46 | - [ ] 更多的agents 47 | 48 | ### Agent 49 | 1. `Extract` 数据提取助手,用于对指定文件或文件夹下所有文件进行检索,根据用户给出的字段进行全文或部分抽取,markdown格式输出 50 | 2. `ScriptAssistant` 对用户的任务生成一个python脚本和venv环境执行 51 | 52 | ### 🔌 **技术栈** 53 | 感谢以下开源项目的支持 54 | - [electron-react-boilerplate](https://github.com/electron-react-boilerplate/electron-react-boilerplate) 用于electron开发的react框架模板 55 | - [langchainjs](https://github.com/langchain-ai/langchainjs) agent框架 56 | - [lancedb](https://lancedb.github.io/lancedb/) 本地向量知识库 57 | - [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) 语音识别和语音合成等 58 | - [@huggingface/transformers](https://github.com/huggingface/transformers) 本地运行onnx模型库 59 | 60 | 如有缺漏请联系作者补充 61 | 62 | 63 | 64 | ### 🐞 **Dev** 65 | ```sh 66 | # node 22 67 | npm install 68 | npm run start 69 | ``` 70 | 71 | ### 💼 **Build** 72 | ```sh 73 | # window 74 | npm run package 75 | ``` 76 | 77 | ### 🌐 **About** 78 | author: 781172480@qq.com 79 | 80 | ### ChangeLog 81 | [CHANGELOG](./CHANGELOG.md) 82 | 83 | ## License 84 | 85 | This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details. 86 | 87 | © [Aime Box](https://github.com/AimeBox/aime-box.git) 88 | 89 | -------------------------------------------------------------------------------- /assets/69306179-3673-459c-85ab-3c2562e492af.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/69306179-3673-459c-85ab-3c2562e492af.png -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | import React = require('react'); 5 | 6 | export const ReactComponent: React.FC>; 7 | 8 | const content: string; 9 | export default content; 10 | } 11 | 12 | declare module '*.png' { 13 | const content: string; 14 | export default content; 15 | } 16 | 17 | declare module '*.jpg' { 18 | const content: string; 19 | export default content; 20 | } 21 | 22 | declare module '*.scss' { 23 | const content: Styles; 24 | export default content; 25 | } 26 | 27 | declare module '*.sass' { 28 | const content: Styles; 29 | export default content; 30 | } 31 | 32 | declare module '*.css' { 33 | const content: Styles; 34 | export default content; 35 | } 36 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/file-drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/file-drag.png -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icon.png -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/128x128.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/24x24.png -------------------------------------------------------------------------------- /assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/256x256.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/48x48.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/512x512.png -------------------------------------------------------------------------------- /assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/64x64.png -------------------------------------------------------------------------------- /assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/icons/96x96.png -------------------------------------------------------------------------------- /assets/model-logos/anthropic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/anthropic.png -------------------------------------------------------------------------------- /assets/model-logos/azure_openai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/azure_openai.png -------------------------------------------------------------------------------- /assets/model-logos/baidu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/baidu.png -------------------------------------------------------------------------------- /assets/model-logos/deepseek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/deepseek.png -------------------------------------------------------------------------------- /assets/model-logos/deepspeed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/deepspeed.png -------------------------------------------------------------------------------- /assets/model-logos/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/google.png -------------------------------------------------------------------------------- /assets/model-logos/groq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/groq.png -------------------------------------------------------------------------------- /assets/model-logos/lmstudio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/lmstudio.png -------------------------------------------------------------------------------- /assets/model-logos/minimax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/minimax.png -------------------------------------------------------------------------------- /assets/model-logos/ollama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/ollama.png -------------------------------------------------------------------------------- /assets/model-logos/openai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/openai.png -------------------------------------------------------------------------------- /assets/model-logos/openrouter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/openrouter.png -------------------------------------------------------------------------------- /assets/model-logos/siliconflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/siliconflow.png -------------------------------------------------------------------------------- /assets/model-logos/togetherai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/togetherai.png -------------------------------------------------------------------------------- /assets/model-logos/tongyi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/tongyi.png -------------------------------------------------------------------------------- /assets/model-logos/volcanoengine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/volcanoengine.png -------------------------------------------------------------------------------- /assets/model-logos/zhipu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/assets/model-logos/zhipu.png -------------------------------------------------------------------------------- /assets/prompts/manus_planner_prompt.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | You are a professional task execution planner responsible for breaking down user tasks into detailed, step-by-step execution plans. You excel in prioritizing tasks, decomposing goals, and ensuring efficient execution. Your role is to help users create reasonable, clear, and well-structured execution plans. 4 | 5 | # Current Work Environment Available Tools 6 | 7 | {renderedTools} 8 | 9 | # Current Work Environment Available Professional AI Assistants 10 | 11 | {agentDescription} 12 | 13 | # Workflow 14 | 15 | - If no task plan has been created, first use the `create_plan` tool to generate one. 16 | - Based on the historical message records and completion status, use the `update_steps` tool to edit the current task plan list. This process will repeat until all tasks are completed. 17 | - If a plan is created, I will provide the current Markdown-formatted plan enclosed between "[Here is todo.md start]" and "[Here is todo.md end]" Refer to this plan when using the `update_steps` tool. You can update multiple steps at once if needed. Each step has its own index, such as: 18 | 19 | ```md 20 | # title 21 | ## outline 22 | - 1. [x] step title1 23 | - 2. [-] step title2 24 | - 3. [ ] step title3 25 | - 4. [ ] step title4 26 | ``` 27 | 28 | - Steps marked "[x]" are completed , "[ ]" are not started, and "[-]" are skipped. 29 | - You can use the action `insert_steps` in `update_steps` tool to add steps. I will insert the step below the specified number. 30 | - I will execute the steps in the generated order. Ensure the logical accuracy of the sequence. 31 | - If the current step encounters an error or gets stuck, use the `update_steps` tool to readjust and continue execution. 32 | 33 | # Tools 34 | 35 | - `create_plan`: 36 | - `update_steps`: 37 | 38 | # Create Plan Output Format 39 | 40 | ```json 41 | {{ 42 | "title": "task title", 43 | "outline": [ 44 | {{ 45 | "title": "group title", 46 | "steps": ["step action1", "step action2", "step action3", ...] 47 | }}, 48 | ... 49 | ] 50 | }} 51 | ``` 52 | 53 | # Update Plan Output Format 54 | {{ 55 | "update_status": [ 56 | {{ "index": 1, "status": "done"}}, ... 57 | ], 58 | "update_title": [ 59 | {{ "index": 2, "title": "xxxx"}}, ... 60 | ] 61 | }} 62 | 63 | 64 | # Note 65 | 66 | - You can modify unfinished plans but should not add new plans every time. 67 | - Only steps marked as "Not Started [ ]" will have editable numbering. 68 | - As long as the information is sufficient, avoid frequently deleting or inserting steps. 69 | - Based on the complexity of the main task, consider the number of steps to generate, for example: Simple task: [2-5 steps] ,Complex task: [10-30 steps] 70 | - Current work language **CHINESE** 71 | -------------------------------------------------------------------------------- /assets/prompts/manus_system_prompt.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | 您是一个AI助手,设计用于使用各种工具和功能帮助用户完成广泛的任务。 4 | 5 | ## 当前系统环境 6 | 7 | 系统: macos 8 | 9 | ## 当前响应语言 10 | 11 | - **中文** 12 | 13 | ## 响应规则 14 | 15 | 1. 响应格式:必须始终以这种格式的有效JSON进行响应: 16 | {{ 17 | "current_state":" {{ 18 | "thought": "当前的思考步骤", 19 | "evaluation_previous_goal": "成功|失败|未知,检查先前的目标/行动是否按照任务预期成功。提及是否发生了意外情况。简要说明原因", 20 | "memory": "描述已完成的内容和您需要记住的内容。要非常具体。在此始终计算您已完成某事的次数以及剩余多少。例如:已分析0个网站,共10个。继续执行abc和xyz", 21 | "next_goal": "下一个即时行动需要完成的内容", 22 | "reply": "你的回复使用第一人称(可选)" 23 | }}, 24 | "action": {{ 25 | "action_name": {{ // action_args }} 26 | }}, 27 | }} 28 | 29 | 2. 行动:您可以在列表中指定一个按顺序执行的行动。 30 | 31 | - 如果是简单对话,您可以使用`human_feedback`工具提问或回答用户问题。使用场景: 1. 询问用户建议,需要用户给出选择,最好先给出候选选项或建议. 2. 需要得到用户文件,等待用户上传. 如果使用此工具,"current_state.reply"可以设置为null。 32 | - 当主要任务尚未确定时,如果用户给您一个相对复杂的任务或当前任务有重大变化,请使用`locked_task`工具来确定任务详情或对任务进行适当更改。你必须保留一切用户输入的重要信息,最好一字不漏记录下来。不要轻易修改当前的最终任务。 33 | - 使用`handoff`操作将任务分配给另一个专业AI助手代理。 34 | - 当主要任务确定且评估任务是否需要较长的执行步骤时,使用`plan`操作创建任务执行计划(todo.md)。 35 | - 可以根据todo.md中的完成状态,检查所有项目是否都已标记为已完成"[x]"或已跳过"[-]",根据历史消息记录来确定最终任务是否完成,使用`done`动作来结束任务 36 | 37 | 3. 可用工具 38 | {renderedTools} 39 | 40 | 4. 可用专业AI助手代理 41 | 您可以使用`handoff`操作将任务详情交给其他专业助手。为确保任务有效完成,您需要提供当前任务的摘要和背景,以及非常清晰和具体的任务描述。最后,您应该要求其他助手返回工作详情和关键交付物,如生成的文件路径、分析表格等。 42 | 可用代理列表: 43 | {agentDescription} 44 | 45 | 5. 任务完成: 46 | 47 | - 一旦最终任务完成,立即使用`done`操作作为最后一个操作 48 | - **在完成用户要求的所有内容之前,不要使用`done`** 49 | - 不要臆造行动 50 | - 确保在done文本参数中包含您为最终任务找到的所有信息。不要仅仅说您已完成,而是包括任务所要求的信息。 51 | 52 | ## 工作流程 53 | 54 | 1. 必须在使用`plan`之前确定主要任务,即必须在使用`plan`之前使用`locked_task`。 55 | 2. 根据`todo.md`中的完成进度,确定下一个操作。每个操作必须是单个步骤,避免一次执行多个步骤。 56 | 3. 结合历史完成记录和当前的步骤执行下一步操作,可以使用`handoff`交给助手处理,或者自己使用相应的工具完成 57 | 4. 当前足以完成最终任务时请用`done`结束工作 58 | 59 | ## 注意事项 60 | 61 | - 使用`human_feedback`时不要询问用户过多问题 62 | -------------------------------------------------------------------------------- /docs/assets/20250307003357.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/docs/assets/20250307003357.png -------------------------------------------------------------------------------- /docs/assets/20250307003718.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/docs/assets/20250307003718.png -------------------------------------------------------------------------------- /docs/assets/20250307003731.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/docs/assets/20250307003731.png -------------------------------------------------------------------------------- /docs/assets/20250307003750.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/docs/assets/20250307003750.png -------------------------------------------------------------------------------- /docs/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/docs/assets/icon.png -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aime-box", 3 | "version": "0.1.7", 4 | "description": "Aime Box is a tool agent box for producer.", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "AimeBox", 8 | "email": "781172480@qq.com", 9 | "url": "https://github.com/AimeBox/aime-box" 10 | }, 11 | "main": "./dist/main/main.js", 12 | "scripts": { 13 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "dependencies": { 18 | "@huggingface/transformers": "^3.5.1", 19 | "@lancedb/lancedb": "^0.12.0", 20 | "@langchain/community": "^0.3.42", 21 | "@langchain/core": "^0.3.55", 22 | "@langchain/langgraph-checkpoint-sqlite": "^0.1.4", 23 | "@modelcontextprotocol/sdk": "^1.7.0", 24 | "apache-arrow": "^17.0.0", 25 | "better-sqlite3": "^9.6.0", 26 | "langchain": "^0.3.24", 27 | "playwright": "^1.50.1", 28 | "sharp": "^0.34.2", 29 | "sherpa-onnx-node": "^1.12.0", 30 | "speaker": "^0.5.5", 31 | "sqlite3": "^5.1.7", 32 | "typeorm": "^0.3.20" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/__tests__/Agent.test.ts: -------------------------------------------------------------------------------- 1 | import { dbManager } from '../main/db'; 2 | import { chatManager } from '../main/chat'; 3 | import { toolsManager } from '../main/tools'; 4 | import { agentManager } from '../main/agents'; 5 | import settingsManager from '../main/settings'; 6 | import { kbManager } from '../main/knowledgebase'; 7 | 8 | const main = async () => { 9 | await dbManager.init(); 10 | await chatManager.init(); 11 | await settingsManager.loadSettings(); 12 | await kbManager.init(); 13 | await toolsManager.init(); 14 | await agentManager.init(); 15 | }; 16 | 17 | main(); 18 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import App from '../renderer/App'; 4 | 5 | describe('App', () => { 6 | it('should render', () => { 7 | expect(render()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/__tests__/Chat.test.ts: -------------------------------------------------------------------------------- 1 | import { ChatOpenAI } from "@langchain/openai"; 2 | import {z} from 'zod'; 3 | 4 | const main = async ()=>{ 5 | const llm = new ChatOpenAI({ 6 | apiKey:'NULL', 7 | modelName: "chat-v3", 8 | configuration: { 9 | apiKey: "NULL", 10 | baseURL: "http://127.0.0.1:3000/v1", 11 | } 12 | }); 13 | const wll = llm.withStructuredOutput(z.object({ 14 | time: z.string() 15 | })) 16 | const res = await wll.invoke("Hello,now date time is 2025-12-12 10:45:22"); 17 | console.log(res) 18 | debugger; 19 | } 20 | 21 | 22 | 23 | 24 | 25 | main() -------------------------------------------------------------------------------- /src/entity/Agent.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | import { 3 | Entity, 4 | Column, 5 | PrimaryColumn, 6 | OneToMany, 7 | ManyToOne, 8 | RelationOptions, 9 | JoinColumn, 10 | Index, 11 | } from 'typeorm'; 12 | 13 | @Entity('agent') 14 | export class Agent { 15 | constructor( 16 | id: string, 17 | name: string, 18 | description?: string, 19 | prompt?: string, 20 | type?: string | 'react' | 'supervisor' | 'built-in' | 'a2a' | 'anp', 21 | tools?: any[], 22 | agents?: any[], 23 | model?: string, 24 | recursionLimit?: number, 25 | config?: any, 26 | ) { 27 | this.id = id || uuidv4(); 28 | this.name = name; 29 | this.description = description; 30 | this.prompt = prompt; 31 | this.type = type; 32 | this.tools = tools; 33 | this.agents = agents; 34 | this.model = model; 35 | this.recursionLimit = recursionLimit; 36 | this.config = config; 37 | this.static = false; 38 | } 39 | 40 | @PrimaryColumn() 41 | id!: string; 42 | 43 | @Index({ unique: true }) 44 | @Column() 45 | name!: string; 46 | 47 | @Column({ nullable: true }) 48 | description?: string; 49 | 50 | @Column({ nullable: true }) 51 | prompt?: string; 52 | 53 | @Column({ type: 'json', nullable: true }) 54 | tags?: string[]; 55 | 56 | @Column({ nullable: true }) 57 | type?: string | 'react' | 'supervisor' | 'built-in' | 'a2a' | 'anp'; 58 | 59 | @Column({ type: 'json', nullable: true }) 60 | tools?: any[]; 61 | 62 | @Column({ type: 'json', nullable: true }) 63 | agents?: any[]; 64 | 65 | @Column({ nullable: true }) 66 | model?: string; 67 | 68 | @Column({ type: 'json', nullable: true }) 69 | config?: any; 70 | 71 | @Column({ nullable: true }) 72 | static?: boolean; 73 | 74 | @Column({ nullable: true }) 75 | mermaid?: string; 76 | 77 | @Column({ nullable: true }) 78 | supervisorOutputMode?: string; 79 | 80 | @Column({ nullable: true }) 81 | recursionLimit?: number; 82 | 83 | @Column({ nullable: true }) 84 | remote_url?: string; 85 | } 86 | -------------------------------------------------------------------------------- /src/entity/CheckPoints.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | import { Entity, Column, PrimaryColumn } from 'typeorm'; 3 | 4 | @Entity('langgraph_checkpoints') 5 | export class LanggraphCheckPoints { 6 | @PrimaryColumn() 7 | thread_id!: string; 8 | 9 | @PrimaryColumn() 10 | checkpoint_ns!: string; 11 | 12 | @PrimaryColumn() 13 | checkpoint_id!: string; 14 | 15 | @Column({ nullable: true }) 16 | parent_checkpoint_id?: string; 17 | 18 | @Column({ nullable: true }) 19 | type?: string; 20 | 21 | @Column({ nullable: true, type: 'blob' }) 22 | checkpoint?: Buffer; 23 | 24 | @Column({ nullable: true, type: 'blob' }) 25 | metadata?: Buffer; 26 | } 27 | 28 | @Entity('langgraph_writes') 29 | export class LanggraphWrites { 30 | @PrimaryColumn() 31 | thread_id!: string; 32 | 33 | @PrimaryColumn() 34 | checkpoint_ns!: string; 35 | 36 | @PrimaryColumn() 37 | checkpoint_id!: string; 38 | 39 | @PrimaryColumn() 40 | task_id!: string; 41 | 42 | @PrimaryColumn() 43 | idx!: number; 44 | 45 | @Column({ nullable: false }) 46 | channel!: string; 47 | 48 | @Column({ nullable: true }) 49 | type?: string; 50 | 51 | @Column({ nullable: true, type: 'blob' }) 52 | value?: Buffer; 53 | } 54 | -------------------------------------------------------------------------------- /src/entity/KnowledgeBase.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryColumn, 5 | RelationOptions, 6 | ManyToOne, 7 | OneToMany, 8 | JoinColumn, 9 | } from 'typeorm'; 10 | 11 | export enum VectorStoreType { 12 | LanceDB = 'lancedb', 13 | Vectra = 'vectra', 14 | PGVector = 'pgvector', 15 | Milvus = 'milvus', 16 | } 17 | 18 | @Entity('knowledgebase') 19 | export class KnowledgeBase { 20 | @PrimaryColumn() 21 | id!: string; 22 | 23 | @Column() 24 | name!: string; 25 | 26 | @Column() 27 | description?: string; 28 | 29 | @Column('json', { nullable: true }) 30 | tags?: any; 31 | 32 | @Column({ enum: VectorStoreType }) 33 | vectorStoreType?: VectorStoreType; 34 | 35 | @Column('json', { nullable: true }) 36 | vectorStoreConfig?: any; 37 | 38 | @Column() 39 | embedding: string; 40 | 41 | @Column({ nullable: true }) 42 | reranker?: string; 43 | 44 | @OneToMany((type) => KnowledgeBaseItem, (item) => item.knowledgeBase) // note: we will create author property in the Photo class below 45 | items?: KnowledgeBaseItem[]; 46 | 47 | @Column({ default: true }) 48 | isPrivate!: boolean; 49 | } 50 | 51 | export enum KnowledgeBaseItemState { 52 | Pending = 'pending', 53 | Processing = 'processing', 54 | Completed = 'completed', 55 | Fail = 'fail', 56 | } 57 | export enum KnowledgeBaseSourceType { 58 | Web = 'web', 59 | File = 'file', 60 | Folder = 'folder', 61 | Text = 'text', 62 | } 63 | @Entity('knowledgebase_item') 64 | export class KnowledgeBaseItem { 65 | @PrimaryColumn() 66 | id!: string; 67 | 68 | @Column({ nullable: false }) 69 | knowledgeBaseId!: string; 70 | 71 | @ManyToOne((type) => KnowledgeBase, (kb) => kb.items, { 72 | nullable: false, 73 | onDelete: 'CASCADE', 74 | onUpdate: 'CASCADE', 75 | } as RelationOptions) 76 | @JoinColumn() 77 | knowledgeBase!: KnowledgeBase; 78 | 79 | @Column() 80 | name!: string; 81 | 82 | @Column({ nullable: true }) 83 | source?: string; 84 | 85 | @Column({ enum: KnowledgeBaseSourceType }) 86 | sourceType?: KnowledgeBaseSourceType; 87 | 88 | @Column('json', { nullable: true }) 89 | tags?: any; 90 | 91 | @Column('json', { nullable: true }) 92 | metadata?: any; 93 | 94 | @Column() 95 | isEnable: boolean = true; 96 | 97 | @Column({ enum: KnowledgeBaseItemState }) 98 | state!: KnowledgeBaseItemState; 99 | 100 | @Column({ nullable: true }) 101 | chunkCount?: number; 102 | 103 | @Column() 104 | timestamp!: number; 105 | 106 | @Column({ nullable: true }) 107 | sha256?: string; 108 | 109 | @Column('json', { nullable: true }) 110 | config?: any; 111 | 112 | @Column({ nullable: true }) 113 | content?: string; 114 | } 115 | -------------------------------------------------------------------------------- /src/entity/Memoy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryColumn, 5 | RelationOptions, 6 | ManyToOne, 7 | OneToMany, 8 | JoinColumn, 9 | } from 'typeorm'; 10 | 11 | @Entity('memoy-history') 12 | export class MemoyHistory { 13 | @PrimaryColumn() 14 | id!: string; 15 | 16 | @Column() 17 | memoryId!: string; 18 | 19 | @Column({ nullable: true }) 20 | userId?: string; 21 | 22 | @Column({ nullable: true }) 23 | agentId?: string; 24 | 25 | @Column({ nullable: true }) 26 | sessionId?: string; 27 | 28 | @Column({ nullable: true }) 29 | preValue?: string; 30 | 31 | @Column({ nullable: true }) 32 | newValue?: string; 33 | 34 | @Column('json', { nullable: true }) 35 | categories?: any; 36 | 37 | @Column({ nullable: true }) 38 | event?: string; 39 | 40 | @Column() 41 | timestamp!: number; 42 | 43 | @Column() 44 | isDeleted: boolean; 45 | } 46 | -------------------------------------------------------------------------------- /src/entity/Plugins.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryColumn, 5 | RelationOptions, 6 | ManyToOne, 7 | OneToMany, 8 | JoinColumn, 9 | } from 'typeorm'; 10 | 11 | @Entity('plugins') 12 | export class Plugins { 13 | @PrimaryColumn() 14 | id!: string; 15 | 16 | @Column() 17 | name!: string; 18 | 19 | @Column() 20 | description?: string; 21 | 22 | @Column() 23 | version!: string; 24 | 25 | @Column() 26 | author?: string; 27 | 28 | @Column() 29 | path!: string; 30 | 31 | @Column() 32 | isEnable: boolean; 33 | } 34 | -------------------------------------------------------------------------------- /src/entity/Prompt.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryColumn, 5 | OneToMany, 6 | ManyToOne, 7 | RelationOptions, 8 | JoinColumn, 9 | } from 'typeorm'; 10 | 11 | export class PromptItem { 12 | role?: string; 13 | 14 | content: string; 15 | } 16 | @Entity('prompt_group') 17 | export class PromptGroup { 18 | @PrimaryColumn() 19 | id!: string; 20 | 21 | @Column() 22 | name!: string; 23 | 24 | @OneToMany((type) => Prompt, (prompt) => prompt.group) 25 | prompts?: Prompt[]; 26 | } 27 | 28 | @Entity('prompt') 29 | export class Prompt { 30 | @PrimaryColumn() 31 | id!: string; 32 | 33 | @Column({ nullable: true }) 34 | role?: string; 35 | 36 | @Column({ nullable: true }) 37 | content: string; 38 | 39 | @Column({ nullable: true }) 40 | description?: string; 41 | 42 | @Column({ type: 'json', nullable: true }) 43 | tags?: string[]; 44 | 45 | @ManyToOne((type) => PromptGroup, (group) => group.prompts, { 46 | nullable: false, 47 | onDelete: 'CASCADE', 48 | onUpdate: 'CASCADE', 49 | } as RelationOptions) 50 | @JoinColumn() 51 | group!: PromptGroup; 52 | 53 | @Column() 54 | timestamp!: number; 55 | } 56 | -------------------------------------------------------------------------------- /src/entity/Providers.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryColumn, Index } from 'typeorm'; 2 | 3 | export enum ProviderType { 4 | OLLAMA = 'ollama', 5 | OPENAI = 'openai', 6 | TONGYI = 'tongyi', 7 | ZHIPU = 'zhipu', 8 | GROQ = 'groq', 9 | ANTHROPIC = 'anthropic', 10 | TOGETHERAI = 'togetherai', 11 | GOOGLE = 'google', 12 | OPENROUTER = 'openrouter', 13 | SILICONFLOW = 'siliconflow', 14 | DEEPSEEK = 'deepseek', 15 | BAIDU = 'baidu', 16 | LMSTUDIO = 'lmstudio', 17 | AZURE_OPENAI = 'azure_openai', 18 | VOLCANOENGINE = 'volcanoengine', 19 | MINIMAX = 'minimax', 20 | } 21 | 22 | @Entity('providers') 23 | export class Providers { 24 | @PrimaryColumn() 25 | id!: string; 26 | 27 | @Column() 28 | @Index({ unique: true }) 29 | name!: string; 30 | 31 | @Column({ enum: ProviderType }) 32 | type!: string; 33 | 34 | @Column({ nullable: true }) 35 | api_base?: string; 36 | 37 | @Column() 38 | api_key?: string; 39 | 40 | @Column('json', { nullable: true }) 41 | models?: any; 42 | 43 | static: boolean; 44 | 45 | @Column('json', { nullable: true }) 46 | config?: any; 47 | 48 | @Column({ nullable: true }) 49 | icon?: string; 50 | } 51 | -------------------------------------------------------------------------------- /src/entity/Settings.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity('settings') 4 | export default class Settings { 5 | @PrimaryColumn() 6 | id!: string; 7 | 8 | @Column({ nullable: true }) 9 | value?: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/entity/Tools.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | import { 3 | Entity, 4 | Column, 5 | PrimaryColumn, 6 | OneToMany, 7 | ManyToOne, 8 | RelationOptions, 9 | JoinColumn, 10 | Index, 11 | } from 'typeorm'; 12 | 13 | @Entity('tools') 14 | export class Tools { 15 | constructor( 16 | name: string, 17 | description?: string, 18 | type?: string | 'custom' | 'mcp' | 'built-in', 19 | config?: any, 20 | ) { 21 | this.name = name; 22 | this.description = description; 23 | this.type = type; 24 | this.config = config; 25 | } 26 | 27 | @PrimaryColumn() 28 | name!: string; 29 | 30 | @Column({ nullable: true }) 31 | description?: string; 32 | 33 | @Column({ type: 'json', nullable: true }) 34 | config?: any; 35 | 36 | @Column() 37 | type!: string | 'custom' | 'mcp' | 'built-in'; 38 | 39 | @Column() 40 | enabled!: boolean; 41 | 42 | @Column({ nullable: true }) 43 | toolkit_name?: string; 44 | 45 | @Column({ type: 'json', nullable: true }) 46 | tags?: string[]; 47 | 48 | @Column({ nullable: true }) 49 | mcp_id?: string; 50 | } 51 | 52 | @Entity('mcp_servers') 53 | export class McpServers { 54 | constructor( 55 | id: string, 56 | name: string, 57 | description?: string, 58 | type?: 'sse' | 'stdio' | 'ws', 59 | command?: string, 60 | 61 | config?: any, 62 | env?: any, 63 | ) { 64 | this.id = id; 65 | this.name = name; 66 | this.description = description; 67 | this.type = type; 68 | this.command = command; 69 | this.config = config; 70 | this.env = env; 71 | } 72 | 73 | @PrimaryColumn() 74 | id!: string; 75 | 76 | @Column() 77 | @Index({ unique: true }) 78 | name!: string; 79 | 80 | @Column({ nullable: true }) 81 | description?: string; 82 | 83 | @Column({ type: 'json', nullable: true }) 84 | config?: any; 85 | 86 | @Column({ type: 'json', nullable: true }) 87 | env?: any; 88 | 89 | @Column() 90 | type!: string | 'sse' | 'command'; 91 | 92 | @Column() 93 | enabled!: boolean; 94 | 95 | @Column() 96 | command!: string; 97 | 98 | @Column({ nullable: true }) 99 | version?: string; 100 | } 101 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import LanguageDetector from 'i18next-browser-languagedetector'; 4 | import enUs from './locales/en-us.json'; 5 | import zhCn from './locales/zh-cn.json'; 6 | 7 | const option = { 8 | fallbackLng: 'zh-cn', 9 | debug: process.env.NODE_ENV !== 'production', 10 | resources: { 11 | 'en-US': { 12 | translation: enUs, 13 | }, 14 | 'zh-CN': { 15 | translation: zhCn, 16 | }, 17 | }, 18 | interpolation: { 19 | escapeValue: false, // not needed for react!! 20 | }, 21 | }; 22 | i18n.use(LanguageDetector).use(initReactI18next).init(option); 23 | 24 | export default i18n; 25 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/agents/BaseAgent.ts: -------------------------------------------------------------------------------- 1 | import { ChatOptions } from '@/entity/Chat'; 2 | import { FormSchema } from '@/types/form'; 3 | import { Tool } from '@langchain/core/tools'; 4 | import { z, ZodObject } from 'zod'; 5 | import { dbManager } from '../db'; 6 | import { Agent } from '@/entity/Agent'; 7 | import { removeEmptyValues } from '../utils/common'; 8 | import { BaseStore, Annotation } from '@langchain/langgraph'; 9 | import { BaseChatModel } from '@langchain/core/language_models/chat_models'; 10 | import { BaseMessage } from '@langchain/core/messages'; 11 | 12 | 13 | export interface AgentMessageEvent { 14 | created?: (msg: BaseMessage[]) => Promise; 15 | updated?: (msg: BaseMessage[]) => Promise; 16 | finished?: (msg: BaseMessage[]) => Promise; 17 | deleted?: (msg: BaseMessage[]) => Promise; 18 | } 19 | 20 | export const BaseAnnotation = { 21 | waitHumanAsk: Annotation, 22 | }; 23 | 24 | 25 | 26 | 27 | 28 | export abstract class BaseAgent extends Tool { 29 | abstract name: string; 30 | 31 | abstract description: string; 32 | 33 | abstract tags: string[]; 34 | 35 | declare schema; 36 | 37 | declare opener?: string; 38 | 39 | declare fixedThreadId?: boolean; 40 | 41 | agentOptions?: { 42 | provider: string; 43 | modelName: string; 44 | options: ChatOptions; 45 | }; 46 | 47 | configSchema: FormSchema[]; 48 | 49 | config: any; 50 | 51 | abstract hidden: boolean; 52 | 53 | constructor(options: { 54 | provider: string; 55 | modelName: string; 56 | options: ChatOptions; 57 | }) { 58 | super(); 59 | this.agentOptions = options; 60 | } 61 | 62 | async getConfig(): Promise { 63 | const agentRepository = dbManager.dataSource.getRepository(Agent); 64 | const agent = await agentRepository.findOne({ 65 | where: { 66 | id: this.name.toLowerCase(), 67 | }, 68 | }); 69 | 70 | let config = { ...(this.config || {}), ...(agent?.config || {}) }; 71 | config = removeEmptyValues(config); 72 | return { ...(this.config || {}), ...config }; 73 | } 74 | 75 | abstract createAgent(params: { 76 | store?: BaseStore; 77 | model?: BaseChatModel; 78 | messageEvent?: AgentMessageEvent; 79 | chatOptions?: ChatOptions; 80 | signal?: AbortSignal; 81 | configurable?: Record; 82 | }): Promise; 83 | 84 | // abstract invoke(input: z.infer | string): Promise; 85 | } 86 | -------------------------------------------------------------------------------- /src/main/agents/document_modify/DocumentModify.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/agents/extract/prompt.ts: -------------------------------------------------------------------------------- 1 | export const ExtractAgentSystemPrompt = ` 2 | 因为你的任务是提取用户给定的文件/文件夹路径中文件的抽取字段信息 3 | step1:判断用户是否输入了路径信息,如果没有请先询问用户处理的文件/文件夹路径 4 | step2:判断用户输入的信息是否给出了需要抽取的字段描述 5 | - 如果没有描述抽取的字段请先询问用户需要抽取的字段(不提供建议) 6 | - 如果抽取的描述很模糊你应该给出一些字段建议供用户参考 7 | step3:如果用户给出了需要提取的字段描述和路径,请使用工具` 8 | -------------------------------------------------------------------------------- /src/main/agents/manus/Actions.ts: -------------------------------------------------------------------------------- 1 | import { ComponentTypes } from '@/types'; 2 | import { z } from 'zod'; 3 | 4 | export type BaseAction = { 5 | name: string; 6 | description: string; 7 | schema: z.ZodTypeAny; 8 | }; 9 | 10 | const DoneAction: BaseAction = { 11 | name: 'done', 12 | description: 13 | 'Call this tool when you are done with the task, and supply your answer or summary.', 14 | schema: z.object({ 15 | question: z.string(), 16 | }), 17 | }; 18 | 19 | const PlanAction: BaseAction = { 20 | name: 'plan', 21 | description: '任务规划', 22 | schema: z.object({}), 23 | }; 24 | 25 | const HumanFeedbackAction: BaseAction = { 26 | name: 'human_feedback', 27 | description: 28 | '任务的详细总结,若有文件则在全文最后使用文件路径输出文件', 29 | schema: z.object({ 30 | question: z.string(), 31 | form: z.array( 32 | z 33 | .object({ 34 | component: z.enum(ComponentTypes), 35 | componentProps: z.any().optional(), 36 | subLabel: z.string().optional(), 37 | label: z.string(), 38 | field: z.string(), 39 | required: z.boolean().default(false).optional(), 40 | }) 41 | .describe('表单项') 42 | .optional(), 43 | ), 44 | }), 45 | }; 46 | 47 | const HandoffAction: BaseAction = { 48 | name: 'handoff', 49 | description: '将任务交给专业agent完成', 50 | schema: z.object({ agent_name: z.string(), task: z.string() }), 51 | }; 52 | 53 | const LockedTaskAction: BaseAction = { 54 | name: 'locked_task', 55 | description: 'Lock the task for subsequent loop processing.', 56 | schema: z.object({ 57 | task: z.string(), 58 | }), 59 | }; 60 | 61 | export { 62 | DoneAction, 63 | PlanAction, 64 | HumanFeedbackAction, 65 | HandoffAction, 66 | LockedTaskAction, 67 | }; 68 | -------------------------------------------------------------------------------- /src/main/agents/manus/Memory.ts: -------------------------------------------------------------------------------- 1 | export class Memory { 2 | private memory: MemoryItem[] = []; 3 | 4 | constructor(memory?: MemoryItem[]) { 5 | if (memory) { 6 | this.memory = memory; 7 | } 8 | } 9 | 10 | add(description: string, content: string) { 11 | this.memory.push( 12 | new MemoryItem(this.memory.length.toString(), description, content), 13 | ); 14 | } 15 | 16 | get() { 17 | return this.memory; 18 | } 19 | 20 | clear() { 21 | this.memory = []; 22 | } 23 | 24 | print() { 25 | return this.memory 26 | .map((item) => `[${item.id}]: ${item.description}`) 27 | .join('\n'); 28 | } 29 | } 30 | 31 | export class MemoryItem { 32 | description: string; 33 | 34 | id: string; 35 | 36 | content: string; 37 | 38 | constructor(id: string, description: string, content: string) { 39 | this.id = id; 40 | this.description = description; 41 | this.content = content; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/agents/nodes/HumanNode.ts: -------------------------------------------------------------------------------- 1 | import { AIMessage } from '@langchain/core/messages'; 2 | import { Command, interrupt, MessagesAnnotation } from '@langchain/langgraph'; 3 | 4 | const HumanNode = (state: typeof MessagesAnnotation.State) => { 5 | const value = interrupt({ 6 | text_to_revise: state.messages[state.messages.length - 1].text, 7 | }); 8 | return new Command({ 9 | update: { 10 | messages: [...state.messages, value], 11 | }, 12 | goto: 'manus', 13 | }); 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /src/main/agents/nodes/LongMemonyNode.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/src/main/agents/nodes/LongMemonyNode.ts -------------------------------------------------------------------------------- /src/main/agents/nodes/RouteNode.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChatPromptTemplate, 3 | MessagesPlaceholder, 4 | PromptTemplate, 5 | SystemMessagePromptTemplate, 6 | HumanMessagePromptTemplate, 7 | } from '@langchain/core/prompts'; 8 | import { 9 | BaseChatModel, 10 | BaseChatModelParams, 11 | } from '@langchain/core/language_models/chat_models'; 12 | import { JsonOutputFunctionsParser } from '@langchain/core/output_parsers/openai_functions'; 13 | import { z } from 'zod'; 14 | import { zodToJsonSchema } from 'zod-to-json-schema'; 15 | import { OllamaFunctions } from '@langchain/community/experimental/chat_models/ollama_functions'; 16 | import { RunnableLambda } from '@langchain/core/runnables'; 17 | import { 18 | AIMessage, 19 | BaseMessage, 20 | HumanMessage, 21 | SystemMessage, 22 | ToolMessage, 23 | } from '@langchain/core/messages'; 24 | import { ChatGroq } from '@langchain/groq'; 25 | 26 | export const RouteNode = async ( 27 | llm: BaseChatModel, 28 | roles: { name: string; describe: string }[], 29 | ): Promise => { 30 | const prompt_template = ChatPromptTemplate.fromTemplate( 31 | `你是一个意图分类助手,根据用户的问题分派给指定的\`role\`去处理 32 | 33 | =====角色===== 34 | {roles} 35 | ============== 36 | 37 | 用户问题:{question}`, 38 | ); 39 | const rolesName = roles.map((x) => x.name) as string[]; 40 | const _roles = roles 41 | .map((x) => { 42 | return `${x.name}: ${x.describe}`; 43 | }) 44 | .join('\n'); 45 | const outputSchema = z.object({ 46 | role: z.enum(rolesName as any), 47 | }); 48 | const llmwithStructured = llm.withStructuredOutput(outputSchema); 49 | 50 | // const functions = [ 51 | // { 52 | // name: 'role', 53 | // parameters: zodToJsonSchema(z.object({ role: z.enum(rolesName as any) })), 54 | // }, 55 | // ]; 56 | // const llm_with_functions = (llm as any).bind({ 57 | // functions: functions, 58 | // function_call: functions[0], 59 | // }); 60 | // console.log(functions); 61 | 62 | const chain = (await prompt_template.partial({ roles: _roles })).pipe( 63 | llmwithStructured, 64 | ); 65 | //.pipe(new JsonOutputFunctionsParser({ argsOnly: true })); 66 | // .pipe( 67 | // new RunnableLambda({ 68 | // func: (ai_message: BaseMessage, options: any) => { 69 | // ai_message.name = options.name; 70 | // return ai_message; 71 | // }, 72 | // }), 73 | // ); 74 | 75 | return chain; 76 | }; 77 | -------------------------------------------------------------------------------- /src/main/agents/nodes/SolverNode.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChatPromptTemplate, 3 | MessagesPlaceholder, 4 | PromptTemplate, 5 | SystemMessagePromptTemplate, 6 | HumanMessagePromptTemplate, 7 | } from '@langchain/core/prompts'; 8 | import { 9 | BaseChatModel, 10 | BaseChatModelParams, 11 | } from '@langchain/core/language_models/chat_models'; 12 | import { 13 | JsonOutputParser, 14 | StringOutputParser, 15 | } from '@langchain/core/output_parsers'; 16 | 17 | export const SolverNode = (llm: BaseChatModel) => { 18 | const prompt = `Solve the following task or problem. To solve the problem, we have made step-by-step Plan and \ 19 | retrieved corresponding Evidence to each Plan. Use them with caution since long evidence might \ 20 | contain irrelevant information. 21 | 22 | {plan} 23 | 24 | Now solve the question or task according to provided Evidence above. Respond with the answer 25 | directly with no extra words. 26 | 27 | Task: {task} 28 | Response:`; 29 | // const prompt_template = ChatPromptTemplate.fromMessages([ 30 | // new HumanMessagePromptTemplate(prompt), 31 | // ]); 32 | const prompt_template = PromptTemplate.fromTemplate(prompt); 33 | const node = prompt_template.pipe(llm).pipe(new StringOutputParser()); 34 | return node; 35 | }; 36 | -------------------------------------------------------------------------------- /src/main/agents/nodes/Summary.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JsonOutputParser, 3 | StringOutputParser, 4 | } from '@langchain/core/output_parsers'; 5 | import { 6 | ChatPromptTemplate, 7 | MessagesPlaceholder, 8 | PromptTemplate, 9 | SystemMessagePromptTemplate, 10 | HumanMessagePromptTemplate, 11 | } from '@langchain/core/prompts'; 12 | 13 | const Summary = async (llm) => { 14 | const prompt = PromptTemplate.fromTemplate( 15 | ` 16 | {question} 17 | 18 | 19 | 20 | {generations} 21 | 22 | 23 | 根据问题合并以上信息,可以排除无效的回答. 24 | 25 | 26 | 结果: 27 | `, 28 | ); 29 | const summary_chain = prompt.pipe(llm).pipe(new StringOutputParser()); 30 | return summary_chain; 31 | }; 32 | export default Summary; 33 | -------------------------------------------------------------------------------- /src/main/agents/react/messagesStateReducer.ts: -------------------------------------------------------------------------------- 1 | import { coerceMessageLikeToMessage } from '@langchain/core/messages'; 2 | import { v4 } from 'uuid'; 3 | 4 | /** 5 | * Prebuilt reducer that combines returned messages. 6 | * Can handle standard messages and special modifiers like {@link RemoveMessage} 7 | * instances. 8 | */ 9 | export function messagesStateReducer(left, right) { 10 | const leftArray = Array.isArray(left) ? left : [left]; 11 | const rightArray = Array.isArray(right) ? right : [right]; 12 | // coerce to message 13 | const leftMessages = leftArray.map(coerceMessageLikeToMessage); 14 | const rightMessages = rightArray.map(coerceMessageLikeToMessage); 15 | // assign missing ids 16 | for (const m of leftMessages) { 17 | if (m.id === null || m.id === undefined) { 18 | m.id = v4(); 19 | m.lc_kwargs.id = m.id; 20 | } 21 | } 22 | for (const m of rightMessages) { 23 | if (m.id === null || m.id === undefined) { 24 | m.id = v4(); 25 | m.lc_kwargs.id = m.id; 26 | } 27 | } 28 | // merge 29 | const merged = [...leftMessages]; 30 | const mergedById = new Map(merged.map((m, i) => [m.id, i])); 31 | const idsToRemove = new Set(); 32 | for (const m of rightMessages) { 33 | const existingIdx = mergedById.get(m.id); 34 | if (existingIdx !== undefined) { 35 | if (m._getType() === 'remove') { 36 | idsToRemove.add(m.id); 37 | } else { 38 | idsToRemove.delete(m.id); 39 | merged[existingIdx] = m; 40 | } 41 | } else { 42 | if (m._getType() === 'remove') { 43 | throw new Error( 44 | `Attempting to delete a message with an ID that doesn't exist ('${m.id}')`, 45 | ); 46 | } 47 | mergedById.set(m.id, merged.length); 48 | merged.push(m); 49 | } 50 | } 51 | return merged.filter((m) => !idsToRemove.has(m.id)); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/agents/summary/SummaryAgent.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/src/main/agents/summary/SummaryAgent.ts -------------------------------------------------------------------------------- /src/main/agents/tool_callling/ToolCallingAgent.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChatPromptTemplate, 3 | MessagesPlaceholder, 4 | PromptTemplate, 5 | SystemMessagePromptTemplate, 6 | HumanMessagePromptTemplate, 7 | } from '@langchain/core/prompts'; 8 | import { 9 | AgentExecutor, 10 | createStructuredChatAgent, 11 | createToolCallingAgent, 12 | } from 'langchain/agents'; 13 | import { pull } from 'langchain/hub'; 14 | import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages'; 15 | import { ChatMessageHistory } from 'langchain/stores/message/in_memory'; 16 | import { RunnableWithMessageHistory } from '@langchain/core/runnables'; 17 | import { getChatModel } from '../../llm'; 18 | import { ChatOptions } from '../../../entity/Chat'; 19 | import {} from '@langchain/core/language_models/base'; 20 | import { toolsManager } from '../../tools'; 21 | import { ChatResponse } from '@/main/chat/ChatResponse'; 22 | 23 | export const chatWithToolCallingAgent = async ( 24 | query: { content: any[] }, 25 | files: string[], 26 | connectionName: string, 27 | modelName: string, 28 | options: ChatOptions, 29 | history: BaseMessage[], 30 | ): Promise => { 31 | const prompt = ChatPromptTemplate.fromMessages([ 32 | ['system', 'You are a helpful assistant'], 33 | ['placeholder', '{chat_history}'], 34 | ['human', '{input}'], 35 | ['placeholder', '{agent_scratchpad}'], 36 | ]); 37 | const tools = toolsManager.tools 38 | .filter((x) => (options?.toolNames ?? []).includes(x.name)) 39 | .map((x) => x.tool); 40 | 41 | const llm = await getChatModel(connectionName, modelName, options); 42 | const agent = await createToolCallingAgent({ 43 | llm, 44 | tools, 45 | prompt, 46 | }); 47 | const agentExecutor = new AgentExecutor({ 48 | agent, 49 | tools, 50 | }); 51 | const actions = []; 52 | const result = await agentExecutor.invoke( 53 | { 54 | input: query, 55 | chat_history: history, 56 | }, 57 | { 58 | callbacks: [ 59 | { 60 | handleAgentAction(action, runId) { 61 | const regex = /Thought\s*\d*\s*:[\s]*(.*)[\s]/; 62 | const actionMatch = action.log.match(regex); 63 | let thought = null; 64 | if (actionMatch) { 65 | thought = actionMatch[0] 66 | .substring(actionMatch.indexOf(':') + 1) 67 | .trim(); 68 | } 69 | const { tool, toolInput } = action; 70 | 71 | //console.log(messageLog); 72 | actions.push({ 73 | runId, 74 | thought, 75 | tool, 76 | toolInput, 77 | toolOutput: '', 78 | }); 79 | 80 | console.log('\nhandleAgentAction', action); 81 | }, 82 | handleAgentEnd(action, runId) { 83 | console.log('\nhandleAgentEnd', action); 84 | }, 85 | handleToolEnd(output, runId) { 86 | const action = actions.find((x) => x.runId); 87 | action.toolOutput = output; 88 | console.log('\nhandleToolEnd', output); 89 | }, 90 | }, 91 | ], 92 | }, 93 | ); 94 | 95 | let output = ''; 96 | if (typeof result.output === 'string') { 97 | output = result.output; 98 | } else { 99 | output = JSON.stringify(result.output); 100 | } 101 | 102 | return { 103 | actions, 104 | output, 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /src/main/agents/utils.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | -------------------------------------------------------------------------------- /src/main/app/NotificationManager.tsx: -------------------------------------------------------------------------------- 1 | import { NotificationMessage } from '@/types/notification'; 2 | 3 | import { app, BrowserWindow, ipcMain } from 'electron'; 4 | 5 | export class NotificationManager { 6 | mainWindow: BrowserWindow; 7 | 8 | constructor() {} 9 | 10 | public getMainWindow() { 11 | const windows = BrowserWindow.getAllWindows(); 12 | 13 | const mainWindow = windows.length > 0 ? windows[0] : null; 14 | return mainWindow; 15 | } 16 | 17 | public sendNotification( 18 | title: string, 19 | icon: 'success' | 'error' | 'info' | 'warning' | 'loading', 20 | duration?: number | undefined, 21 | ) { 22 | this.getMainWindow().webContents.send('app:notification', { 23 | action: 'create', 24 | data: { 25 | title, 26 | type: 'notification', 27 | icon: icon, 28 | duration, 29 | }, 30 | }); 31 | } 32 | 33 | public create(data: NotificationMessage) { 34 | this.getMainWindow().webContents.send('app:notification', { 35 | action: 'create', 36 | data, 37 | }); 38 | } 39 | 40 | public update(data: NotificationMessage) { 41 | this.getMainWindow().webContents.send('app:notification', { 42 | action: 'update', 43 | data, 44 | }); 45 | } 46 | 47 | public delete(data: NotificationMessage) { 48 | this.getMainWindow().webContents.send('app:notification', { 49 | action: 'delete', 50 | data: data, 51 | }); 52 | } 53 | 54 | public progress( 55 | id: string, 56 | title: string, 57 | percent: number, 58 | description?: string, 59 | closeEnable?: boolean, 60 | duration?: number, 61 | error?: string, 62 | ) { 63 | this.getMainWindow().webContents.send('app:notification', { 64 | action: 'update', 65 | data: { 66 | id, 67 | title, 68 | type: 'progress', 69 | percent, 70 | description, 71 | closeEnable: closeEnable, 72 | duration: duration, 73 | error: error, 74 | }, 75 | }); 76 | } 77 | } 78 | 79 | export const notificationManager = new NotificationManager(); 80 | -------------------------------------------------------------------------------- /src/main/chat/ChatResponse.ts: -------------------------------------------------------------------------------- 1 | import { DocumentInterface } from '@langchain/core/documents'; 2 | 3 | export interface ChatResponse { 4 | output: string; 5 | actions?: any[] | undefined; 6 | documents?: DocumentInterface[] | undefined; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/db/vectorstores/index.ts: -------------------------------------------------------------------------------- 1 | import { DocumentInterface } from '@langchain/core/documents'; 2 | import { 3 | VectorStore, 4 | VectorStoreInterface, 5 | } from '@langchain/core/vectorstores'; 6 | import { 7 | Embeddings, 8 | type EmbeddingsParams, 9 | EmbeddingsInterface, 10 | } from '@langchain/core/embeddings'; 11 | 12 | export interface BaseVectorStoreInterface extends VectorStoreInterface { 13 | get(id: string): Promise; 14 | 15 | initialize(embeddings: Embeddings, collectionName: string); 16 | 17 | getCollections(): Promise; 18 | 19 | createCollection(collectionName: string, extendColumns: []); 20 | 21 | deleteCollection(collectionName: string); 22 | } 23 | 24 | export class BaseVectorStore 25 | extends VectorStore 26 | implements BaseVectorStoreInterface 27 | { 28 | constructor(embeddings: EmbeddingsInterface, dbConfig: Record) { 29 | super(embeddings, dbConfig); 30 | } 31 | 32 | _vectorstoreType(): string { 33 | throw new Error('Method not implemented.'); 34 | } 35 | 36 | addVectors( 37 | vectors: number[][], 38 | documents: DocumentInterface[], 39 | options?: { [x: string]: any }, 40 | ): Promise { 41 | throw new Error('Method not implemented.'); 42 | } 43 | 44 | addDocuments( 45 | documents: DocumentInterface[], 46 | options?: { [x: string]: any }, 47 | ): Promise { 48 | throw new Error('Method not implemented.'); 49 | } 50 | 51 | similaritySearchVectorWithScore( 52 | query: number[], 53 | k: number, 54 | filter?: this['FilterType'], 55 | ): Promise<[DocumentInterface, number][]> { 56 | throw new Error('Method not implemented.'); 57 | } 58 | 59 | get(id: string): Promise { 60 | throw new Error('Method not implemented.'); 61 | } 62 | 63 | initialize( 64 | embeddings: Embeddings, 65 | collectionName: string, 66 | ): Promise { 67 | throw new Error('Method not implemented.'); 68 | } 69 | 70 | getCollections(): Promise { 71 | throw new Error('Method not implemented.'); 72 | } 73 | 74 | //getCollections(): string[]; 75 | createCollection(collectionName: string, extendColumns: {}) { 76 | throw new Error('Method not implemented.'); 77 | } 78 | 79 | deleteCollection(collectionName: string) { 80 | throw new Error('Method not implemented.'); 81 | } 82 | 83 | update(data: Record, where: Record): Promise { 84 | throw new Error('Method not implemented.'); 85 | } 86 | 87 | insert( 88 | data: Record, 89 | content: string, 90 | metadata: Record, 91 | vector: Array, 92 | source: string, 93 | ): Promise { 94 | throw new Error('Method not implemented.'); 95 | } 96 | 97 | delete(input?: Record | string): Promise { 98 | throw new Error('Method not implemented.'); 99 | } 100 | 101 | filter( 102 | input?: Record | string, 103 | limit: number | null = null, 104 | ): Promise { 105 | throw new Error('Method not implemented.'); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/embeddings/index.ts: -------------------------------------------------------------------------------- 1 | // import AppDataSource from '../../data-source'; 2 | import { OllamaEmbeddings } from '@langchain/ollama'; 3 | import { OpenAIEmbeddings } from '@langchain/openai'; 4 | import { OpenAI } from 'openai'; 5 | import { AlibabaTongyiEmbeddings } from '@langchain/community/embeddings/alibaba_tongyi'; 6 | import { ZhipuAIEmbeddings } from '@langchain/community/embeddings/zhipuai'; 7 | import { TogetherAIEmbeddings } from '@langchain/community/embeddings/togetherai'; 8 | 9 | import { ChatAlibabaTongyi } from '@langchain/community/chat_models/alibaba_tongyi'; 10 | import { ChatZhipuAI } from '@langchain/community/chat_models/zhipuai'; 11 | 12 | import { ChatAnthropic } from '@langchain/anthropic'; 13 | 14 | import { HttpsProxyAgent } from 'https-proxy-agent'; 15 | 16 | import { 17 | BaseChatModel, 18 | BaseChatModelParams, 19 | } from '@langchain/core/language_models/chat_models'; 20 | import { Tool, ToolParams } from '@langchain/core/tools'; 21 | 22 | import { zodToJsonSchema } from 'zod-to-json-schema'; 23 | import { RunnableLambda } from '@langchain/core/runnables'; 24 | import { ChatGroqInput, ChatGroq as LangchainChatGroq } from '@langchain/groq'; 25 | import settingsManager from '../settings'; 26 | 27 | import providersManager from '../providers'; 28 | import { ProviderType, Providers } from '../../entity/Providers'; 29 | import { ChatOptions } from '../../entity/Chat'; 30 | import { Embeddings, EmbeddingsParams } from '@langchain/core/embeddings'; 31 | import { HuggingFaceTransformersEmbeddings } from './HuggingFaceTransformersEmbeddings'; 32 | 33 | export async function getEmbeddingModel( 34 | providerName: string, 35 | model?: string, 36 | ): Promise { 37 | const provider = await ( 38 | await providersManager.getProviders() 39 | ).find((x) => x.name === providerName); 40 | 41 | if (!model || providerName == 'local') { 42 | const emb = new HuggingFaceTransformersEmbeddings({ 43 | modelName: model, 44 | }); 45 | return emb; 46 | } 47 | 48 | if (provider?.type === ProviderType.OLLAMA) { 49 | const emb = new OllamaEmbeddings({ 50 | baseUrl: provider.api_base, // Default value 51 | model: model, 52 | }); 53 | return emb; 54 | } else if ( 55 | provider?.type === ProviderType.OPENAI || 56 | provider?.type === ProviderType.SILICONFLOW 57 | ) { 58 | const emb = new OpenAIEmbeddings({ 59 | model: model, 60 | apiKey: provider.api_key, 61 | configuration: { 62 | baseURL: provider.api_base, 63 | httpAgent: settingsManager.getHttpAgent(), 64 | }, 65 | }); 66 | return emb; 67 | } else if (provider?.type === ProviderType.TONGYI) { 68 | const emb = new AlibabaTongyiEmbeddings({ apiKey: provider.api_key }); 69 | return emb; 70 | } else if (provider?.type === ProviderType.ZHIPU) { 71 | const emb = new ZhipuAIEmbeddings({ apiKey: provider.api_key }); 72 | return emb; 73 | } else if (provider?.type === ProviderType.TOGETHERAI) { 74 | const emb = new TogetherAIEmbeddings({ apiKey: provider.api_key }); 75 | return emb; 76 | } 77 | throw new Error(); 78 | } 79 | 80 | export async function getDefaultEmbeddingModel(): Promise { 81 | const defaultEmbedding = settingsManager.getSettings()?.defaultEmbedding; 82 | 83 | if (defaultEmbedding) { 84 | const connectionName = defaultEmbedding.split('@')[1]; 85 | const modelName = defaultEmbedding.split('@')[0]; 86 | return await getEmbeddingModel(connectionName, modelName); 87 | } else { 88 | return null; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/explores/index.ts: -------------------------------------------------------------------------------- 1 | import { ipcMain } from 'electron'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import { pathToFileURL } from 'url'; 5 | 6 | interface Explore { 7 | name: string; 8 | icon: string; 9 | filePath: string; 10 | } 11 | 12 | export class ExploresManager { 13 | public explores: { name: string; explore: Explore; pluginCode: string }[]; 14 | 15 | constructor() { 16 | this.explores = []; 17 | if (!ipcMain) return; 18 | 19 | ipcMain.on('explores:getList', async (event) => { 20 | event.returnValue = this.explores; 21 | }); 22 | } 23 | 24 | public async unload(directory: string) { 25 | try { 26 | const main_url = pathToFileURL(path.join(directory, 'main.cjs')).href; 27 | const main_plugin = await import(/* webpackIgnore: true */ main_url); 28 | const explores = main_plugin.default.explores; 29 | explores.forEach((explore) => { 30 | this.explores = this.explores.filter( 31 | (item) => item.explore.name !== explore.name, 32 | ); 33 | }); 34 | } catch (e) { 35 | console.log(e); 36 | } 37 | } 38 | 39 | public async load(directory: string) { 40 | //const files = fs.readdirSync(path.join(directory, 'renderer', 'explores')); 41 | try { 42 | const renderer_url = pathToFileURL( 43 | path.join(directory, 'renderer.js'), 44 | ).href; 45 | // const renderer_plugin = await import( 46 | // /* webpackIgnore: true */ renderer_url 47 | // ); 48 | const main_url = pathToFileURL(path.join(directory, 'main.cjs')).href; 49 | const main_plugin = await import(/* webpackIgnore: true */ main_url); 50 | const explores = main_plugin.default.explores; 51 | // plugin.default.explores.forEach(async (t) => { 52 | // await exploresManager.load(t); 53 | // }); 54 | const pluginCode = await fs.promises.readFile( 55 | path.join(directory, 'renderer.js'), 56 | 'utf-8', 57 | ); 58 | 59 | // const explores = main_plugin.default.explores; 60 | explores.forEach((explore) => { 61 | this.explores.push({ 62 | name: explore.name, 63 | explore: { name: explore.name, icon: '', filePath: directory }, 64 | pluginCode: pluginCode, 65 | }); 66 | }); 67 | } catch (e) { 68 | console.log(e); 69 | } 70 | } 71 | } 72 | 73 | const exploresManager = new ExploresManager(); 74 | export default exploresManager; 75 | -------------------------------------------------------------------------------- /src/main/ipc/ipcListener.ts: -------------------------------------------------------------------------------- 1 | import { 2 | app, 3 | BrowserWindow, 4 | shell, 5 | ipcMain, 6 | dialog, 7 | Tray, 8 | Menu, 9 | clipboard, 10 | OpenDialogOptions, 11 | SaveDialogOptions, 12 | MessageBoxOptions, 13 | nativeTheme, 14 | } from 'electron'; 15 | // import Store from 'electron-store'; 16 | import fs from 'fs'; 17 | import path from 'path'; 18 | import settingsManager from '../settings'; 19 | import { platform } from 'process'; 20 | 21 | export default function ipcListener(mainWindow: BrowserWindow) {} 22 | -------------------------------------------------------------------------------- /src/main/knowledgebase/RerankDocumentCompressor.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { BaseDocumentCompressor } from 'langchain/retrievers/document_compressors'; 3 | import type { DocumentInterface } from '@langchain/core/documents'; 4 | import { Callbacks } from '@langchain/core/callbacks/manager'; 5 | import { Transformers } from '../utils/transformers'; 6 | 7 | export default class RerankDocumentCompressor extends BaseDocumentCompressor { 8 | constructor() { 9 | super(); 10 | Object.defineProperty(this, 'llmChain', { 11 | enumerable: true, 12 | configurable: true, 13 | writable: true, 14 | value: void 0, 15 | }); 16 | Object.defineProperty(this, 'getInput', { 17 | enumerable: true, 18 | configurable: true, 19 | writable: true, 20 | value: defaultGetInput, 21 | }); 22 | this.llmChain = llmChain; 23 | this.getInput = getInput; 24 | } 25 | 26 | /** 27 | * Compresses a list of documents based on the output of an LLM chain. 28 | * @param documents The list of documents to be compressed. 29 | * @param query The query to be used for document compression. 30 | * @returns A list of compressed documents. 31 | */ 32 | async compressDocuments( 33 | documents: DocumentInterface[], 34 | query: string, 35 | callbacks?: Callbacks, 36 | ): Promise { 37 | const res = await new Transformers({ 38 | modelName: 'bge-reranker-large', 39 | }).rank( 40 | query, 41 | documents.map((x) => x.pageContent), 42 | { return_documents: true }, 43 | ); 44 | return res; 45 | // const compressedDocs = await Promise.all( 46 | // documents.map(async (doc) => { 47 | // const input = this.getInput(query, doc); 48 | // const output = await this.llmChain.predict(input); 49 | // return output.length > 0 50 | // ? new Document({ 51 | // pageContent: output, 52 | // metadata: doc.metadata, 53 | // }) 54 | // : undefined; 55 | // }), 56 | // ); 57 | // return compressedDocs.filter((doc) => doc !== undefined); 58 | } 59 | 60 | /** 61 | * Creates a new instance of LLMChainExtractor from a given LLM, prompt 62 | * template, and getInput function. 63 | * @param llm The BaseLanguageModel instance used for document extraction. 64 | * @param prompt The PromptTemplate instance used for document extraction. 65 | * @param getInput A function used for constructing the chain input from the query and a Document. 66 | * @returns A new instance of LLMChainExtractor. 67 | */ 68 | static fromLLM(llm, prompt, getInput) { 69 | const _prompt = prompt || getDefaultChainPrompt(); 70 | const _getInput = getInput || defaultGetInput; 71 | const llmChain = new LLMChain({ llm, prompt: _prompt }); 72 | return new LLMChainExtractor({ llmChain, getInput: _getInput }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/loaders/ExcelLoader.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@langchain/core/documents'; 2 | import { BaseDocumentLoader } from '@langchain/core/document_loaders/base'; 3 | import { getEnv } from '@langchain/core/utils/env'; 4 | import * as xlsx from 'xlsx'; 5 | import * as fs from 'fs'; 6 | 7 | export class ExcelLoader extends BaseDocumentLoader { 8 | filePathOrBlob: string | Blob; 9 | 10 | constructor(filePathOrBlob: string | Blob) { 11 | super(); 12 | Object.defineProperty(this, 'filePathOrBlob', { 13 | enumerable: true, 14 | configurable: true, 15 | writable: true, 16 | value: filePathOrBlob, 17 | }); 18 | } 19 | 20 | async parse(raw: string): Promise { 21 | return [raw]; 22 | } 23 | 24 | async load(): Promise { 25 | // let text; 26 | let metadata; 27 | let wb: xlsx.WorkBook; 28 | xlsx.set_fs(fs); 29 | if (this.filePathOrBlob instanceof Blob) { 30 | const arrayBuffer = await this.filePathOrBlob.arrayBuffer(); 31 | const data = new Uint8Array(arrayBuffer); 32 | wb = xlsx.read(data, { type: 'array' }); 33 | } else { 34 | wb = xlsx.readFile(this.filePathOrBlob); 35 | metadata = { source: this.filePathOrBlob }; 36 | } 37 | const docs: Document[] = []; 38 | for (const sheetName of wb.SheetNames) { 39 | const worksheet = wb.Sheets[sheetName]; 40 | debugger; 41 | const data = xlsx.utils.sheet_to_txt(worksheet); 42 | // const pageContent = data.map((x) => JSON.stringify(x)).join('\n\n'); 43 | docs.push(new Document({ id: sheetName, pageContent: data, metadata })); 44 | } 45 | return docs; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/loaders/ImageLoader.ts: -------------------------------------------------------------------------------- 1 | import type { RapidOcrTool as RapidOcrToolT } from '../tools/RapidOcr'; 2 | import { Document } from '@langchain/core/documents'; 3 | import { BaseDocumentLoader } from '@langchain/core/document_loaders/base'; 4 | import { getEnv } from '@langchain/core/utils/env'; 5 | 6 | export class ImageLoader extends BaseDocumentLoader { 7 | filePathOrBlob: string | Blob; 8 | 9 | constructor(filePathOrBlob: string | Blob) { 10 | super(); 11 | Object.defineProperty(this, 'filePathOrBlob', { 12 | enumerable: true, 13 | configurable: true, 14 | writable: true, 15 | value: filePathOrBlob, 16 | }); 17 | } 18 | 19 | async parse(raw: string): Promise { 20 | return [raw]; 21 | } 22 | 23 | async load(): Promise { 24 | // let text; 25 | // let metadata; 26 | 27 | const { RapidOcrTool } = await ImageLoader.imports(); 28 | const ocrTool = new RapidOcrTool(); 29 | const text = await ocrTool.invoke(this.filePathOrBlob); 30 | const metadata = { source: this.filePathOrBlob }; 31 | 32 | const pageContent = text; 33 | return [new Document({ pageContent, metadata })]; 34 | } 35 | 36 | static async imports(): Promise<{ RapidOcrTool: typeof RapidOcrToolT }> { 37 | try { 38 | const { RapidOcrTool } = await import('../tools/RapidOcr'); 39 | return { RapidOcrTool: RapidOcrTool }; 40 | } catch (e) { 41 | console.error(e); 42 | throw new Error( 43 | `Failed to load fs/promises. TextLoader available only on environment 'node'. It appears you are running environment '${getEnv()}'. See https:// for alternatives.`, 44 | ); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/loaders/index.ts: -------------------------------------------------------------------------------- 1 | import { DocxLoader } from '@langchain/community/document_loaders/fs/docx'; 2 | import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'; 3 | import { TextLoader } from 'langchain/document_loaders/fs/text'; 4 | import { ImageLoader } from './ImageLoader'; 5 | import { PPTXLoader } from '@langchain/community/document_loaders/fs/pptx'; 6 | import { JSONLoader } from 'langchain/document_loaders/fs/json'; 7 | import { EPubLoader } from '@langchain/community/document_loaders/fs/epub'; 8 | 9 | export const getLoaderFromExt = (ext: string, value: string) => { 10 | switch (ext) { 11 | case '.txt': 12 | return new TextLoader(value); 13 | case '.pdf': 14 | return new PDFLoader(value); 15 | case '.docx': 16 | return new DocxLoader(value, { type: 'docx' }); 17 | case '.doc': 18 | return new DocxLoader(value, { type: 'doc' }); 19 | case '.png': 20 | case '.jpg': 21 | case '.jpeg': 22 | return new ImageLoader(value); 23 | case '.pptx': 24 | return new PPTXLoader(value); 25 | case '.json': 26 | return new JSONLoader(value); 27 | case '.epub': 28 | return new EPubLoader(value); 29 | default: 30 | return null; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/main/providers/BaseProvider.ts: -------------------------------------------------------------------------------- 1 | import { Providers } from '@/entity/Providers'; 2 | 3 | export abstract class BaseProvider { 4 | abstract name: string; 5 | 6 | abstract description: string; 7 | 8 | abstract defaultApiBase?: string; 9 | 10 | abstract getModelList( 11 | provider: Providers, 12 | ): Promise<{ name: string; enable: boolean }[]>; 13 | 14 | getEmbeddingModels(provider: Providers) { 15 | return []; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/providers/MinimaxProvider.ts: -------------------------------------------------------------------------------- 1 | import { Providers, ProviderType } from '@/entity/Providers'; 2 | import { BaseProvider } from './BaseProvider'; 3 | import { ChatMinimax } from '@langchain/community/chat_models/minimax'; 4 | import { OpenAI } from 'openai'; 5 | import { BaseChatModel } from '@langchain/core/language_models/chat_models'; 6 | import { ChatOptions } from '@/entity/Chat'; 7 | import { getEnvironmentVariable } from '@langchain/core/utils/env'; 8 | import { ChatOpenAI } from '@langchain/openai'; 9 | 10 | export class MinimaxProvider extends BaseProvider { 11 | name: string = ProviderType.MINIMAX; 12 | 13 | description: string; 14 | 15 | defaultApiBase: string = 'https://api.minimax.chat/v1'; 16 | 17 | getChatModel( 18 | provider: Providers, 19 | modelName: string, 20 | options: ChatOptions, 21 | ): BaseChatModel { 22 | const apiKey = 23 | provider.api_key || getEnvironmentVariable('MINIMAX_API_KEY'); 24 | const groupId = 25 | provider.api_key || getEnvironmentVariable('MINIMAX_GROUP_ID'); 26 | return new ChatOpenAI({ 27 | apiKey: apiKey, 28 | modelName: modelName, 29 | configuration: { 30 | apiKey: apiKey, 31 | baseURL: provider.api_base || this.defaultApiBase, 32 | }, 33 | topP: options?.top_p, 34 | maxTokens: options?.maxTokens, 35 | temperature: options?.temperature, 36 | streaming: options?.streaming, 37 | }); 38 | // return new ChatMinimax({ 39 | // model: modelName, 40 | // apiKey: apiKey, 41 | // streaming: options?.streaming, 42 | // temperature: options?.temperature, 43 | // topP: options?.top_p, 44 | // minimaxApiKey: apiKey, 45 | // }); 46 | } 47 | 48 | async getModelList( 49 | provider: Providers, 50 | ): Promise<{ name: string; enable: boolean }[]> { 51 | const openai = new OpenAI({ 52 | baseURL: provider.api_base || this.defaultApiBase, 53 | apiKey: provider.api_key, 54 | }); 55 | const models = ['MiniMax-Text-01', 'abab6.5s-chat', 'DeepSeek-R1']; 56 | 57 | return models 58 | .map((x) => { 59 | return { 60 | name: x, 61 | enable: provider.models.find((z) => z.name == x)?.enable || false, 62 | }; 63 | }) 64 | .sort((a, b) => a.name.localeCompare(b.name)); 65 | } 66 | 67 | getEmbeddingModels(provider: Providers) { 68 | return []; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/providers/OllamaProvider.ts: -------------------------------------------------------------------------------- 1 | import { Providers, ProviderType } from '@/entity/Providers'; 2 | import { BaseProvider } from './BaseProvider'; 3 | import { Ollama } from 'ollama'; 4 | 5 | export class OllamaProvider extends BaseProvider { 6 | name: string = ProviderType.OLLAMA; 7 | 8 | description: string; 9 | 10 | defaultApiBase: string = 'http://localhost:11434'; 11 | 12 | async getModelList( 13 | provider: Providers, 14 | ): Promise<{ name: string; enable: boolean }[]> { 15 | const ollama = new Ollama({ 16 | host: provider.api_base || this.defaultApiBase, 17 | }); 18 | const list = await ollama.list(); 19 | return list.models 20 | .map((x) => { 21 | return { 22 | name: x.name, 23 | enable: 24 | provider.models.find((z) => z.name == x.name)?.enable || false, 25 | }; 26 | }) 27 | .sort((a, b) => a.name.localeCompare(b.name)); 28 | } 29 | 30 | getEmbeddingModels(provider: Providers) { 31 | return []; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/providers/VolcanoEngineProvider.ts: -------------------------------------------------------------------------------- 1 | import { Providers, ProviderType } from '@/entity/Providers'; 2 | import { BaseProvider } from './BaseProvider'; 3 | import settingsManager from '../settings'; 4 | 5 | export class VolcanoEngineProvider extends BaseProvider { 6 | name: string = ProviderType.VOLCANOENGINE; 7 | 8 | description: string; 9 | 10 | defaultApiBase: string = 'https://ark.cn-beijing.volces.com/api/v3'; 11 | 12 | async getModelList( 13 | provider: Providers, 14 | ): Promise<{ name: string; enable: boolean }[]> { 15 | const httpProxy = settingsManager.getHttpAgent(); 16 | const options = { 17 | method: 'GET', 18 | agent: httpProxy, 19 | Authorization: `Bearer ${provider.api_key}`, 20 | }; 21 | const url = `${provider.api_base || this.defaultApiBase}/ListFoundationModels`; 22 | const res = await fetch(url, options); 23 | const data = await res.json(); 24 | return data.models 25 | .map((x) => { 26 | return { 27 | name: x.name.split('/')[1], 28 | enable: 29 | provider.models?.find((z) => z.name == x.name.split('/')[1]) 30 | ?.enable || false, 31 | }; 32 | }) 33 | .sort((a, b) => a.name.localeCompare(b.name)); 34 | } 35 | 36 | getEmbeddingModels(provider: Providers) { 37 | return []; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/supabase/supabaseClient.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js'; 2 | 3 | const supabaseUrl = 'https://gcjnqikuszmfqnvjxuzv.supabase.co'; 4 | const supabaseAnonKey = 5 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imdjam5xaWt1c3ptZnFudmp4dXp2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDcyODAzNTIsImV4cCI6MjA2Mjg1NjM1Mn0.v3kdkeDvf-YAbN5dvuZEMsruumbSkOkFdGi56ctNYQw'; 6 | export const supabase = createClient(supabaseUrl, supabaseAnonKey); 7 | -------------------------------------------------------------------------------- /src/main/tools/AskHuman.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager'; 3 | import { RunnableConfig } from '@langchain/core/runnables'; 4 | import { Tool, StructuredTool } from '@langchain/core/tools'; 5 | 6 | import { z } from 'zod'; 7 | import { isUrl } from '../utils/is'; 8 | import fs from 'fs'; 9 | 10 | export class AskHuman extends StructuredTool { 11 | schema = z.object({ 12 | question: z.string().describe('Ask human questions'), 13 | displayFormat: z 14 | .enum(['string', 'radio', 'checkbox', 'file', 'date-range', 'date']) 15 | .describe('base64'), 16 | radioConfig: z 17 | .optional(z.array(z.string())) 18 | .describe('if displayFormat is radio this is required'), 19 | checkboxConfig: z 20 | .optional(z.array(z.string())) 21 | .describe('if displayFormat is checkbox this is required'), 22 | fileConfig: z 23 | .optional(z.object({ multiple: z.boolean() })) 24 | .describe('if displayFormat is file this is required'), 25 | }); 26 | 27 | name = 'ask-human'; 28 | 29 | description = 'Ask human question and show ui Format to human action'; 30 | 31 | async _call(input: z.infer): Promise { 32 | return JSON.stringify(input); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/tools/BaseTool.ts: -------------------------------------------------------------------------------- 1 | import { BaseToolkit, StructuredTool } from '@langchain/core/tools'; 2 | import { FormSchema } from '@/types/form'; 3 | 4 | export const ToolTag = { 5 | IMAGE: 'image', 6 | } as const; 7 | 8 | export abstract class BaseTool extends StructuredTool { 9 | static lc_name() { 10 | return this.name; 11 | } 12 | 13 | officialLink?: string; 14 | 15 | toolKitName?: string; 16 | 17 | output?: string; 18 | 19 | outputFormat?: 'json' | 'markdown' = 'markdown'; 20 | 21 | configSchema?: FormSchema[] = []; 22 | 23 | displayMode?: 'list' | 'markdown' = 'markdown'; 24 | 25 | tags?: string[] = []; 26 | } 27 | 28 | export abstract class BaseToolKit extends BaseToolkit { 29 | name: string; 30 | 31 | description?: string; 32 | 33 | configSchema?: FormSchema[] = []; 34 | 35 | tools: BaseTool[] = []; 36 | 37 | getTools(): BaseTool[] { 38 | return this.tools; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/tools/ComfyuiTool.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import { 3 | exec, 4 | execSync, 5 | execFileSync, 6 | ExecSyncOptionsWithStringEncoding, 7 | } from 'child_process'; 8 | import { is, isArray, isString } from '../utils/is'; 9 | import { z } from 'zod'; 10 | import iconv from 'iconv-lite'; 11 | import path from 'path'; 12 | import { app } from 'electron'; 13 | import { PythonShell, Options } from 'python-shell'; 14 | import fs from 'fs'; 15 | import { BaseTool } from './BaseTool'; 16 | import { FormSchema } from '@/types/form'; 17 | 18 | export interface ComfyuiToolParameters extends ToolParams { 19 | defaultApiBase?: string; 20 | } 21 | 22 | export class ComfyuiTool extends BaseTool { 23 | schema = z.object({ 24 | fileOrCode: z.string().describe('The file or code to run'), 25 | }); 26 | 27 | configSchema: FormSchema[] = [ 28 | { 29 | label: 'defaultApiBase', 30 | field: 'defaultApiBase', 31 | component: 'Input', 32 | defaultValue: 'http://127.0.0.1:8188', 33 | }, 34 | ]; 35 | 36 | name: string = 'comfyui_tool'; 37 | 38 | description: string = `comfyui workflow.`; 39 | 40 | defaultApiBase: string = 'http://127.0.0.1:8188'; 41 | 42 | constructor(params?: ComfyuiToolParameters) { 43 | super(params); 44 | this.defaultApiBase = params?.defaultApiBase; 45 | } 46 | 47 | async _call( 48 | input: z.infer, 49 | runManager, 50 | config, 51 | ): Promise { 52 | if (!this.defaultApiBase) { 53 | return 'not found'; 54 | } 55 | 56 | return null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/tools/DateTimeTool.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import { 3 | exec, 4 | execSync, 5 | execFileSync, 6 | ExecSyncOptionsWithStringEncoding, 7 | } from 'child_process'; 8 | import { isArray, isString } from '../utils/is'; 9 | import { z } from 'zod'; 10 | import iconv from 'iconv-lite'; 11 | import dayjs from 'dayjs'; 12 | import { BaseTool } from './BaseTool'; 13 | 14 | export class DateTimeTool extends BaseTool { 15 | // static lc_name() { 16 | // return 'datetime_tool'; 17 | // } 18 | 19 | schema = z.object({}); 20 | 21 | name: string = 'datetime_tool'; 22 | 23 | description: string = 'Get Current Datetime or Currnet Week'; 24 | 25 | constructor() { 26 | super(); 27 | } 28 | 29 | async _call(input: any, runManager, config): Promise { 30 | return dayjs().format('YYYY-MM-DD HH:mm:ss dddd'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/tools/DuckDuckGoSearch.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DuckDuckGoSearch, 3 | DuckDuckGoSearchParameters, 4 | } from '@langchain/community/tools/duckduckgo_search'; 5 | import { SafeSearchType, search, SearchOptions } from 'duck-duck-scrape'; 6 | import settingsManager from '../settings'; 7 | import { HttpsProxyAgent } from 'https-proxy-agent'; 8 | 9 | export class DuckDuckGoSearchTool extends DuckDuckGoSearch { 10 | private _searchOptions?: SearchOptions; 11 | 12 | private _maxResults: number | undefined; 13 | 14 | constructor(params?: DuckDuckGoSearchParameters) { 15 | super(params); 16 | const { searchOptions, maxResults } = params ?? {}; 17 | this._searchOptions = searchOptions; 18 | if (!this._searchOptions) { 19 | this._searchOptions = { 20 | safeSearch: SafeSearchType.STRICT, 21 | locale: settingsManager.getLanguage()?.toLowerCase(), 22 | } as SearchOptions; 23 | } 24 | this._maxResults = maxResults || this._maxResults; 25 | } 26 | 27 | async _call(input: string): Promise { 28 | const httpProxy = settingsManager.getHttpAgent(); 29 | const { results } = await search(input, this._searchOptions, { 30 | agent: httpProxy ?? false, 31 | }); 32 | return JSON.stringify( 33 | results 34 | .map((result) => ({ 35 | title: result.title, 36 | link: result.url, 37 | snippet: result.description, 38 | })) 39 | .slice(0, this._maxResults), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/tools/ExcelTool.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/src/main/tools/ExcelTool.ts -------------------------------------------------------------------------------- /src/main/tools/FileToText.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import ffmpeg from 'fluent-ffmpeg'; 3 | 4 | import { 5 | exec, 6 | execSync, 7 | execFileSync, 8 | ExecSyncOptionsWithStringEncoding, 9 | } from 'child_process'; 10 | import { isArray, isString } from '../utils/is'; 11 | import { z } from 'zod'; 12 | import iconv from 'iconv-lite'; 13 | import path from 'path'; 14 | import { app } from 'electron'; 15 | import fs from 'fs'; 16 | import { TextLoader } from 'langchain/document_loaders/fs/text'; 17 | import { DocxLoader } from '@langchain/community/document_loaders/fs/docx'; 18 | import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'; 19 | import { PPTXLoader } from '@langchain/community/document_loaders/fs/pptx'; 20 | import { BaseTool } from './BaseTool'; 21 | import { ExcelLoader } from '../loaders/ExcelLoader'; 22 | 23 | export interface FileToTextParameters extends ToolParams {} 24 | 25 | export class FileToText extends BaseTool { 26 | name: string = 'file_to_text'; 27 | 28 | description: string = 29 | 'file convert to text support (pdf, docx, doc, pptx, txt, md)'; 30 | 31 | constructor(params?: FileToTextParameters) { 32 | super(params); 33 | } 34 | 35 | schema = z.object({ 36 | filePath: z.string().describe('file path'), 37 | }); 38 | 39 | async _call( 40 | input: z.infer, 41 | runManager, 42 | config, 43 | ): Promise { 44 | try { 45 | if (!isString(input.filePath)) { 46 | return 'input value is not filePath'; 47 | } 48 | if (!fs.existsSync(input.filePath)) { 49 | return 'file not found'; 50 | } 51 | const ext = path.extname(input.filePath).toLowerCase(); 52 | if (ext.toLowerCase() == '.pdf') { 53 | const loader = new PDFLoader(input.filePath); 54 | const docs = await loader.load(); 55 | return docs.map((x) => x.pageContent).join('\n\n'); 56 | } else if (ext.toLowerCase() == '.txt' || ext.toLowerCase() == '.md') { 57 | const loader = new TextLoader(input.filePath); 58 | const docs = await loader.load(); 59 | return docs.map((x) => x.pageContent).join('\n\n'); 60 | } else if (ext.toLowerCase() == '.docx') { 61 | const loader = new DocxLoader(input.filePath, { type: 'docx' }); 62 | const docs = await loader.load(); 63 | return docs.map((x) => x.pageContent).join('\n\n'); 64 | } else if (ext.toLowerCase() == '.doc') { 65 | const loader = new DocxLoader(input.filePath, { type: 'doc' }); 66 | const docs = await loader.load(); 67 | return docs.map((x) => x.pageContent).join('\n\n'); 68 | } else if (ext.toLowerCase() == '.pptx') { 69 | const loader = new PPTXLoader(input.filePath); 70 | const docs = await loader.load(); 71 | return docs.map((x) => x.pageContent).join('\n\n'); 72 | } else if (ext == '.xlsx' || ext == '.xls') { 73 | const loader = new ExcelLoader(input.filePath); 74 | const docs = await loader.load(); 75 | return docs.map((x) => x.pageContent).join('\n\n'); 76 | } else { 77 | throw new Error(`Unsupported file type: ${ext}`); 78 | } 79 | } catch (err) { 80 | if (err.message) { 81 | return err.message; 82 | } 83 | return JSON.stringify(err); 84 | } 85 | return null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/tools/FireCrawl.ts: -------------------------------------------------------------------------------- 1 | import { FireCrawlLoader } from '@langchain/community/document_loaders/web/firecrawl'; 2 | import { BaseTool } from './BaseTool'; 3 | import { ToolParams } from '@langchain/core/tools'; 4 | import { z } from 'zod'; 5 | import { getEnvironmentVariable } from '@langchain/core/utils/env'; 6 | import { FormSchema } from '@/types/form'; 7 | import FirecrawlApp, { 8 | CrawlResponse, 9 | ScrapeResponse, 10 | } from '@mendable/firecrawl-js'; 11 | 12 | export interface FireCrawlParameters extends ToolParams { 13 | apiKey: string; 14 | } 15 | 16 | export class FireCrawl extends BaseTool { 17 | name: string = 'firecrawl'; 18 | 19 | description: string = 20 | 'Firecrawl is an API service that takes a URL, crawls it, and converts it into clean markdown'; 21 | 22 | apiKey: string; 23 | 24 | configSchema: FormSchema[] = [ 25 | { 26 | label: 'Api Key', 27 | field: 'apiKey', 28 | component: 'InputPassword', 29 | required: true, 30 | }, 31 | ]; 32 | 33 | constructor(fields?: FireCrawlParameters) { 34 | super(fields); 35 | const { apiKey } = fields ?? {}; 36 | this.apiKey = apiKey ?? getEnvironmentVariable('FIRECRAWL_API_KEY'); 37 | } 38 | 39 | schema = z.object({ 40 | url: z.string(), 41 | mode: z 42 | .enum(['scrape', 'crawl']) 43 | .default('scrape') 44 | .describe( 45 | 'The mode to run the crawler in. Can be "scrape" for single urls or "crawl" for all accessible subpages', 46 | ) 47 | .optional(), 48 | }); 49 | 50 | async _call( 51 | input: z.infer, 52 | runManager, 53 | config, 54 | ): Promise { 55 | const app = new FirecrawlApp({ apiKey: this.apiKey }); 56 | 57 | if (input.mode == 'scrape') { 58 | const scrapeResult = (await app.scrapeUrl(input.url, { 59 | formats: ['markdown'], 60 | })) as ScrapeResponse; 61 | 62 | if (!scrapeResult.success) { 63 | throw new Error(`Failed to scrape: ${scrapeResult.error}`); 64 | } 65 | return scrapeResult.markdown; 66 | } else if (input.mode == 'crawl') { 67 | const crawlResult = (await app.crawlUrl(input.url, { 68 | limit: 100, 69 | scrapeOptions: { 70 | formats: ['markdown'], 71 | }, 72 | })) as CrawlResponse; 73 | return crawlResult.url; 74 | } else { 75 | throw new Error('Invalid mode'); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/tools/ImageToSvg.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import { 3 | exec, 4 | execSync, 5 | execFileSync, 6 | ExecSyncOptionsWithStringEncoding, 7 | } from 'child_process'; 8 | import { isArray, isString } from '../utils/is'; 9 | import { z } from 'zod'; 10 | import iconv from 'iconv-lite'; 11 | import path from 'path'; 12 | import { app } from 'electron'; 13 | import fs from 'fs'; 14 | // import Potrace from 'potrace'; 15 | 16 | export interface ImageToSvgParameters extends ToolParams {} 17 | 18 | export class ImageToSvg extends Tool { 19 | static lc_name() { 20 | return 'ImageToSvg'; 21 | } 22 | 23 | name: string; 24 | 25 | description: string; 26 | 27 | constructor(params?: ImageToSvgParameters) { 28 | super(params); 29 | Object.defineProperty(this, 'name', { 30 | enumerable: true, 31 | configurable: true, 32 | writable: true, 33 | value: 'image_to_svg', 34 | }); 35 | Object.defineProperty(this, 'description', { 36 | enumerable: true, 37 | configurable: true, 38 | writable: true, 39 | value: 'image convert to svg', 40 | }); 41 | } 42 | 43 | async _call(filePath: string, runManager, config): Promise { 44 | // try { 45 | // if (!isString(filePath)) { 46 | // return 'input value is not filePath'; 47 | // } 48 | // const ext = path.extname(filePath).toLowerCase(); 49 | // const traced = await Potrace(filePath).trace(); 50 | // } catch (err) { 51 | // return JSON.stringify(err); 52 | // } 53 | return null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/tools/KnowledgeBaseQuery.ts: -------------------------------------------------------------------------------- 1 | import { IterableReadableStream } from '@langchain/core/utils/stream'; 2 | import { BaseTool } from './BaseTool'; 3 | import { z } from 'zod'; 4 | import { ToolCall } from '@langchain/core/dist/messages/tool'; 5 | import { ToolParams } from '@langchain/core/tools'; 6 | import { kbManager } from '../knowledgebase'; 7 | 8 | interface KnowledgeBaseQueryParameters extends ToolParams { 9 | knowledgebaseIds?: string[] | undefined; 10 | limit?: number; 11 | } 12 | 13 | export class KnowledgeBaseQuery extends BaseTool { 14 | schema = z.object({ 15 | knowledgebaseIds: z 16 | .optional(z.array(z.string())) 17 | .describe('knowledgebase id'), 18 | question: z.string().describe('question'), 19 | }); 20 | 21 | output = 'path\n├── file-1.mp4\n├── file-2.mp4\n├── ...'; 22 | 23 | name: string = 'knowledgebase-query'; 24 | 25 | description: string = 26 | 'find the context related to the question from the knowledge base.'; 27 | 28 | knowledgebaseIds?: string[] | undefined; 29 | 30 | limit?: number; 31 | 32 | constructor(params?: KnowledgeBaseQueryParameters) { 33 | super(params); 34 | this.knowledgebaseIds = params?.knowledgebaseIds; 35 | this.limit = params?.limit ?? 10; 36 | } 37 | 38 | async _call( 39 | input: z.infer, 40 | runManager, 41 | config, 42 | ): Promise { 43 | const stream = await this.stream(input, config); 44 | let output = ''; 45 | for await (const chunk of stream) { 46 | output += chunk; 47 | } 48 | return output; 49 | } 50 | 51 | async stream( 52 | input: z.infer, 53 | options, 54 | ): Promise> { 55 | if (!input.question) { 56 | throw new Error('question is required'); 57 | } 58 | const that = this; 59 | async function* generateStream() { 60 | for (const kbId of that.knowledgebaseIds) { 61 | try { 62 | const result = await kbManager.query(kbId, input.question as string, { 63 | k: that.limit, 64 | }); 65 | const output = result 66 | .filter( 67 | (x) => 68 | x.score > 0.5 && (!x.reranker_score || x.reranker_score > 0.5), 69 | ) 70 | .map( 71 | (x) => 72 | `\n${x.document.metadata.title ? `TITLE:[${x.document.metadata.title}](${x.document.metadata.source})\n` : ''}\nPAGE CONTENT:\n${x.document.pageContent}\n`, 73 | ); 74 | 75 | yield output.join('\n'); 76 | } catch {} 77 | } 78 | yield ''; 79 | } 80 | 81 | const stream = IterableReadableStream.fromAsyncGenerator(generateStream()); 82 | return stream; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/tools/RemoveBackground.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager'; 3 | import { RunnableConfig } from '@langchain/core/runnables'; 4 | import { Tool, StructuredTool, ToolParams } from '@langchain/core/tools'; 5 | import { HuggingFaceTransformersEmbeddings } from '../embeddings/HuggingFaceTransformersEmbeddings'; 6 | 7 | import { Transformers } from '../utils/transformers'; 8 | import { z } from 'zod'; 9 | import { isUrl } from '../utils/is'; 10 | import fs from 'fs'; 11 | import path from 'path'; 12 | import { getTmpPath } from '../utils/path'; 13 | import { v4 as uuidv4 } from 'uuid'; 14 | import { BaseTool } from './BaseTool'; 15 | import { FormSchema } from '@/types/form'; 16 | import { t } from 'i18next'; 17 | 18 | export interface RemoveBackgroundParameters extends ToolParams { 19 | modelName: string; 20 | } 21 | 22 | export class RemoveBackground extends BaseTool { 23 | schema = z.object({ 24 | pathOrUrl: z.string().describe('local file or folder path, or Url '), 25 | outputFormat: z.optional(z.enum(['base64', 'file'])).default('file'), 26 | outputPath: z.optional( 27 | z 28 | .string() 29 | .describe('if outputFormat is file ,this path for save png file'), 30 | ), 31 | }); 32 | 33 | configSchema: FormSchema[] = [ 34 | { 35 | label: t('common.model'), 36 | field: 'modelName', 37 | component: 'Select', 38 | defaultValue: 'rmbg-1.4', 39 | componentProps: { 40 | options: [ 41 | { label: 'rmbg-2.0', value: 'rmbg-2.0' }, 42 | { label: 'rmbg-1.4', value: 'rmbg-1.4' }, 43 | ], 44 | }, 45 | }, 46 | ]; 47 | 48 | name = 'remove-background'; 49 | 50 | description = 'Remove Image Background'; 51 | 52 | modelName: string; 53 | 54 | output = 'c:\\windows\\...'; 55 | 56 | constructor(params?: RemoveBackgroundParameters) { 57 | super(params); 58 | this.modelName = params?.modelName; 59 | } 60 | 61 | async _call(input: z.infer): Promise { 62 | const buffer = await new Transformers({ 63 | task: 'image-segmentation', 64 | modelName: this.modelName ?? 'rmbg-1.4', 65 | }).rmbg(input.pathOrUrl); 66 | if (input.outputFormat == 'base64') { 67 | const base64String = buffer.toString('base64'); 68 | return `data:image/png;base64,${base64String}`; 69 | } else { 70 | if (!input.outputPath) 71 | input.outputPath = path.join(getTmpPath(), `${uuidv4()}.png`); 72 | fs.writeFileSync(input.outputPath, buffer); 73 | return input.outputPath; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/tools/Sleep.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import { 3 | exec, 4 | execSync, 5 | execFileSync, 6 | ExecSyncOptionsWithStringEncoding, 7 | execFile, 8 | } from 'child_process'; 9 | import { isArray, isString } from '../utils/is'; 10 | import { z } from 'zod'; 11 | import iconv from 'iconv-lite'; 12 | import { runCommand } from '../utils/exec'; 13 | import { BaseTool } from './BaseTool'; 14 | import { platform } from 'process'; 15 | 16 | export interface SleepToolParameters extends ToolParams {} 17 | 18 | export class SleepTool extends BaseTool { 19 | schema = z.object({ 20 | seconds: z.number(), 21 | }); 22 | 23 | name: string = 'sleep'; 24 | 25 | description: string = `sleep for a while`; 26 | 27 | constructor(params?: SleepToolParameters) { 28 | super(); 29 | } 30 | 31 | async _call( 32 | input: z.infer, 33 | runManager, 34 | config, 35 | ): Promise { 36 | const seconds = input.seconds < 0 ? 5 : input.seconds; 37 | await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); 38 | 39 | return `sleep ${seconds} seconds done`; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/tools/Suro.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/src/main/tools/Suro.ts -------------------------------------------------------------------------------- /src/main/tools/TavilySearch.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import { 3 | exec, 4 | execSync, 5 | execFileSync, 6 | ExecSyncOptionsWithStringEncoding, 7 | } from 'child_process'; 8 | import { isArray, isString } from '../utils/is'; 9 | import { z } from 'zod'; 10 | import iconv from 'iconv-lite'; 11 | 12 | import { TavilySearchAPIRetriever } from '@langchain/community/retrievers/tavily_search_api'; 13 | import { TavilySearchResults } from '@langchain/community/tools/tavily_search'; 14 | import fetch from 'node-fetch'; 15 | import { getEnvironmentVariable } from '@langchain/core/utils/env'; 16 | import { HttpsProxyAgent } from 'https-proxy-agent'; 17 | import settingsManager from '../settings'; 18 | import { BaseTool } from './BaseTool'; 19 | import { FormSchema } from '@/types/form'; 20 | 21 | export interface TavilySearchParameters extends ToolParams { 22 | apiKey: string; 23 | maxResults: number; 24 | kwargs?: Record; 25 | } 26 | 27 | export class TavilySearchTool extends BaseTool { 28 | schema = z.object({ 29 | query: z.string().describe('The search query to search for.'), 30 | }); 31 | 32 | configSchema: FormSchema[] = [ 33 | { 34 | label: 'Api Key', 35 | field: 'apiKey', 36 | component: 'InputPassword', 37 | }, 38 | { 39 | label: 'Max Results', 40 | field: 'maxResults', 41 | component: 'InputNumber', 42 | defaultValue: 5, 43 | }, 44 | ]; 45 | 46 | name: string = 'tavily_search'; 47 | 48 | description: string = 49 | 'A search engine optimized for comprehensive, accurate, and trusted results. Useful for when you need to answer questions about current events. Input should be a search query.'; 50 | 51 | officialLink: string = 'https://app.tavily.com/home'; 52 | 53 | apiKey: string; 54 | 55 | maxResults: number; 56 | 57 | kwargs?: Record; 58 | 59 | constructor(fields?: TavilySearchParameters) { 60 | super(fields); 61 | const { apiKey } = fields ?? {}; 62 | this.maxResults = fields?.maxResults ?? this.maxResults; 63 | this.kwargs = fields?.kwargs ?? this.kwargs; 64 | this.apiKey = fields?.apiKey ?? getEnvironmentVariable('TAVILY_API_KEY'); 65 | } 66 | 67 | async _call( 68 | input: z.infer, 69 | runManager, 70 | config, 71 | ): Promise { 72 | const proxy = settingsManager.getPorxy(); 73 | 74 | const body = { 75 | query: input.query, 76 | max_results: this.maxResults, 77 | api_key: this.apiKey, 78 | }; 79 | const response = await fetch('https://api.tavily.com/search', { 80 | method: 'POST', 81 | headers: { 82 | 'content-type': 'application/json', 83 | }, 84 | agent: proxy ? new HttpsProxyAgent(proxy) : false, 85 | body: JSON.stringify({ ...body, ...this.kwargs }), 86 | }); 87 | const json = await response.json(); 88 | if (!response.ok) { 89 | throw new Error( 90 | `Request failed with status code ${response.status}: ${json.error || json.detail}`, 91 | ); 92 | } 93 | if (!Array.isArray(json.results)) { 94 | throw new Error(`Could not parse Tavily results. Please try again.`); 95 | } 96 | return JSON.stringify(json.results); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/tools/TerminalTool.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import { 3 | exec, 4 | execSync, 5 | execFileSync, 6 | ExecSyncOptionsWithStringEncoding, 7 | execFile, 8 | } from 'child_process'; 9 | import { isArray, isString } from '../utils/is'; 10 | import { z } from 'zod'; 11 | import iconv from 'iconv-lite'; 12 | import { runCommand } from '../utils/exec'; 13 | import { BaseTool } from './BaseTool'; 14 | import { platform } from 'process'; 15 | import { FormSchema } from '@/types/form'; 16 | 17 | export interface TerminalToolParameters extends ToolParams { 18 | ask_human_input: boolean; 19 | defaultTerminal?: string; 20 | } 21 | 22 | export class TerminalTool extends BaseTool { 23 | schema = z.object( 24 | platform == 'win32' 25 | ? { 26 | command: z.string(), 27 | terminal: z.enum(['cmd.exe', 'pwsh.exe']).optional(), 28 | } 29 | : { command: z.string() }, 30 | ); 31 | 32 | name: string = 'terminal'; 33 | 34 | description: string = `run ${platform == 'win32' ? 'Cmd or PowerShell' : 'bash'} command on ${platform} system.`; 35 | 36 | ask_human_input: boolean = false; 37 | 38 | terminale: string = platform == 'win32' ? 'cmd.exe' : 'bash'; 39 | 40 | configSchema?: FormSchema[] = [ 41 | { 42 | field: 'defaultTerminal', 43 | component: 'Select', 44 | label: 'Default Terminal', 45 | componentProps: { 46 | options: 47 | platform == 'win32' 48 | ? [ 49 | { 50 | label: 'Cmd', 51 | value: 'cmd.exe', 52 | }, 53 | { 54 | label: 'PowerShell', 55 | value: 'pwsh.exe', 56 | }, 57 | ] 58 | : [ 59 | { 60 | label: 'bash', 61 | value: 'bash', 62 | }, 63 | ], 64 | }, 65 | }, 66 | ]; 67 | 68 | constructor(params?: TerminalToolParameters) { 69 | super(); 70 | const { ask_human_input, defaultTerminal } = params ?? {}; 71 | this.ask_human_input = ask_human_input ?? false; 72 | if (platform == 'win32') { 73 | this.terminale = defaultTerminal || 'cmd.exe'; 74 | } else { 75 | this.terminale = defaultTerminal || 'bash'; 76 | } 77 | } 78 | 79 | async _call( 80 | input: z.infer, 81 | runManager, 82 | config, 83 | ): Promise { 84 | console.log(`Executing command:\n ${input.command}`); 85 | 86 | if (this.ask_human_input) { 87 | // if (user_input == 'y') { 88 | // } 89 | return null; 90 | } else { 91 | let res; 92 | try { 93 | res = await runCommand( 94 | input.command as string, 95 | (input.terminal as string) || this.terminale, 96 | ); 97 | } catch (err) { 98 | console.error(err); 99 | res = err.message; 100 | } 101 | return res; 102 | } 103 | 104 | //return null; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/tools/VectorizerAI.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import fs from 'fs'; 3 | import ffmpeg from 'fluent-ffmpeg'; 4 | import { PassThrough } from 'stream'; 5 | import { Transformers } from '../utils/transformers'; 6 | import FormData from 'form-data'; 7 | import fetch, { Response } from 'node-fetch'; 8 | import { BaseTool } from './BaseTool'; 9 | import { FormSchema } from '@/types/form'; 10 | import { z } from 'zod'; 11 | import { t } from 'i18next'; 12 | 13 | export interface VectorizerParameters extends ToolParams { 14 | apiKeyName: string; 15 | 16 | apiKey: string; 17 | 18 | mode: string; 19 | } 20 | 21 | export class Vectorizer extends BaseTool { 22 | schema = z.object({ 23 | image: z.string().describe('The image to vectorize'), 24 | }); 25 | configSchema: FormSchema[] = [ 26 | { 27 | label: 'Api Key Name', 28 | field: 'apiKeyName', 29 | component: 'Input', 30 | }, 31 | { 32 | label: 'Api Key', 33 | field: 'apiKey', 34 | component: 'InputPassword', 35 | }, 36 | { 37 | label: t('common.mode'), 38 | field: 'mode', 39 | component: 'Select', 40 | defaultValue: 'production', 41 | componentProps: { 42 | options: [ 43 | { label: 'Test', value: 'test' }, 44 | { label: 'Preview', value: 'preview' }, 45 | { label: 'Production', value: 'production' }, 46 | ], 47 | }, 48 | }, 49 | ]; 50 | 51 | name: string = 'vectorizer'; 52 | 53 | description: string = 54 | 'A tool to quickly and easily convert PNG and JPG images to SVG vector graphics'; 55 | 56 | apiKeyName: string; 57 | 58 | apiKey: string; 59 | 60 | officialLink: string = 'https://vectorizer.ai/account'; 61 | 62 | mode: string; 63 | 64 | constructor(params?: VectorizerParameters) { 65 | super(params); 66 | this.apiKey = params?.apiKey; 67 | this.apiKeyName = params?.apiKeyName; 68 | this.mode = params?.mode || 'production'; 69 | } 70 | 71 | async _call(input: string, runManager, config): Promise { 72 | // 构建Basic Auth header 73 | const auth = `Basic ${Buffer.from(`${this.apiKeyName}:${this.apiKey}`).toString('base64')}`; 74 | const form = new FormData(); 75 | form.append('image', fs.createReadStream(input)); // 将文件添加到 FormData 76 | form.append('mode', this.mode); 77 | const res = await fetch('https://vectorizer.ai/api/v1/vectorize', { 78 | method: 'POST', 79 | headers: { 80 | Authorization: auth, 81 | }, 82 | body: form, 83 | }); 84 | if (res.ok) { 85 | const buffer: Buffer = await res.buffer(); 86 | 87 | // 将 buffer 写到文件 result.svg 88 | // fs.writeFileSync('result.svg', buffer); 89 | const xml = buffer.toString(); 90 | return xml; 91 | } else { 92 | throw new Error(`HTTP error! status: ${res.status}`); 93 | } 94 | 95 | return ''; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/tools/WeatherTool.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import { z } from 'zod'; 3 | import { WolframAlphaTool } from '@langchain/community/tools/wolframalpha'; 4 | export interface WeatherToolParameters extends ToolParams { 5 | localtion: string; 6 | unit: string; 7 | } 8 | 9 | export class WeatherTool extends Tool { 10 | static lc_name() { 11 | return 'WeatherTool'; 12 | } 13 | 14 | localtion!: string; 15 | 16 | unit!: string; 17 | 18 | name: string; 19 | 20 | description: string; 21 | 22 | constructor(params?: WeatherToolParameters) { 23 | super(params); 24 | Object.defineProperty(this, 'name', { 25 | enumerable: true, 26 | configurable: true, 27 | writable: true, 28 | value: 'get_current_weather', 29 | }); 30 | Object.defineProperty(this, 'description', { 31 | enumerable: true, 32 | configurable: true, 33 | writable: true, 34 | value: 'Get the current weather in a given location', 35 | }); 36 | Object.defineProperty(this, 'schema', { 37 | enumerable: true, 38 | configurable: true, 39 | writable: true, 40 | value: z.object({ 41 | localtion: z 42 | .string() 43 | .describe('The city and state, e.g. San Francisco, CA'), 44 | unit: z.enum(['celsius', 'fahrenheit']), 45 | }), 46 | }); 47 | const { localtion, unit } = params ?? {}; 48 | this.localtion = localtion; 49 | this.unit = unit || this.unit; 50 | } 51 | call(arg, callbacks) { 52 | return super.call( 53 | typeof arg === 'string' || !arg ? { input: arg } : arg, 54 | callbacks, 55 | ); 56 | } 57 | 58 | async _call(input): Promise { 59 | const { localtion, unit } = input; 60 | if (localtion.toLowerCase().includes('tokyo')) { 61 | return JSON.stringify({ 62 | localtion: 'Tokyo', 63 | temperature: '10', 64 | unit: 'celsius', 65 | }); 66 | } 67 | if ( 68 | localtion.toLowerCase().includes('beijing') || 69 | localtion.toLowerCase().includes('北京') 70 | ) { 71 | return JSON.stringify({ 72 | localtion: 'beijing', 73 | temperature: '10', 74 | unit: 'celsius', 75 | }); 76 | } 77 | if ( 78 | localtion.toLowerCase().includes('guangzhou') || 79 | localtion.toLowerCase().includes('广州') 80 | ) { 81 | return JSON.stringify({ 82 | localtion: 'guangzhou', 83 | temperature: '20', 84 | unit: 'celsius', 85 | }); 86 | } 87 | if (localtion.toLowerCase().includes('san francisco')) { 88 | return JSON.stringify({ 89 | location: 'San Francisco', 90 | temperature: '72', 91 | unit: 'fahrenheit', 92 | }); 93 | } 94 | if (localtion.toLowerCase().includes('paris')) { 95 | return JSON.stringify({ 96 | localtion: 'Paris', 97 | temperature: '22', 98 | unit: 'celsius', 99 | }); 100 | } 101 | return JSON.stringify({ localtion: localtion, temperature: 'unknown' }); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/tools/XhsTool.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/src/main/tools/XhsTool.ts -------------------------------------------------------------------------------- /src/main/tools/websearch/BraveSearch.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolParams } from '@langchain/core/tools'; 2 | import { z } from 'zod'; 3 | import iconv from 'iconv-lite'; 4 | import { BraveSearch } from '@langchain/community/tools/brave_search'; 5 | // import fetch from 'node-fetch'; 6 | import { getEnvironmentVariable } from '@langchain/core/utils/env'; 7 | import { HttpsProxyAgent } from 'https-proxy-agent'; 8 | 9 | import { BaseTool } from '../BaseTool'; 10 | import { FormSchema } from '@/types/form'; 11 | import settingsManager from '@/main/settings'; 12 | 13 | export interface BraveSearchParameters extends ToolParams { 14 | apiKey: string; 15 | } 16 | 17 | export class BraveSearchTool extends BaseTool { 18 | schema = z.object({ 19 | query: z.string().describe('The search query to search for.'), 20 | type: z 21 | .enum(['web', 'news', 'images', 'videos', 'suggest', 'spellcheck']) 22 | .default('web') 23 | .optional(), 24 | }); 25 | 26 | configSchema: FormSchema[] = [ 27 | { 28 | label: 'Api Key', 29 | field: 'apiKey', 30 | component: 'InputPassword', 31 | required: true, 32 | }, 33 | // { 34 | // label: 'Max Results', 35 | // field: 'maxResults', 36 | // component: 'InputNumber', 37 | // defaultValue: 5, 38 | // }, 39 | ]; 40 | 41 | name: string = 'brave_web_search'; 42 | 43 | description: string = 44 | 'a search engine. useful for when you need to answer questions about current events. input should be a search query.'; 45 | 46 | officialLink: string = 'https://api-dashboard.search.brave.com/app/keys'; 47 | 48 | apiKey: string; 49 | 50 | maxResults: number; 51 | 52 | kwargs?: Record; 53 | 54 | constructor(fields?: BraveSearchParameters) { 55 | super(fields); 56 | const { apiKey } = fields ?? {}; 57 | this.apiKey = 58 | apiKey || 59 | settingsManager.getSettings().webSearchEngine.brave.apiKey || 60 | getEnvironmentVariable('BRAVE_SEARCH_API_KEY'); 61 | } 62 | 63 | async _call( 64 | input: z.infer, 65 | runManager, 66 | config, 67 | ): Promise { 68 | //const proxy = settingsManager.getPorxy(); 69 | 70 | const headers = { 71 | 'X-Subscription-Token': this.apiKey, 72 | Accept: 'application/json', 73 | }; 74 | const searchUrl = new URL( 75 | `https://api.search.brave.com/res/v1/${input.type || 'web'}/search?q=${encodeURIComponent(input.query)}`, 76 | ); 77 | const response = await fetch(searchUrl, { 78 | headers, 79 | //agent: proxy ? new HttpsProxyAgent(proxy) : false, 80 | }); 81 | if (!response.ok) { 82 | throw new Error(`HTTP error ${response.status}`); 83 | } 84 | const parsedResponse = await response.json(); 85 | if (!response.ok) { 86 | throw new Error( 87 | `Request failed with status code ${response.status}: ${parsedResponse.error || parsedResponse.detail}`, 88 | ); 89 | } 90 | const webSearchResults = parsedResponse.web?.results; 91 | const finalResults = Array.isArray(webSearchResults) 92 | ? webSearchResults.map((item) => ({ 93 | title: item.title, 94 | link: item.url, 95 | snippet: item.description, 96 | })) 97 | : []; 98 | return JSON.stringify(finalResults); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export function resolveHtmlPath(htmlFileName: string) { 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | const url = new URL(`http://localhost:${port}`); 9 | url.pathname = htmlFileName; 10 | return url.href; 11 | } 12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/utils/common.ts: -------------------------------------------------------------------------------- 1 | import { createWriteStream } from 'fs'; 2 | import path from 'path'; 3 | import { getTmpPath } from './path'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | 6 | export const removeEmptyValues = ( 7 | obj: Record, 8 | ): Record => { 9 | const result = { ...obj }; 10 | 11 | Object.keys(result).forEach((key) => { 12 | const value = result[key]; 13 | 14 | // 检查值是否为 null、undefined 或空字符串 15 | if ( 16 | value === null || 17 | value === undefined || 18 | (typeof value === 'string' && value.trim() === '') 19 | ) { 20 | delete result[key]; 21 | } 22 | // 如果值是对象,递归清理 23 | else if (typeof value === 'object' && !Array.isArray(value)) { 24 | result[key] = removeEmptyValues(value); 25 | // 如果清理后对象为空,则删除该属性 26 | if (Object.keys(result[key]).length === 0) { 27 | delete result[key]; 28 | } 29 | } 30 | }); 31 | 32 | return result; 33 | }; 34 | 35 | export const downloadFile = async ( 36 | url: string, 37 | savePath: string, 38 | ): Promise => { 39 | try { 40 | const response = await fetch(url); 41 | 42 | if (!response.ok) { 43 | throw new Error(`下载失败: ${response.status} ${response.statusText}`); 44 | } 45 | 46 | const buffer = await response.arrayBuffer(); 47 | const data = Buffer.from(buffer); 48 | 49 | // 如果未指定保存路径,生成一个临时路径 50 | let finalSavePath = savePath; 51 | if (!finalSavePath) { 52 | finalSavePath = path.join( 53 | getTmpPath(), 54 | `${uuidv4()}${path.extname(url) || ''}`, 55 | ); 56 | } 57 | 58 | return new Promise((resolve, reject) => { 59 | const writer = createWriteStream(finalSavePath); 60 | writer.on('error', (err) => { 61 | reject(err); 62 | }); 63 | writer.on('finish', () => { 64 | resolve(finalSavePath); 65 | }); 66 | writer.write(data); 67 | writer.end(); 68 | }); 69 | } catch (error) { 70 | console.error('文件下载错误:', error); 71 | throw error; 72 | } 73 | }; 74 | 75 | export const base64ToFile = async ( 76 | base64: string, 77 | savePath: string, 78 | ): Promise => { 79 | const buffer = Buffer.from(base64, 'base64'); 80 | const writer = createWriteStream(savePath); 81 | writer.write(buffer); 82 | writer.end(); 83 | return savePath; 84 | }; 85 | -------------------------------------------------------------------------------- /src/main/utils/document_transformers.ts: -------------------------------------------------------------------------------- 1 | // import { Readability } from '@mozilla/readability'; 2 | // import { JSDOM } from 'jsdom'; 3 | 4 | import { 5 | MappingDocumentTransformer, 6 | Document, 7 | } from '@langchain/core/documents'; 8 | // import TurndownService from 'turndown'; 9 | import settingsManager from '../settings'; 10 | import fetch from 'node-fetch'; 11 | 12 | export const htmlToMarkdown = async ( 13 | doc: Document, 14 | options?: { 15 | debug?: boolean; 16 | maxElemsToParse?: number; 17 | nbTopCandidates?: number; 18 | charThreshold?: number; 19 | classesToPreserve?: string[]; 20 | keepClasses?: boolean; 21 | disableJSONLD?: boolean; 22 | allowedVideoRegex?: RegExp; 23 | }, 24 | ): Promise => { 25 | const url = `https://r.jina.ai/${doc.metadata.source}`; 26 | const res = await fetch(url, { 27 | method: 'GET', 28 | }); 29 | const text = await res.text(); 30 | 31 | const title = text.substring(7, text.indexOf('\n\nURL Source: ')); 32 | const urlSource = text.substring( 33 | text.indexOf('\n\nURL Source: ') + '\n\nURL Source: '.length, 34 | text.indexOf('\n\nMarkdown Content:\n'), 35 | ); 36 | const markdown = text.substring( 37 | text.indexOf('\n\nMarkdown Content:\n') + '\n\nMarkdown Content:\n'.length, 38 | ); 39 | // const dom = new JSDOM(doc.pageContent); 40 | // const reader = new Readability(dom.window.document, options); 41 | // const result = reader.parse(); 42 | 43 | // const turndownService = new TurndownService({ 44 | // codeBlockStyle: 'fenced', 45 | // }); 46 | // const markdown = turndownService.turndown(result.content); 47 | // const name = result.title; 48 | return new Document({ 49 | metadata: { title: title, source: urlSource }, 50 | pageContent: markdown, 51 | }); 52 | }; 53 | export const urlToMarkdown = async ( 54 | url: string, 55 | options?: { 56 | debug?: boolean; 57 | maxElemsToParse?: number; 58 | nbTopCandidates?: number; 59 | charThreshold?: number; 60 | classesToPreserve?: string[]; 61 | keepClasses?: boolean; 62 | disableJSONLD?: boolean; 63 | allowedVideoRegex?: RegExp; 64 | }, 65 | ): Promise => { 66 | const res = await fetch(`https://r.jina.ai/${url}`, { 67 | method: 'GET', 68 | agent: await settingsManager.getHttpAgent(), 69 | }); 70 | const text = await res.text(); 71 | 72 | let title = text.substring(7, text.indexOf('\n\nURL Source: ')).trim(); 73 | if (!title) { 74 | title = 'unkown'; 75 | } 76 | const urlSource = text.substring( 77 | text.indexOf('\n\nURL Source: ') + '\n\nURL Source: '.length, 78 | text.indexOf('\n\nMarkdown Content:\n'), 79 | ); 80 | const markdown = text.substring( 81 | text.indexOf('\n\nMarkdown Content:\n') + '\n\nMarkdown Content:\n'.length, 82 | ); 83 | // const dom = new JSDOM(doc.pageContent); 84 | // const reader = new Readability(dom.window.document, options); 85 | // const result = reader.parse(); 86 | 87 | // const turndownService = new TurndownService({ 88 | // codeBlockStyle: 'fenced', 89 | // }); 90 | // const markdown = turndownService.turndown(result.content); 91 | // const name = result.title; 92 | return new Document({ 93 | metadata: { title: title, source: urlSource }, 94 | pageContent: markdown, 95 | }); 96 | }; 97 | -------------------------------------------------------------------------------- /src/main/utils/is.ts: -------------------------------------------------------------------------------- 1 | const { toString } = Object.prototype; 2 | 3 | export function is(val: unknown, type: string) { 4 | return toString.call(val) === `[object ${type}]`; 5 | } 6 | 7 | export function isDef(val?: T): val is T { 8 | return typeof val !== 'undefined'; 9 | } 10 | 11 | export function isUnDef(val?: T): val is T { 12 | return !isDef(val); 13 | } 14 | 15 | export function isObject(val: any): val is Record { 16 | return val !== null && is(val, 'Object'); 17 | } 18 | 19 | export function isDate(val: unknown): val is Date { 20 | return is(val, 'Date'); 21 | } 22 | 23 | export function isNull(val: unknown): val is null { 24 | return val === null; 25 | } 26 | 27 | export function isNullAndUnDef(val: unknown): val is null | undefined { 28 | return isUnDef(val) && isNull(val); 29 | } 30 | 31 | export function isNullOrUnDef(val: unknown): val is null | undefined { 32 | return isUnDef(val) || isNull(val); 33 | } 34 | 35 | export function isNumber(val: unknown): val is number { 36 | return is(val, 'Number'); 37 | } 38 | export function isFunction(val: unknown): val is Function { 39 | return typeof val === 'function'; 40 | } 41 | export function isPromise(val: unknown): val is Promise { 42 | return ( 43 | is(val, 'Promise') && 44 | isObject(val) && 45 | isFunction(val.then) && 46 | isFunction(val.catch) 47 | ); 48 | } 49 | 50 | export function isString(val: unknown): val is string { 51 | return is(val, 'String'); 52 | } 53 | 54 | export function isBoolean(val: unknown): val is boolean { 55 | return is(val, 'Boolean'); 56 | } 57 | 58 | export function isRegExp(val: unknown): val is RegExp { 59 | return is(val, 'RegExp'); 60 | } 61 | 62 | export function isArray(val: any): val is Array { 63 | return val && Array.isArray(val); 64 | } 65 | 66 | export function isWindow(val: any): val is Window { 67 | return typeof window !== 'undefined' && is(val, 'Window'); 68 | } 69 | 70 | export function isElement(val: unknown): val is Element { 71 | return isObject(val) && !!val.tagName; 72 | } 73 | 74 | export function isMap(val: unknown): val is Map { 75 | return is(val, 'Map'); 76 | } 77 | 78 | export const isServer = typeof window === 'undefined'; 79 | 80 | export const isClient = !isServer; 81 | 82 | export function isUrl(path: string): boolean { 83 | const reg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?/; 84 | return reg.test(path); 85 | } 86 | 87 | export function isEmpty(val: T): val is T { 88 | if (isArray(val) || isString(val)) { 89 | return val.length === 0; 90 | } 91 | 92 | if (val instanceof Map || val instanceof Set) { 93 | return val.size === 0; 94 | } 95 | 96 | if (isObject(val)) { 97 | return Object.keys(val).length === 0; 98 | } 99 | 100 | return false; 101 | } 102 | -------------------------------------------------------------------------------- /src/main/utils/path.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'electron'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import settingsManager from '../settings'; 5 | import { isArray, isString } from './is'; 6 | 7 | const getDataPath = () => { 8 | let userData; 9 | if (app.isPackaged) { 10 | userData = app.getPath('userData'); 11 | } else { 12 | userData = app.getAppPath(); 13 | } 14 | userData = app.getPath('userData'); 15 | 16 | const dataPath = path.join(userData, 'data'); 17 | if (!fs.existsSync(dataPath)) { 18 | fs.mkdirSync(dataPath, { recursive: true }); 19 | } 20 | return dataPath; 21 | }; 22 | 23 | const getTmpPath = () => { 24 | const userData = app.getPath('userData'); 25 | return path.join(userData, 'tmp'); 26 | }; 27 | 28 | const getAssetPath = (...paths: string[]): string => { 29 | return path.join(app.getAppPath(), 'assets', ...paths); 30 | }; 31 | 32 | const rootPath = app.isPackaged ? app.getAppPath() : __dirname; 33 | 34 | const getModelsPath = () => { 35 | let userData; 36 | if (app.isPackaged) { 37 | userData = app.getPath('userData'); 38 | } else { 39 | userData = app.getAppPath(); 40 | } 41 | const { localModelPath } = settingsManager.getSettings(); 42 | const modelPath = localModelPath; 43 | 44 | if (!fs.existsSync(modelPath)) { 45 | fs.mkdirSync(modelPath, { recursive: true }); 46 | } 47 | return modelPath; 48 | }; 49 | 50 | const getDefaultModelsPath = () => { 51 | let userData; 52 | if (app.isPackaged) { 53 | userData = app.getPath('userData'); 54 | } else { 55 | userData = app.getAppPath(); 56 | } 57 | const modelPath = path.join(userData, 'models'); 58 | 59 | if (!fs.existsSync(modelPath)) { 60 | fs.mkdirSync(modelPath, { recursive: true }); 61 | } 62 | return modelPath; 63 | }; 64 | 65 | const getWorkspacePath = ( 66 | inputPath: string | string[], 67 | config: any, 68 | ): string[] | string => { 69 | const { workspace } = config.configurable; 70 | if (!workspace) return inputPath; 71 | 72 | if (isArray(inputPath)) { 73 | return inputPath.map((x) => 74 | path.isAbsolute(x) ? x : path.join(workspace, x), 75 | ); 76 | } else if (isString(inputPath)) { 77 | return path.isAbsolute(inputPath) 78 | ? inputPath 79 | : path.join(workspace, inputPath); 80 | } 81 | throw new Error('InputPath Error'); 82 | }; 83 | 84 | export { 85 | getDataPath, 86 | getTmpPath, 87 | getModelsPath, 88 | getDefaultModelsPath, 89 | getAssetPath, 90 | rootPath, 91 | }; 92 | -------------------------------------------------------------------------------- /src/main/utils/providerUtil.ts: -------------------------------------------------------------------------------- 1 | export const getProviderModel = (providerModel: string) => { 2 | try { 3 | const _ = providerModel.split('@'); 4 | return { modelName: _[0], provider: _[1] }; 5 | } catch { 6 | return undefined; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/main/utils/systemProxy.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import os from 'os'; 3 | 4 | export type SystemProxySettings = { 5 | proxyEnable: boolean; 6 | proxyServer: string; 7 | }; 8 | 9 | export function getSystemProxySettings(): SystemProxySettings { 10 | const platform = os.platform(); 11 | 12 | if (platform === 'win32') { 13 | return getWindowsProxySettings(); 14 | } else if (platform === 'darwin') { 15 | return getMacProxySettings(); 16 | } else { 17 | console.warn('当前系统不支持动态获取(只支持 Windows 和 macOS)'); 18 | return { proxyEnable: false, proxyServer: '' }; 19 | } 20 | } 21 | 22 | function getWindowsProxySettings(): SystemProxySettings { 23 | try { 24 | const output = execSync( 25 | 'reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"', 26 | ).toString(); 27 | 28 | let proxyEnable = false; 29 | let proxyServer = ''; 30 | 31 | const lines = output.split('\n'); 32 | for (const line of lines) { 33 | if (line.includes('ProxyEnable')) { 34 | const match = line.match(/0x(\d+)/); 35 | proxyEnable = match ? parseInt(match[1]) !== 0 : false; 36 | } 37 | 38 | if (line.includes('ProxyServer')) { 39 | const match = line.match(/\s+REG_SZ\s+(.*)/); 40 | proxyServer = match ? match[1].trim() : ''; 41 | } 42 | } 43 | 44 | // 规范化成 http://xxx 形式 45 | if (proxyEnable && proxyServer && !proxyServer.startsWith('http')) { 46 | proxyServer = `http://${proxyServer}`; 47 | } 48 | 49 | return { proxyEnable, proxyServer }; 50 | } catch (err) { 51 | console.error('读取注册表失败:', err); 52 | return { proxyEnable: false, proxyServer: '' }; 53 | } 54 | } 55 | 56 | function getMacProxySettings(): SystemProxySettings { 57 | try { 58 | // 默认用 Wi-Fi 网络服务 59 | const httpOutput = execSync('networksetup -getwebproxy "Wi-Fi"').toString(); 60 | const httpsOutput = execSync( 61 | 'networksetup -getsecurewebproxy "Wi-Fi"', 62 | ).toString(); 63 | 64 | const http = parseMacProxyOutput(httpOutput); 65 | const https = parseMacProxyOutput(httpsOutput); 66 | 67 | let proxyEnable = false; 68 | let proxyServer = ''; 69 | 70 | if (http.enabled && http.server) { 71 | proxyEnable = true; 72 | proxyServer = `http://${http.server}:${http.port}`; 73 | } else if (https.enabled && https.server) { 74 | proxyEnable = true; 75 | proxyServer = `http://${https.server}:${https.port}`; // 这里依然返回 http:// 方便统一处理 76 | } 77 | 78 | return { proxyEnable, proxyServer }; 79 | } catch (err) { 80 | console.error('读取 macOS 代理失败:', err); 81 | return { proxyEnable: false, proxyServer: '' }; 82 | } 83 | } 84 | 85 | function parseMacProxyOutput(output: string) { 86 | const lines = output.split('\n'); 87 | let enabled = false; 88 | let server = ''; 89 | let port = ''; 90 | 91 | for (const line of lines) { 92 | if (line.startsWith('Enabled:')) { 93 | enabled = line.includes('Yes'); 94 | } 95 | if (line.startsWith('Server:')) { 96 | server = line.replace('Server: ', '').trim(); 97 | } 98 | if (line.startsWith('Port:')) { 99 | port = line.replace('Port: ', '').trim(); 100 | } 101 | } 102 | 103 | return { enabled, server, port }; 104 | } 105 | -------------------------------------------------------------------------------- /src/main/utils/tokenCounter.ts: -------------------------------------------------------------------------------- 1 | import { Tiktoken } from 'js-tiktoken/lite'; 2 | import { getEncodingNameForModel, getEncoding } from 'js-tiktoken'; 3 | import { 4 | BaseMessage, 5 | isAIMessage, 6 | isToolMessage, 7 | } from '@langchain/core/messages'; 8 | import { BaseChatModel } from '@langchain/core/language_models/chat_models'; 9 | import { isArray, isString } from './is'; 10 | 11 | const tokenCounter = async ( 12 | messages: BaseMessage[], 13 | model?: BaseChatModel, 14 | ): Promise => { 15 | // if (model && 'getNumTokens' in model) { 16 | // try { 17 | // const listTokenCounter = async (msgs: BaseMessage[]) => { 18 | // let tokenCounts = 0; 19 | // for (const msg of msgs) { 20 | // if (isAIMessage(msg)) { 21 | // if (msg.tool_calls && msg.tool_calls.length > 0) { 22 | // tokenCounts += await model.getNumTokens( 23 | // JSON.stringify(msg.tool_calls) + msg.content, 24 | // ); 25 | // } else { 26 | // tokenCounts += await model.getNumTokens(msg.content); 27 | // } 28 | // } else { 29 | // tokenCounts += await model.getNumTokens(msg.content); 30 | // } 31 | // } 32 | 33 | // return tokenCounts; 34 | // }; 35 | // const count = await listTokenCounter(messages); 36 | // return count; 37 | // } catch { 38 | // console.log('tokenCounter error'); 39 | // } 40 | // } 41 | const iktoken = getEncoding('o200k_base'); 42 | const content = messages 43 | .map((m) => { 44 | let _content = ''; 45 | if (isArray(m.content)) { 46 | _content = m.content 47 | .filter((x) => x.type == 'text') 48 | .map((x) => x.text) 49 | .join('\n'); 50 | } else if (isString(m.content)) { 51 | _content = m.content; 52 | } 53 | if (isAIMessage(m)) { 54 | return JSON.stringify(m.tool_calls) + _content; 55 | } 56 | return _content; 57 | }) 58 | .join('\n'); 59 | const tokens = iktoken.encode(content); 60 | 61 | return tokens.length; 62 | }; 63 | 64 | export default tokenCounter; 65 | -------------------------------------------------------------------------------- /src/renderer/components/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Modal, Typography } from 'antd'; 3 | import { GithubOutlined } from '@ant-design/icons'; 4 | import { supabase } from '@/main/supabase/supabaseClient'; 5 | 6 | const { Title } = Typography; 7 | 8 | function Login(props: { open: boolean; onClose: () => void }) { 9 | const { onClose, open } = props; 10 | //const [visible, setVisible] = useState(_visible); 11 | const [loading, setLoading] = useState(false); 12 | const [errorMsg, setErrorMsg] = useState(null); 13 | 14 | const handleGithubLogin = async () => { 15 | setLoading(true); 16 | setErrorMsg(null); 17 | const { error } = await supabase.auth.signInWithOAuth({ 18 | provider: 'github', 19 | options: { 20 | //redirectTo: `aime-box://auth/callback`, 21 | //skipBrowserRedirect: true, 22 | }, 23 | }); 24 | setLoading(false); 25 | if (error) { 26 | setErrorMsg(`登录失败: ${error.message}`); 27 | } else { 28 | onClose(); 29 | } 30 | }; 31 | 32 | return ( 33 | 41 | AI Chat 登录 42 | 52 | {errorMsg && ( 53 |
{errorMsg}
54 | )} 55 |
56 | ); 57 | } 58 | 59 | export default Login; 60 | -------------------------------------------------------------------------------- /src/renderer/components/agents/AgentSelect.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, useEffect, useState } from 'react'; 2 | import { Select, Space } from 'antd'; 3 | import { SelectProps } from 'antd/lib/select'; 4 | import { Providers } from '@/entity/Providers'; 5 | 6 | const { Option } = Select; 7 | 8 | interface AgentSelectProps extends SelectProps { 9 | excludes?: string[]; 10 | } 11 | 12 | interface AgentSelectRef { 13 | setAgents: () => void; 14 | } 15 | const AgentSelect = React.forwardRef( 16 | (props: AgentSelectProps, ref: ForwardedRef) => { 17 | const [agents, setAgents] = useState([]); 18 | const getAgents = async () => { 19 | let agents = await window.electron.agents.getList(); 20 | agents = agents.filter((x) => !props.excludes?.includes(x.id)); 21 | setAgents(agents); 22 | }; 23 | useEffect(() => { 24 | getAgents(); 25 | }, []); 26 | 27 | return ( 28 | 55 | ); 56 | }, 57 | ); 58 | 59 | export default AgentSelect; 60 | -------------------------------------------------------------------------------- /src/renderer/components/chat/ChatMessageBox.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AimeBox/aime-box/bab6b28edc6eb0fcb38a6411f4efd213bd182e18/src/renderer/components/chat/ChatMessageBox.css -------------------------------------------------------------------------------- /src/renderer/components/chat/ChatQuickInput.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | import { Button } from 'antd'; 3 | import React, { HTMLAttributes, useState, useContext } from 'react'; 4 | import { GlobalContext } from '@/renderer/context/GlobalContext'; 5 | import { FaRegMessage, FaSignalMessenger } from 'react-icons/fa6'; 6 | import { t } from 'i18next'; 7 | import { Prompt } from '@/entity/Prompt'; 8 | 9 | export interface ChatQuickInputProps { 10 | className?: string; 11 | onClick: (input: string) => void; 12 | } 13 | 14 | const ChatQuickInput = React.forwardRef( 15 | ( 16 | { className, onClick }: ChatQuickInputProps, 17 | ref: React.ForwardedRef<{ setValue: (value: string) => void }>, 18 | ) => { 19 | const [messages, setMessages] = useState(['Hello ?']); 20 | const { prompts } = useContext(GlobalContext); 21 | 22 | return ( 23 |
24 | {messages.map((message, index) => { 25 | return ( 26 | 35 | ); 36 | })} 37 | 53 |
54 | ); 55 | }, 56 | ); 57 | 58 | export default ChatQuickInput; 59 | -------------------------------------------------------------------------------- /src/renderer/components/chat/tool-views/CodeSandboxView.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Sandpack, 3 | SandpackCodeEditor, 4 | SandpackFileExplorer, 5 | SandpackLayout, 6 | SandpackPreview, 7 | SandpackProvider, 8 | } from '@codesandbox/sandpack-react'; 9 | import { useMemo } from 'react'; 10 | 11 | export interface CodeSandboxViewProps { 12 | className?: string; 13 | toolName?: string; 14 | toolCall?: any; 15 | } 16 | 17 | export default function CodeSandboxView(props: CodeSandboxViewProps) { 18 | const { className, toolName, toolCall } = props; 19 | const files = useMemo(() => { 20 | return { 21 | '/index.js': 'console.log("Hello, world!");', 22 | }; 23 | }, []); 24 | return ( 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/renderer/components/chat/tool-views/CodeView.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | SandpackCodeEditor, 3 | SandpackFileExplorer, 4 | SandpackLayout, 5 | SandpackPreview, 6 | SandpackProvider, 7 | } from '@codesandbox/sandpack-react'; 8 | import { Button } from 'antd'; 9 | import { useMemo } from 'react'; 10 | import { FaDownload } from 'react-icons/fa'; 11 | import { Markdown } from '../../common/Markdown'; 12 | 13 | export interface CodeViewProps { 14 | className?: string; 15 | toolName?: string; 16 | toolCall?: any; 17 | content?: any; 18 | } 19 | 20 | export default function CodeView(props: CodeViewProps) { 21 | const { className, toolName, toolCall, content } = props; 22 | 23 | return ( 24 |
25 | {/* 26 | {tcontent} 27 | */} 28 | 29 | {content && } 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/renderer/components/chat/tool-views/FileView.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | SandpackCodeEditor, 3 | SandpackFileExplorer, 4 | SandpackLayout, 5 | SandpackPreview, 6 | SandpackProvider, 7 | } from '@codesandbox/sandpack-react'; 8 | import { Button } from 'antd'; 9 | import { useMemo } from 'react'; 10 | import { FaDownload } from 'react-icons/fa'; 11 | import { Markdown } from '../../common/Markdown'; 12 | 13 | export interface FileViewProps { 14 | className?: string; 15 | toolName?: string; 16 | toolCall?: any; 17 | } 18 | 19 | export default function FileView(props: FileViewProps) { 20 | const { className, toolName, toolCall } = props; 21 | const extension = useMemo( 22 | () => toolCall.args.path.split('.').pop().toLowerCase(), 23 | [toolCall.args.path], 24 | ); 25 | const content = useMemo(() => { 26 | if (extension == 'html') { 27 | return toolCall.args.data; 28 | } 29 | return toolCall.args.data; 30 | }, [toolCall.args.data, extension]); 31 | 32 | return ( 33 |
34 | 35 | {toolCall.args.path} 36 | 37 | {extension == 'html' && ( 38 |
39 | 44 | {/* 50 | 51 | 52 | 53 | 54 | */} 55 |
56 | )} 57 | {extension == 'md' && } 58 | {!extension && toolCall.args.data} 59 | {/*
 */}
60 |     
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/renderer/components/chat/tool-views/WebSearchView.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'antd'; 2 | import { useMemo } from 'react'; 3 | import { FaDownload, FaSearch } from 'react-icons/fa'; 4 | 5 | export interface WebSearchViewProps { 6 | className?: string; 7 | toolName?: string; 8 | toolCall?: any; 9 | content?: any; 10 | } 11 | 12 | export default function WebSearchView(props: WebSearchViewProps) { 13 | const { className, toolName, toolCall, content } = props; 14 | 15 | return ( 16 |
17 |
18 | 19 | {toolCall.args.query} 20 |
21 |
22 | {JSON.parse(content?.text)?.map((item: any) => ( 23 |
27 |
{item.title}
28 | 29 | {item.url} 30 | 31 |
32 | ))} 33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/renderer/components/common/FileDropZone.css: -------------------------------------------------------------------------------- 1 | .overlay { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | /* 移除pointer-events: none,因为它会阻止拖放事件 */ 8 | opacity: 0; 9 | visibility: hidden; 10 | transition: opacity 0.5s ease, visibility 0.2s ease; 11 | background-color: rgba(0, 0, 0, 0.5); 12 | z-index: 999; 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | color: white; 17 | font-size: 24px; 18 | } 19 | 20 | .overlay.visible { 21 | opacity: 0.8; 22 | visibility: visible; 23 | 24 | } -------------------------------------------------------------------------------- /src/renderer/components/common/ProviderIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ollamaIcon from '../../../../assets/model-logos/ollama.png'; 4 | import tongyiIcon from '../../../../assets/model-logos/tongyi.png'; 5 | import anthropicIcon from '../../../../assets/model-logos/anthropic.png'; 6 | import zhipuIcon from '../../../../assets/model-logos/zhipu.png'; 7 | import openaiIcon from '../../../../assets/model-logos/openai.png'; 8 | import groqIcon from '../../../../assets/model-logos/groq.png'; 9 | import openrouterIcon from '../../../../assets/model-logos/openrouter.png'; 10 | import siliconflowIcon from '../../../../assets/model-logos/siliconflow.png'; 11 | import googleIcon from '../../../../assets/model-logos/google.png'; 12 | import deepseekIcon from '../../../../assets/model-logos/deepseek.png'; 13 | import togetheraiIcon from '../../../../assets/model-logos/togetherai.png'; 14 | import baiduIcon from '../../../../assets/model-logos/baidu.png'; 15 | import lmstudioIcon from '../../../../assets/model-logos/lmstudio.png'; 16 | import azureOpenaiIcon from '../../../../assets/model-logos/azure_openai.png'; 17 | import volcanoEngineIcon from '../../../../assets/model-logos/volcanoengine.png'; 18 | import minimaxIcon from '../../../../assets/model-logos/minimax.png'; 19 | import { pathToFileURL } from 'url'; 20 | import { cn } from '@/lib/utils'; 21 | 22 | interface ProviderIconProps { 23 | provider: string; 24 | size?: number | string | null; 25 | className?: string | null; 26 | } 27 | const logos = { 28 | tongyi: tongyiIcon, 29 | ollama: ollamaIcon, 30 | anthropic: anthropicIcon, 31 | baidu: baiduIcon, 32 | zhipu: zhipuIcon, 33 | openai: openaiIcon, 34 | groq: groqIcon, 35 | openrouter: openrouterIcon, 36 | siliconflow: siliconflowIcon, 37 | google: googleIcon, 38 | deepseek: deepseekIcon, 39 | togetherai: togetheraiIcon, 40 | lmstudio: lmstudioIcon, 41 | azure_openai: azureOpenaiIcon, 42 | volcanoengine: volcanoEngineIcon, 43 | minimax: minimaxIcon, 44 | }; 45 | // eslint-disable-next-line react/function-component-definition 46 | const ProviderIcon: React.FC = ({ 47 | provider, 48 | size = 24, 49 | className = '', 50 | }) => { 51 | const iconProps = { 52 | size, 53 | className, 54 | }; 55 | 56 | // const logos = {}; 57 | // Object.keys(logos).forEach((key) => { 58 | // logos[key] = tongyiIcon; 59 | // }); 60 | 61 | return ( 62 |
63 | {provider} 69 |
70 | ); 71 | // switch (provider) { 72 | // case ProviderType.GOOGLE: 73 | // return {''}; 74 | 75 | // default: 76 | // return null; 77 | // } 78 | }; 79 | 80 | export default ProviderIcon; 81 | -------------------------------------------------------------------------------- /src/renderer/components/common/ResponseCard.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useEffect, useState } from 'react'; 2 | import { Markdown } from './Markdown'; 3 | import { cn } from '@/lib/utils'; 4 | 5 | export interface ResponseCardProps { 6 | value: string; 7 | className?: string; 8 | } 9 | 10 | export function ResponseCard({ value, className }: ResponseCardProps) { 11 | function convertToMarkdown(obj: any, parentKey: string = ''): string { 12 | let markdown = ''; 13 | if (!obj) return ''; 14 | if (typeof obj == 'object') { 15 | for (const [key, value] of Object.entries(obj)) { 16 | const fullKey = parentKey ? `${parentKey}.${key}` : key; 17 | 18 | if (typeof value === 'object' && value !== null) { 19 | markdown += convertToMarkdown(value, fullKey); 20 | } else { 21 | if (typeof value === 'string' && value.startsWith('data:image/')) { 22 | markdown += `![image](${value})\n`; 23 | } 24 | 25 | markdown += `**${fullKey}**\n${value}\n`; 26 | } 27 | } 28 | return markdown; 29 | } else { 30 | return obj.toString(); 31 | } 32 | } 33 | const canParseJson = (value) => { 34 | try { 35 | if (typeof value == 'object') return true; 36 | const _ = JSON.parse(value); 37 | return true; 38 | } catch { 39 | return false; 40 | } 41 | }; 42 | const toMarkdown = (value: object | string) => { 43 | if (typeof value == 'object') { 44 | return convertToMarkdown(value); 45 | } else if (typeof value == 'string' && canParseJson(value)) { 46 | const s = JSON.parse(value); 47 | return convertToMarkdown(s); 48 | } else if (typeof value == 'string' && !canParseJson(value)) { 49 | if (value.startsWith('data:image/')) { 50 | return `![image](${value})\n`; 51 | } 52 | } 53 | 54 | return value; 55 | }; 56 | 57 | const [markdown, setMarkdown] = useState(toMarkdown(value)); 58 | 59 | useEffect(() => { 60 | setMarkdown(toMarkdown(value)); 61 | }, [value]); 62 | return ( 63 |
69 | 70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/renderer/components/form/BasicForm.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Checkbox, 4 | Form, 5 | Input, 6 | Modal, 7 | ModalProps, 8 | Radio, 9 | Select, 10 | message, 11 | } from 'antd'; 12 | import { 13 | FormProps, 14 | FormSchema, 15 | RenderCallbackParams, 16 | } from '../../../types/form'; 17 | import { 18 | forwardRef, 19 | useEffect, 20 | useImperativeHandle, 21 | useMemo, 22 | useState, 23 | } from 'react'; 24 | import TextArea from 'antd/es/input/TextArea'; 25 | 26 | import { isArray, isBoolean, isFunction } from '../../../main/utils/is'; 27 | import FormItem from './FormItem'; 28 | 29 | interface BasicFormProps extends FormProps { 30 | value?: object | undefined; 31 | schemas?: FormSchema[]; 32 | onFinish?: (value: any) => Promise; 33 | onChange?: (value: any) => void; 34 | loading?: boolean; 35 | // onCancel?: (text: string | undefined) => void; 36 | } 37 | const BasicForm = forwardRef((props: BasicFormProps, ref) => { 38 | const { loading = false, onChange, value } = props; 39 | const [form] = Form.useForm(); 40 | const [formModel, setFormModel] = useState(undefined); 41 | const [schemas, setSchemas] = useState(props.schemas); 42 | 43 | useImperativeHandle(ref, () => ({ 44 | updateSchema: (data: FormSchema | FormSchema[]) => { 45 | setSchemas(isArray(data) ? data : [data]); 46 | }, 47 | })); 48 | const onFinish = async () => { 49 | try { 50 | const values = await form.validateFields(); 51 | await props.onFinish(values); 52 | } catch (err) {} 53 | }; 54 | const onFieldsChange = (changedFields, allFields) => { 55 | changedFields.forEach((item) => { 56 | if (item.validated) { 57 | formModel[item.name[0]] = item.value; 58 | } 59 | }); 60 | setFormModel({ ...formModel }); 61 | onChange?.(formModel); 62 | }; 63 | // useEffect(() => { 64 | // setSchemas(props.schemas); 65 | // }, [props.schemas]); 66 | 67 | useEffect(() => { 68 | form.resetFields(); 69 | if (props.schemas) { 70 | props.schemas.forEach((x) => { 71 | if (x.defaultValue) { 72 | const defaultValue = {}; 73 | defaultValue[x.field] = x.defaultValue; 74 | form.setFieldsValue(defaultValue); 75 | } 76 | }); 77 | } 78 | setSchemas(props.schemas); 79 | 80 | setFormModel(form.getFieldsValue()); 81 | }, [props.schemas]); 82 | 83 | useEffect(() => { 84 | form.setFieldsValue(value); 85 | }, [form, value]); 86 | return ( 87 |
94 | {schemas?.map((schema, index) => { 95 | return ( 96 | 103 | ); 104 | })} 105 |
106 | 109 |
110 | 111 | ); 112 | }); 113 | 114 | export default BasicForm; 115 | -------------------------------------------------------------------------------- /src/renderer/components/form/JsonEditor.tsx: -------------------------------------------------------------------------------- 1 | import ReactJson, { InteractionProps } from '@microlink/react-json-view'; 2 | import { Select, SelectProps, Upload, UploadFile } from 'antd'; 3 | 4 | import { t } from 'i18next'; 5 | 6 | import React, { ForwardedRef, useEffect, useRef, useState } from 'react'; 7 | import { 8 | FaFile, 9 | FaFileAlt, 10 | FaFolder, 11 | FaPlus, 12 | FaRegFileAlt, 13 | FaRegFolder, 14 | } from 'react-icons/fa'; 15 | 16 | interface JsonEditorProps { 17 | value?: Record; 18 | onChange?: (value: Record) => void; 19 | } 20 | 21 | interface JsonEditorRef {} 22 | const JsonEditor = React.forwardRef( 23 | (props: JsonEditorProps, ref: ForwardedRef) => { 24 | const { value, onChange } = props; 25 | 26 | // useEffect(() => { 27 | // setFileList( 28 | // value?.map((x) => ({ 29 | // uid: x, 30 | // name: x.replaceAll('\\', '/').split('/').pop(), 31 | // fileName: x.replaceAll('\\', '/').split('/').pop(), 32 | // status: 'done', 33 | // })) || [], 34 | // ); 35 | // }, [value]); 36 | 37 | const onEdit = (value: InteractionProps) => { 38 | onChange?.(value.updated_src); 39 | }; 40 | 41 | const onAdd = (value: InteractionProps) => { 42 | onChange?.(value.updated_src); 43 | }; 44 | 45 | const onDelete = (value: any) => { 46 | onChange?.(value.updated_src); 47 | }; 48 | 49 | return ( 50 | 57 | ); 58 | }, 59 | ); 60 | 61 | export default JsonEditor; 62 | -------------------------------------------------------------------------------- /src/renderer/components/layout/Content.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface ContentProps { 4 | children?: React.ReactNode; 5 | } 6 | 7 | export default function Content({ children }: ContentProps) { 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/components/layout/Sidebar.css: -------------------------------------------------------------------------------- 1 | /* .ant-menu-item{ 2 | @apply !bg-blue-300 3 | } */ 4 | 5 | .profile-item { 6 | @apply flex justify-center 7 | 8 | 9 | } 10 | .profile-item > .ant-menu-submenu-title{ 11 | height: 64px 12 | } 13 | 14 | .ant-menu-submenu-title{ 15 | height: 64px 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/components/providers/ProviderSelect.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, useEffect, useState } from 'react'; 2 | import { Select } from 'antd'; 3 | import { SelectProps } from 'antd/lib/select'; 4 | import { Providers } from '@/entity/Providers'; 5 | 6 | const { Option } = Select; 7 | 8 | interface ProviderSelectProps extends SelectProps { 9 | type?: 'llm' | 'reranker' | 'stt' | 'tts' | 'embedding' | 'websearch'; 10 | } 11 | 12 | interface ProviderSelectRef { 13 | setProviders: () => void; 14 | } 15 | const ProviderSelect = React.forwardRef( 16 | (props: ProviderSelectProps, ref: ForwardedRef) => { 17 | const [providers, setProviders] = useState([]); 18 | const getProviders = async () => { 19 | let res; 20 | if (props.type == 'llm') 21 | res = await window.electron.providers.getLLMModels(); 22 | else if (props.type == 'reranker') 23 | res = await window.electron.providers.getRerankerModels(); 24 | else if (props.type == 'tts') 25 | res = await window.electron.providers.getTTSModels(); 26 | else if (props.type == 'stt') 27 | res = await window.electron.providers.getSTTModels(); 28 | else if (props.type == 'embedding') 29 | res = await window.electron.providers.getEmbeddingModels(); 30 | else if (props.type == 'websearch') 31 | res = await window.electron.providers.getWebSearchProviders(); 32 | 33 | const options = res.map((x) => { 34 | const options = x.models.map((o) => { 35 | return { 36 | label: {o}, 37 | value: `${o}@${x.name}`, 38 | }; 39 | }); 40 | return { 41 | label: {x.name}, 42 | title: x.name, 43 | options, 44 | }; 45 | }); 46 | 47 | setProviders(options); 48 | }; 49 | useEffect(() => { 50 | getProviders(); 51 | }, []); 52 | 53 | return ( 54 | 81 | ); 82 | }, 83 | ); 84 | 85 | export default ProviderSelect; 86 | -------------------------------------------------------------------------------- /src/renderer/components/theme/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import { GlobalSettings } from '@/main/settings'; 2 | import { State } from '@/renderer/store'; 3 | import { changeTheme, setSettings } from '@/renderer/store/settingsSlice'; 4 | import React, { createContext, useContext, useEffect, useState } from 'react'; 5 | import { useDispatch, useSelector } from 'react-redux'; 6 | 7 | type Theme = 'dark' | 'light'; 8 | 9 | type ThemeProviderProps = { 10 | children: React.ReactNode; 11 | defaultTheme: Theme; 12 | storageKey: string; 13 | }; 14 | 15 | type ThemeProviderState = { 16 | theme: Theme; 17 | setTheme: (theme: Theme | string) => void; 18 | }; 19 | 20 | const initialState: ThemeProviderState = { 21 | theme: 'light', 22 | setTheme: () => null, 23 | }; 24 | 25 | const ThemeProviderContext = createContext(initialState); 26 | export function ThemeProvider({ 27 | children, 28 | defaultTheme = 'light', 29 | storageKey = 'theme', 30 | ...props 31 | }: ThemeProviderProps) { 32 | const dispatch = useDispatch(); 33 | const settings = useSelector( 34 | (state) => state.settings.settings, 35 | ); 36 | const theme = settings?.theme?.mode ?? 'light'; 37 | 38 | const [setTheme] = useState( 39 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, 40 | ); 41 | 42 | useEffect(() => { 43 | const root = window.document.documentElement; 44 | 45 | root.classList.remove('light', 'dark'); 46 | 47 | if (theme === 'system') { 48 | const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') 49 | .matches 50 | ? 'dark' 51 | : 'light'; 52 | 53 | root.classList.add(systemTheme); 54 | return; 55 | } 56 | 57 | root.classList.add(theme); 58 | }, [theme]); 59 | 60 | const value = { 61 | theme: theme, 62 | setTheme: (theme: Theme) => { 63 | window.electron.setting.set('theme.mode', theme); 64 | localStorage.setItem('theme', theme); 65 | const settings = window.electron.setting.getSettings(); 66 | dispatch(setSettings(settings)); 67 | }, 68 | }; 69 | 70 | return ( 71 | 72 | {children} 73 | 74 | ); 75 | } 76 | 77 | export const useTheme = () => { 78 | const context = useContext(ThemeProviderContext); 79 | 80 | if (context === undefined) 81 | throw new Error('useTheme必须在ThemeProvider中使用'); 82 | 83 | return context; 84 | }; 85 | -------------------------------------------------------------------------------- /src/renderer/components/theme/ThemeToggle.tsx: -------------------------------------------------------------------------------- 1 | import { FaMoon, FaSun } from 'react-icons/fa'; 2 | import { useTheme } from './ThemeProvider'; 3 | 4 | function ThemeToggle() { 5 | const { theme, setTheme } = useTheme(); 6 | return ( 7 | 23 | ); 24 | } 25 | 26 | export default ThemeToggle; 27 | -------------------------------------------------------------------------------- /src/renderer/components/tools/ToolSelect.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, useEffect, useState } from 'react'; 2 | import { Select } from 'antd'; 3 | import { SelectProps } from 'antd/lib/select'; 4 | import { Providers } from '@/entity/Providers'; 5 | 6 | const { Option } = Select; 7 | 8 | interface ToolSelectProps extends SelectProps {} 9 | 10 | interface ToolSelectRef { 11 | setAgents: () => void; 12 | } 13 | const ToolSelect = React.forwardRef( 14 | (props: ToolSelectProps, ref: ForwardedRef) => { 15 | const [tools, setTools] = useState([]); 16 | const getTools = async () => { 17 | const tools = await window.electron.tools.getList(); 18 | 19 | const list = []; 20 | const builtInTools = tools.filter((x) => x.type == 'built-in'); 21 | list.push(...builtInTools); 22 | const mcpTools = tools.filter((x) => x.type == 'mcp'); 23 | 24 | const toolkitNames = [ 25 | ...new Set(mcpTools.map((tool) => tool.toolkit_name)), 26 | ]; 27 | toolkitNames.forEach((toolkitName) => { 28 | const mcpToolsByToolkit = mcpTools.filter( 29 | (x) => x.toolkit_name == toolkitName, 30 | ); 31 | list.push({ 32 | name: toolkitName, 33 | tools: mcpToolsByToolkit, 34 | }); 35 | }); 36 | console.log(list); 37 | setTools(list); 38 | }; 39 | useEffect(() => { 40 | getTools(); 41 | }, []); 42 | 43 | return ( 44 | 74 | ); 75 | }, 76 | ); 77 | 78 | export default ToolSelect; 79 | -------------------------------------------------------------------------------- /src/renderer/components/ui/ShinyText/ShinyText.css: -------------------------------------------------------------------------------- 1 | .shiny-text { 2 | color: #4f4f4fa4; /* Adjust this color to change intensity/style */ 3 | background: linear-gradient( 4 | 120deg, 5 | rgba(136, 136, 136, 0) 40%, 6 | rgba(0, 0, 0, 0.8) 50%, 7 | rgba(255, 255, 255, 0) 60% 8 | ); 9 | background-size: 200% 100%; 10 | -webkit-background-clip: text; 11 | background-clip: text; 12 | animation: shine 5s linear infinite; 13 | } 14 | 15 | @keyframes shine { 16 | 0% { 17 | background-position: 100%; 18 | } 19 | 100% { 20 | background-position: -100%; 21 | } 22 | } 23 | 24 | .shiny-text.disabled { 25 | animation: none; 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/components/ui/ShinyText/ShinyText.tsx: -------------------------------------------------------------------------------- 1 | import './ShinyText.css'; 2 | 3 | const ShinyText = ({ text, disabled = false, speed = 5, className = '' }) => { 4 | const animationDuration = `${speed}s`; 5 | 6 | return ( 7 |
11 | {text} 12 |
13 | ); 14 | }; 15 | 16 | export default ShinyText; 17 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | AimeBox 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from './App'; 3 | 4 | const container = document.getElementById('root') as HTMLElement; 5 | const root = createRoot(container); 6 | root.render(); 7 | -------------------------------------------------------------------------------- /src/renderer/pages/Agent/AgentConfigDrawer.tsx: -------------------------------------------------------------------------------- 1 | import { ChatOptions } from '@/entity/Chat'; 2 | import { ToolInfo } from '@/main/tools'; 3 | import { ScrollArea } from '@/renderer/components/ui/scroll-area'; 4 | import { 5 | Button, 6 | ConfigProvider, 7 | Divider, 8 | Drawer, 9 | DrawerProps, 10 | Form, 11 | Input, 12 | message, 13 | Slider, 14 | Switch, 15 | Tabs, 16 | TabsProps, 17 | Tag, 18 | } from 'antd'; 19 | import { t } from 'i18next'; 20 | import { useContext, useEffect, useMemo, useState } from 'react'; 21 | import { FaBook, FaTools, FaTrash, FaTrashAlt } from 'react-icons/fa'; 22 | import { GlobalContext } from '@/renderer/context/GlobalContext'; 23 | import BasicForm from '@/renderer/components/form/BasicForm'; 24 | import { FormSchema } from '@/types/form'; 25 | import { AgentInfo } from '@/main/agents'; 26 | 27 | export interface AgentConfigDrawerProps extends DrawerProps { 28 | value?: AgentInfo; 29 | 30 | onChange?: (value: Record) => void; 31 | } 32 | 33 | export default function AgentConfigDrawer(props: AgentConfigDrawerProps) { 34 | const { value, onChange } = props; 35 | const [currentTab, setCurrentTab] = useState('base'); 36 | 37 | const [insideValue, setInsideValue] = useState(value); 38 | 39 | const items: TabsProps['items'] = useMemo( 40 | () => [ 41 | { 42 | key: 'base', 43 | label: t('agent.base_config'), 44 | }, 45 | ], 46 | [], 47 | ); 48 | 49 | // const onChange = (value: Record) => { 50 | // //console.log(value); 51 | // props?.onChange?.(value); 52 | // }; 53 | 54 | const onFinish = async (config: Record) => { 55 | const data = { ...value, config: config }; 56 | try { 57 | await window.electron.agents.update(data); 58 | onChange?.(value); 59 | } catch (err) { 60 | message.error(err.message); 61 | } 62 | }; 63 | 64 | useEffect(() => {}, []); 65 | 66 | useEffect(() => { 67 | setInsideValue(value); 68 | }, [value]); 69 | return ( 70 | 77 | 85 |
86 |
87 | 92 |
93 | 94 | {currentTab == 'base' && ( 95 |
96 | 102 |
103 | )} 104 | {currentTab == 'pre_prompt' &&
} 105 |
106 |
107 |
108 |
109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /src/renderer/pages/Agent/AgentPage.tsx: -------------------------------------------------------------------------------- 1 | import List from '@/renderer/components/common/List'; 2 | import { Route, Routes } from 'react-router-dom'; 3 | import Content from '@/renderer/components/layout/Content'; 4 | import AgentContent from './AgentContent'; 5 | import { Button } from 'antd'; 6 | import { ListItem } from '@/renderer/components/common/ListItem'; 7 | import { useState } from 'react'; 8 | import { t } from 'i18next'; 9 | 10 | export default function AgentPage() { 11 | const [categories, setCategories] = useState([ 12 | // { key: 'custom', name: t('custom') }, 13 | { key: 'all', name: t('common.all') }, 14 | ]); 15 | 16 | const [selectedCategory, setSelectedCategory] = useState('all'); 17 | 18 | return ( 19 | 20 |
21 | 添加分类} 27 | > 28 |
29 | {categories.map((category) => ( 30 | setSelectedCategory(category.key)} 35 | /> 36 | ))} 37 |
38 |
39 | 40 |
41 | 42 | } /> 43 | 44 |
45 |
46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/renderer/pages/Chat/ChatPage.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | import { 3 | Link, 4 | Route, 5 | Routes, 6 | useLocation, 7 | useNavigate, 8 | } from 'react-router-dom'; 9 | import { Button, Input, message, Splitter } from 'antd'; 10 | import { FaEdit, FaFile, FaPlus, FaSmile } from 'react-icons/fa'; 11 | 12 | import List from '@/renderer/components/common/List'; 13 | import FormModal from '@/renderer/components/modals/FormModal'; 14 | import { isArray, isBoolean, isNumber, isString } from '@/main/utils/is'; 15 | import { FormSchema } from '@/types/form'; 16 | 17 | import BasicForm from '@/renderer/components/form/BasicForm'; 18 | 19 | import { ScrollArea } from '@/renderer/components/ui/scroll-area'; 20 | import { t } from 'i18next'; 21 | import { FaSeedling, FaTowerObservation } from 'react-icons/fa6'; 22 | import ChatList, { ChatListRef } from '@/renderer/components/chat/ChatList'; 23 | import ChatContent from './ChatContent'; 24 | import Content from '@/renderer/components/layout/Content'; 25 | import { ChatMode } from '@/types/chat'; 26 | import { Chat } from '@/entity/Chat'; 27 | import ChatFileContent from './ChatFileContent'; 28 | import ChatPlannerContent from './ChatPlannerContent'; 29 | 30 | export default function ChatPage() { 31 | const chatListRef = useRef(null); 32 | const [mode, setMode] = useState(null); 33 | const navigate = useNavigate(); 34 | const location = useLocation(); 35 | useEffect(() => { 36 | // ?mode=default 37 | if (location.search) { 38 | const s = {}; 39 | location.search 40 | .substring(1) 41 | .split('&') 42 | .forEach((item) => { 43 | const [key, value] = item.split('='); 44 | s[key] = value; 45 | }); 46 | const _mode = s['mode']; 47 | setMode(_mode); 48 | } 49 | }, [location.search]); 50 | 51 | const onNewChat = async (mode: ChatMode) => { 52 | const chat = await window.electron.chat.create(mode); 53 | if (chatListRef.current) { 54 | chatListRef.current.getData(true); 55 | } 56 | if (chat) { 57 | navigate(`/chat/${chat.id}?mode=${mode}`); 58 | } 59 | }; 60 | return ( 61 | 62 |
63 | 64 | 65 |
66 | 67 | {(mode == 'default' || mode == 'agent') && ( 68 | } /> 69 | )} 70 | {mode == 'file' && } />} 71 | {mode == 'planner' && ( 72 | } /> 73 | )} 74 | 75 |
76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/renderer/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import ShowcaseLayout from '../components/layout/ShowcaseLayout'; 3 | import { ScrollArea } from '../components/ui/scroll-area'; 4 | import Content from '../components/layout/Content'; 5 | import { Editor } from '../components/common/Editor'; 6 | import { Document, Page, pdfjs } from 'react-pdf'; 7 | import FileDropZone from '../components/common/FileDropZone'; 8 | 9 | interface Props {} 10 | 11 | function Home(props: Props): React.ReactElement { 12 | return ( 13 | 14 | 15 | {/* */} 16 | {/* */} 17 | 18 |

Welcome to the Home Page

19 |
20 |
21 |
22 | ); 23 | } 24 | 25 | export default Home; 26 | -------------------------------------------------------------------------------- /src/renderer/pages/Settings/AboutPage.tsx: -------------------------------------------------------------------------------- 1 | import { Collapse, Input, Menu, Select } from 'antd'; 2 | import { t } from 'i18next'; 3 | import React, { useEffect, useState } from 'react'; 4 | import { FaInfoCircle } from 'react-icons/fa'; 5 | import { useTheme } from '../../components/theme/ThemeProvider'; 6 | import { GlobalSettings } from '@/main/settings'; 7 | import { isUrl } from '@/main/utils/is'; 8 | import i18n from '@/i18n'; 9 | 10 | export default function AboutPage() { 11 | const [appInfo, setAppInfo] = useState({}); 12 | useEffect(() => { 13 | setAppInfo(window.electron.app.info()); 14 | }, []); 15 | return ( 16 | <> 17 |
18 |

{t('settings.about')}

19 |
20 |
21 |
22 |
Aime Box
23 |
24 | Aime Box is a tool agent box for producer. 25 |
26 | 33 | {Object.keys(appInfo).map((key) => ( 34 |
35 | {key}: {appInfo[key]?.toString()} 36 |
37 | ))} 38 | 39 | ), 40 | }, 41 | ]} 42 | /> 43 |
44 |
45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/renderer/preload.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronHandler } from '../main/preload'; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-unused-vars 5 | interface Window { 6 | electron: ElectronHandler; 7 | } 8 | } 9 | 10 | export {}; 11 | -------------------------------------------------------------------------------- /src/renderer/store/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import { providerSlice } from './providerSlice'; 3 | import { settingsSlice } from './settingsSlice'; 4 | import { Providers } from '@/entity/Providers'; 5 | import { GlobalSettings } from '@/main/settings'; 6 | 7 | export interface State { 8 | provider: { providers: Providers[] }; 9 | settings: { settings: GlobalSettings }; 10 | } 11 | 12 | export default configureStore({ 13 | reducer: { 14 | provider: providerSlice.reducer, 15 | settings: settingsSlice.reducer, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /src/renderer/store/providerSlice.ts: -------------------------------------------------------------------------------- 1 | import { Providers } from '@/entity/Providers'; 2 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 3 | 4 | export const fetchProviders = createAsyncThunk( 5 | 'provider/fetchProviders', 6 | async () => { 7 | try { 8 | const providers = await window.electron.providers.getList(); 9 | return providers; 10 | } catch (err) { 11 | console.error(err); 12 | return [] as Providers[]; 13 | } 14 | }, 15 | ); 16 | 17 | const initialState = { 18 | providers: [] as Providers[], 19 | status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' 20 | error: null as string | null, 21 | }; 22 | 23 | export const providerSlice = createSlice({ 24 | name: 'provider', 25 | initialState, 26 | reducers: { 27 | // 移除了同步的getProviders 28 | }, 29 | extraReducers: (builder) => { 30 | builder 31 | .addCase(fetchProviders.pending, (state) => { 32 | state.status = 'loading'; 33 | }) 34 | .addCase(fetchProviders.fulfilled, (state, action) => { 35 | state.status = 'succeeded'; 36 | state.providers = action.payload; 37 | }) 38 | .addCase(fetchProviders.rejected, (state, action) => { 39 | state.status = 'failed'; 40 | state.error = action.error.message || '获取提供者列表失败'; 41 | }); 42 | }, 43 | }); 44 | 45 | // 不再导出同步action 46 | // export const { } = providerSlice.actions; 47 | 48 | // 导出 reducer 49 | export default providerSlice.reducer; 50 | -------------------------------------------------------------------------------- /src/renderer/store/settingsSlice.ts: -------------------------------------------------------------------------------- 1 | import i18n from '@/i18n'; 2 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; 3 | 4 | const initialState = { 5 | settings: null, 6 | }; 7 | 8 | export const settingsSlice = createSlice({ 9 | name: 'settings', 10 | initialState, 11 | reducers: { 12 | setSettings: (state, action) => { 13 | state.settings = action.payload; 14 | i18n.changeLanguage(state.settings.language || 'zh-CN'); 15 | }, 16 | }, 17 | }); 18 | 19 | // 导出 action creators 20 | export const { setSettings } = settingsSlice.actions; 21 | 22 | // 导出 reducer 23 | export default settingsSlice.reducer; 24 | -------------------------------------------------------------------------------- /src/renderer/styles/globals.css: -------------------------------------------------------------------------------- 1 | @layer base { 2 | :root { 3 | --background: 0 0% 100%; 4 | --foreground: 222.2 47.4% 11.2%; 5 | 6 | --muted: 210 40% 96.1%; 7 | --muted-foreground: 215.4 16.3% 46.9%; 8 | 9 | --popover: 0 0% 100%; 10 | --popover-foreground: 222.2 47.4% 11.2%; 11 | 12 | --border: 214.3 31.8% 91.4%; 13 | --input: 214.3 31.8% 91.4%; 14 | 15 | --card: 0 0% 100%; 16 | --card-foreground: 222.2 47.4% 11.2%; 17 | 18 | --primary: 222.2 47.4% 11.2%; 19 | --primary-foreground: 210 40% 98%; 20 | 21 | --secondary: 210 40% 96.1%; 22 | --secondary-foreground: 222.2 47.4% 11.2%; 23 | 24 | --accent: 210 40% 96.1%; 25 | --accent-foreground: 222.2 47.4% 11.2%; 26 | 27 | --destructive: 0 100% 50%; 28 | --destructive-foreground: 210 40% 98%; 29 | 30 | --ring: 215 20.2% 65.1%; 31 | 32 | --radius: 0.5rem; 33 | } 34 | 35 | .dark { 36 | --background: 224 71% 4%; 37 | --foreground: 213 31% 91%; 38 | 39 | --muted: 223 47% 11%; 40 | --muted-foreground: 215.4 16.3% 56.9%; 41 | 42 | --accent: 216 34% 17%; 43 | --accent-foreground: 210 40% 98%; 44 | 45 | --popover: 224 71% 4%; 46 | --popover-foreground: 215 20.2% 65.1%; 47 | 48 | --border: 216 34% 17%; 49 | --input: 216 34% 17%; 50 | 51 | --card: 224 71% 4%; 52 | --card-foreground: 213 31% 91%; 53 | 54 | --primary: 210 40% 98%; 55 | --primary-foreground: 222.2 47.4% 1.2%; 56 | 57 | --secondary: 222.2 47.4% 11.2%; 58 | --secondary-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 63% 31%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --ring: 216 34% 17%; 64 | 65 | --radius: 0.5rem; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | 74 | body { 75 | @apply bg-background text-foreground; 76 | font-feature-settings: "rlig" 1, "calt" 1; 77 | 78 | } 79 | } -------------------------------------------------------------------------------- /src/types/agent.ts: -------------------------------------------------------------------------------- 1 | export interface CreateA2A { 2 | apiBase: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/types/chat.ts: -------------------------------------------------------------------------------- 1 | export interface ChatInputAttachment { 2 | path: string; 3 | name: string; 4 | type: 'file' | 'folder'; 5 | ext: string | undefined; 6 | } 7 | 8 | export interface ChatInputExtend { 9 | attachments: ChatInputAttachment[]; 10 | } 11 | 12 | export type ChatMode = 'default' | 'planner' | 'agent' | 'file'; 13 | -------------------------------------------------------------------------------- /src/types/formItem.ts: -------------------------------------------------------------------------------- 1 | import { ColProps } from 'antd'; 2 | import { NamePath } from 'antd/es/form/interface'; 3 | import { HTMLAttributes } from 'react'; 4 | 5 | export interface FormItem { 6 | /** 7 | * Used with label, whether to display : after label text. 8 | * @default true 9 | * @type boolean 10 | */ 11 | colon?: boolean; 12 | 13 | /** 14 | * The extra prompt message. It is similar to help. Usage example: to display error message and prompt message at the same time. 15 | * @type any (string | slot) 16 | */ 17 | extra?: string | JSX.Element; 18 | 19 | /** 20 | * Used with validateStatus, this option specifies the validation status icon. Recommended to be used only with Input. 21 | * @default false 22 | * @type boolean 23 | */ 24 | hasFeedback?: boolean; 25 | 26 | /** 27 | * The prompt message. If not provided, the prompt message will be generated by the validation rule. 28 | * @type any (string | slot) 29 | */ 30 | help?: string | JSX.Element; 31 | 32 | /** 33 | * Label test 34 | * @type any (string | slot) 35 | */ 36 | label?: string | JSX.Element; 37 | 38 | /** 39 | * The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with 40 | * @type Col 41 | */ 42 | labelCol?: ColProps; 43 | 44 | /** 45 | * Whether provided or not, it will be generated by the validation rule. 46 | * @default false 47 | * @type boolean 48 | */ 49 | required?: boolean; 50 | 51 | /** 52 | * The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating' 53 | * @type string 54 | */ 55 | validateStatus?: '' | 'success' | 'warning' | 'error' | 'validating'; 56 | 57 | /** 58 | * The layout for input controls, same as labelCol 59 | * @type Col 60 | */ 61 | wrapperCol?: ColProps; 62 | /** 63 | * Set sub label htmlFor. 64 | */ 65 | htmlFor?: string; 66 | /** 67 | * text align of label 68 | */ 69 | labelAlign?: 'left' | 'right'; 70 | /** 71 | * a key of model. In the setting of validate and resetFields method, the attribute is required 72 | */ 73 | name?: NamePath; 74 | /** 75 | * validation rules of form 76 | */ 77 | rules?: object | object[]; 78 | /** 79 | * Whether to automatically associate form fields. In most cases, you can setting automatic association. 80 | * If the conditions for automatic association are not met, you can manually associate them. See the notes below. 81 | */ 82 | autoLink?: boolean; 83 | /** 84 | * Whether stop validate on first rule of error for this field. 85 | */ 86 | validateFirst?: boolean; 87 | /** 88 | * When to validate the value of children node 89 | */ 90 | validateTrigger?: string | string[] | false; 91 | } 92 | -------------------------------------------------------------------------------- /src/types/notification.ts: -------------------------------------------------------------------------------- 1 | export interface NotificationMessage { 2 | id: string; 3 | type: 'notification' | 'message' | 'progress'; 4 | title: string; 5 | description: string; 6 | percent: number | undefined; 7 | duration: number | undefined; 8 | closeEnable: boolean; 9 | icon: string | 'success' | 'error' | 'info' | 'warning' | 'loading'; 10 | error: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/types/webSearchEngine.ts: -------------------------------------------------------------------------------- 1 | export enum WebSearchEngine { 2 | ZhuPu = 'zhipu', 3 | DuckDuckGo = 'duckduckgo', 4 | Searxng = 'searxng', 5 | Tavily = 'tavily', 6 | Serpapi = 'serpapi', 7 | Brave = 'brave', 8 | } 9 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | import Typography from '@tailwindcss/typography'; 2 | import TailwindcssAnimate from 'tailwindcss-animate'; 3 | import { fontFamily } from 'tailwindcss/defaultTheme'; 4 | 5 | module.exports = { 6 | darkMode: 'class', 7 | content: ['./src/**/*.{html,js,jsx,ts,tsx}'], 8 | theme: { 9 | container: { 10 | center: true, 11 | padding: '2rem', 12 | screens: { 13 | '2xl': '1400px', 14 | }, 15 | }, 16 | extend: { 17 | typography: { 18 | DEFAULT: { 19 | css: { 20 | 'table th': { 21 | 'white-space': 'nowrap', 22 | }, 23 | 'table td': { 24 | overflow: 'hidden', 25 | 'text-overflow': 'ellipsis', 26 | 'white-space': 'nowrap', 27 | // display: '-webkit-box;', 28 | // '-webkit-box-orient': 'vertical', 29 | // '-webkit-line-clamp': 5, 30 | }, 31 | }, 32 | }, 33 | }, 34 | colors: { 35 | border: 'hsl(var(--border))', 36 | input: 'hsl(var(--input))', 37 | ring: 'hsl(var(--ring))', 38 | background: 'hsl(var(--background))', 39 | foreground: 'hsl(var(--foreground))', 40 | primary: { 41 | DEFAULT: 'hsl(var(--primary))', 42 | foreground: 'hsl(var(--primary-foreground))', 43 | }, 44 | secondary: { 45 | DEFAULT: 'hsl(var(--secondary))', 46 | foreground: 'hsl(var(--secondary-foreground))', 47 | }, 48 | destructive: { 49 | DEFAULT: 'hsl(var(--destructive))', 50 | foreground: 'hsl(var(--destructive-foreground))', 51 | }, 52 | muted: { 53 | DEFAULT: 'hsl(var(--muted))', 54 | foreground: 'hsl(var(--muted-foreground))', 55 | }, 56 | accent: { 57 | DEFAULT: 'hsl(var(--accent))', 58 | foreground: 'hsl(var(--accent-foreground))', 59 | }, 60 | popover: { 61 | DEFAULT: 'hsl(var(--popover))', 62 | foreground: 'hsl(var(--popover-foreground))', 63 | }, 64 | card: { 65 | DEFAULT: 'hsl(var(--card))', 66 | foreground: 'hsl(var(--card-foreground))', 67 | }, 68 | }, 69 | borderRadius: { 70 | lg: `var(--radius)`, 71 | md: `calc(var(--radius) - 2px)`, 72 | sm: 'calc(var(--radius) - 4px)', 73 | }, 74 | fontFamily: { 75 | sans: ['var(--font-sans)', ...fontFamily.sans], 76 | }, 77 | keyframes: { 78 | 'accordion-down': { 79 | from: { height: '0' }, 80 | to: { height: 'var(--radix-accordion-content-height)' }, 81 | }, 82 | 'accordion-up': { 83 | from: { height: 'var(--radix-accordion-content-height)' }, 84 | to: { height: '0' }, 85 | }, 86 | }, 87 | animation: { 88 | 'accordion-down': 'accordion-down 0.2s ease-out', 89 | 'accordion-up': 'accordion-up 0.2s ease-out', 90 | }, 91 | }, 92 | }, 93 | plugins: [Typography, TailwindcssAnimate], 94 | corePlugins: {}, 95 | }; 96 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2022", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2022"], 7 | "jsx": "react-jsx", 8 | "strict": false, 9 | "sourceMap": true, 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "resolveJsonModule": true, 14 | "allowJs": true, 15 | "outDir": ".erb/dll", 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./src/*"] 19 | }, 20 | "emitDecoratorMetadata": true, 21 | "experimentalDecorators": true, 22 | "allowImportingTsExtensions": true, 23 | "noEmit": true 24 | }, 25 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"] 26 | } 27 | --------------------------------------------------------------------------------