├── .env.example ├── .gitignore ├── .trae └── rules │ └── project_rules.md ├── API_README.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTING_CN.md ├── LICENSE ├── Makefile ├── README.md ├── README_CN.md ├── WEB_README.md ├── bin └── www ├── browser_server ├── browser_use │ ├── browser │ │ ├── __pycache__ │ │ │ ├── browser_agent.cpython-311.pyc │ │ │ └── browser_agent.cpython-312.pyc │ │ └── browser_agent.py │ ├── server.py │ └── utils │ │ └── img.py ├── data │ ├── agentTest.py │ ├── agent_screenshot.py │ └── log │ │ ├── ex.json │ │ ├── messages copy.txt │ │ └── messages.txt ├── pyproject.toml ├── readme.md └── requirements.txt ├── containers ├── app │ └── Dockerfile └── runtime │ └── Dockerfile ├── docker-compose.yml ├── frontend ├── .env.example ├── .gitignore ├── README.md ├── deploy.sh ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── src │ ├── App.vue │ ├── assets │ │ ├── experience │ │ │ ├── action.svg │ │ │ ├── camera.svg │ │ │ ├── edit.svg │ │ │ ├── left.svg │ │ │ ├── plan.svg │ │ │ └── updown.svg │ │ ├── fileClass │ │ │ ├── circle.svg │ │ │ ├── click.svg │ │ │ ├── code.svg │ │ │ ├── download.svg │ │ │ ├── file.svg │ │ │ ├── googleDriver.svg │ │ │ ├── locale.svg │ │ │ ├── ppt.svg │ │ │ ├── txt.svg │ │ │ ├── yes.svg │ │ │ └── zip.svg │ │ ├── filePreview │ │ │ ├── close.svg │ │ │ ├── code.svg │ │ │ ├── copy.svg │ │ │ ├── download.svg │ │ │ ├── eye.svg │ │ │ ├── left.svg │ │ │ ├── maxmize.svg │ │ │ ├── minmize.svg │ │ │ ├── moreOptions.svg │ │ │ ├── pdfExport.svg │ │ │ └── right.svg │ │ ├── image │ │ │ └── lemon.jpg │ │ ├── logout.svg │ │ ├── message │ │ │ ├── bash.svg │ │ │ ├── browse.svg │ │ │ ├── collapse.svg │ │ │ ├── deleteFile.svg │ │ │ ├── edit.svg │ │ │ ├── failure.svg │ │ │ ├── file.svg │ │ │ ├── stop.svg │ │ │ ├── success.svg │ │ │ └── think.svg │ │ ├── sidebar │ │ │ └── user.svg │ │ ├── svg │ │ │ ├── add.svg │ │ │ ├── chat.svg │ │ │ ├── collect.svg │ │ │ ├── collected.svg │ │ │ ├── command.svg │ │ │ ├── delete.svg │ │ │ ├── down.svg │ │ │ ├── download.svg │ │ │ ├── edit.svg │ │ │ ├── google.svg │ │ │ ├── leftList.svg │ │ │ ├── logo.svg │ │ │ ├── menuSearch.svg │ │ │ ├── menuSwitch.svg │ │ │ ├── more.svg │ │ │ ├── searchFile.svg │ │ │ ├── share.svg │ │ │ └── vscode.svg │ │ └── vue.svg │ ├── components │ │ ├── MessageFileList │ │ │ └── index.vue │ │ ├── Navbar.vue │ │ ├── browser │ │ │ ├── image.vue │ │ │ ├── searchResults.vue │ │ │ └── video.vue │ │ ├── defaultModel │ │ │ ├── TopicNaming.vue │ │ │ ├── assistantSetting.vue │ │ │ ├── index.vue │ │ │ ├── selectModel.vue │ │ │ └── translation.vue │ │ ├── file │ │ │ └── index.vue │ │ ├── fileClass │ │ │ └── fileSvg.vue │ │ ├── lang │ │ │ └── index.vue │ │ ├── markdown │ │ │ ├── index.vue │ │ │ ├── markdown-it-markmap.js │ │ │ ├── markdown-it-mermaid.js │ │ │ ├── markdown-it-prism.js │ │ │ └── markdown-it-think.js │ │ ├── mcpServer │ │ │ └── index.vue │ │ ├── platforms │ │ │ ├── addPlatform.vue │ │ │ ├── modelinfo.vue │ │ │ ├── modelsList.vue │ │ │ └── settingPlatform.vue │ │ ├── preview │ │ │ ├── fileClass.vue │ │ │ ├── fullPreview.vue │ │ │ └── index.vue │ │ ├── searchEngine │ │ │ └── index.vue │ │ ├── terminal │ │ │ ├── index.vue │ │ │ ├── index.vue.bak │ │ │ └── siderTerminal.vue │ │ └── vscode │ │ │ ├── fileTree.vue │ │ │ └── index.vue │ ├── locals │ │ ├── index.js │ │ ├── lang │ │ │ ├── de.js │ │ │ ├── en.js │ │ │ ├── es.js │ │ │ ├── fr.js │ │ │ ├── ja.js │ │ │ ├── kr.js │ │ │ ├── pt.js │ │ │ ├── tr.js │ │ │ ├── tw.js │ │ │ ├── vi.js │ │ │ └── zh.js │ │ └── utils │ │ │ └── index.js │ ├── main.js │ ├── router │ │ └── index.js │ ├── services │ │ ├── chat.js │ │ ├── default-model-setting.js │ │ ├── experience.js │ │ ├── files.js │ │ ├── message.js │ │ ├── platforms.js │ │ ├── resume.js │ │ ├── search-engine.js │ │ ├── see-agent.js │ │ ├── setting.js │ │ ├── sse.js │ │ ├── workspace.js │ │ └── wx-client.js │ ├── store │ │ ├── index.js │ │ └── modules │ │ │ ├── chat.js │ │ │ └── demo.js │ ├── style.scss │ ├── utils │ │ ├── base64.js │ │ ├── emitter.js │ │ ├── http.js │ │ ├── markdown.js │ │ ├── time.js │ │ └── viewList.js │ └── view │ │ ├── demo │ │ ├── HelloWorld.vue │ │ └── index.vue │ │ ├── lemon │ │ ├── components │ │ │ ├── ChatHeader.vue │ │ │ ├── ChatInput.vue │ │ │ ├── ChatMessages.vue │ │ │ ├── ChatPanel.vue │ │ │ ├── ConfirmPrompt.vue │ │ │ ├── ConversationList.vue │ │ │ ├── LoadingDots.vue │ │ │ ├── Sample.vue │ │ │ ├── Sidebar.vue │ │ │ ├── Suggestion.vue │ │ │ ├── SuggestionCard.vue │ │ │ └── Welcome.vue │ │ ├── index.vue │ │ ├── message │ │ │ ├── Action.vue │ │ │ ├── Message.vue │ │ │ ├── Observation.vue │ │ │ ├── Planing.vue │ │ │ └── index.vue │ │ └── sidebar │ │ │ ├── Footer.vue │ │ │ ├── Header.vue │ │ │ └── index.vue │ │ ├── setting │ │ ├── ContentSide.vue │ │ ├── MenuSide.vue │ │ ├── basic.vue │ │ ├── default-model.vue │ │ ├── defaultModelSetting.vue │ │ ├── experience.vue │ │ ├── index.vue │ │ ├── mcp.vue │ │ ├── model.vue │ │ └── search.vue │ │ └── share │ │ └── index.vue └── vite.config.js ├── jsconfig.json ├── nodemon.json ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── public ├── default_data │ ├── default_experience.json │ ├── default_platform.json │ └── default_search_provider.json ├── img │ ├── Lemon_logo.png │ ├── example.png │ └── qr_code.jpg └── schemas │ ├── conversation.json │ ├── default_model_setting.json │ ├── experience.json │ ├── file.json │ ├── model.json │ ├── model_enable.json │ ├── platform.json │ ├── provider.json │ └── provider_setting_result.json ├── src ├── agent │ ├── AgenticAgent.js │ ├── AgenticAgent.run.js │ ├── TaskManager.js │ ├── code-act │ │ ├── code-act.js │ │ ├── code-act.run.js │ │ ├── thinking.js │ │ └── thinking.prompt.js │ ├── memory │ │ ├── BaseMemory.js │ │ ├── LocalMemory.js │ │ └── index.js │ ├── planning │ │ ├── index.js │ │ ├── index.test.js │ │ └── plan.run.js │ ├── prompt │ │ ├── auto_reply.js │ │ ├── generate_result.js │ │ ├── generate_title.js │ │ ├── generate_todo.js │ │ ├── index.js │ │ ├── plan.js │ │ ├── plan.test.js │ │ ├── system.md │ │ ├── tool.js │ │ └── tool.test.js │ ├── reflection │ │ ├── index.js │ │ └── llm.evaluate.js │ └── tools │ │ ├── browser.js │ │ ├── browser_use.js │ │ ├── index.js │ │ ├── read_file.js │ │ ├── terminal_run.js │ │ ├── web_search.js │ │ └── write_code.js ├── app.js ├── completion │ ├── README.md │ ├── calc.token.js │ ├── configs.js │ ├── handle.error.js │ ├── index.js │ ├── llm.azure.openai.js │ ├── llm.base.js │ ├── llm.config.js │ ├── llm.gemini.js │ ├── llm.one.js │ ├── log.record.js │ └── resolveServiceConfig.js ├── logging │ ├── index.js │ └── logger.js ├── middlewares │ └── wrap.context.js ├── models │ ├── BaseModel.js │ ├── Conversation.js │ ├── DefaultModelSetting.js │ ├── Experience.js │ ├── File.js │ ├── LLMLogs.js │ ├── Message.js │ ├── Model.js │ ├── Platform.js │ ├── SearchProvider.js │ ├── Task.js │ ├── UserProviderConfig.js │ ├── UserSearchSetting.js │ ├── index.js │ └── sync.js ├── routers │ ├── agent │ │ ├── README.md │ │ ├── index.js │ │ └── run.js │ ├── conversation │ │ ├── conversation.js │ │ ├── favorite.js │ │ └── index.js │ ├── default_model_setting │ │ ├── default_model_setting.js │ │ └── index.js │ ├── experience │ │ ├── experience.js │ │ └── index.js │ ├── file │ │ ├── file.js │ │ └── index.js │ ├── index.js │ ├── message │ │ ├── index.js │ │ └── message.js │ ├── model │ │ ├── index.js │ │ └── model.js │ ├── platform │ │ ├── index.js │ │ └── platform.js │ ├── runtime │ │ ├── index.js │ │ └── runtime.js │ └── search_provider_setting │ │ ├── index.js │ │ └── setting.js ├── runtime │ ├── DockerRuntime.d.ts │ ├── DockerRuntime.js │ ├── LocalRuntime.js │ ├── LocalRuntime.test.js │ ├── action_execution_server.js │ ├── browser.js │ ├── plugins │ │ ├── browser │ │ │ └── index.js │ │ └── vscode │ │ │ ├── index.js │ │ │ └── settings.json │ ├── read_file.js │ ├── runtime.util.js │ ├── terminal_run.js │ └── utils │ │ ├── system.js │ │ └── tools.js ├── swagger │ └── swagger.js ├── tools │ ├── WebSearch.js │ ├── browser.js │ ├── impl │ │ └── web_search │ │ │ ├── LocalSearch.js │ │ │ ├── LocalSearch.run.js │ │ │ ├── TalivySearch.js │ │ │ └── TalivySearch.run.js │ ├── index.js │ ├── read_file.js │ ├── terminal_run.js │ └── write_code.js └── utils │ ├── default_model.js │ ├── function.call.js │ ├── json.js │ ├── jwt.js │ ├── llm.js │ ├── message.js │ ├── network.js │ ├── planning.js │ ├── resolve.js │ ├── stream.util.js │ ├── thinking.js │ └── validate.js ├── test └── api │ └── platform │ └── platform.test.js └── types ├── LocalRuntime.d.ts └── Tool.d.ts /.env.example: -------------------------------------------------------------------------------- 1 | STORAGE_PATH=data/database.sqlite 2 | WORKSPACE_DIR=workspace 3 | RUNTIME_TYPE=docker 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | data/* 3 | public/files/* 4 | .env 5 | *.local 6 | llm.json 7 | 8 | .vscode 9 | .DS_Store 10 | 11 | 12 | cache/* 13 | uploads/* 14 | scripts/* 15 | 16 | # agent workspace directory 17 | workspace/** 18 | !workspace 19 | 20 | src/cache 21 | 22 | #jetbrains 23 | .idea 24 | 25 | # complied files 26 | out 27 | .vite -------------------------------------------------------------------------------- /.trae/rules/project_rules.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexdocom/lemonai/2505c058c969da010d9ab480f7699326c20446bb/.trae/rules/project_rules.md -------------------------------------------------------------------------------- /API_README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Backend Configuration File 3 | 4 | The backend configuration file is located at the root of the project.envFile. You can refer.env.exampleFile to learn about the available configuration items and their examples. 5 | 6 | #### Configuration Item Description 7 | 8 | * **STORAGE\_PATH** 9 | * Description: Defines where SQLite database files are stored. 10 | * Default:data/database.sqlite 11 | * Example: 12 | 13 | STORAGE\_PATH=data/database.sqlite\ 14 | 15 | 16 | * 17 | * Note: If not configured, the system will use the defaultdata/database.sqliteAs the storage path for the database file. 18 | * **WORKSPACE\_DIR** 19 | * Description: Defines the mapping path of the workspace on the local host. 20 | * Example: 21 | 22 | WORKSPACE\_DIR=workspace\ 23 | 24 | 25 | * **RUNTIME\_TYPE** 26 | * Description: Defines how to start the backend service. 27 | * Default:docker(Sandbox Environment required) 28 | * Optional values: 29 | * docker: The default value, which means running in the Docker sandbox environment. 30 | * local: For developers to use when debugging locally. 31 | * Example: 32 | 33 | RUNTIME\_TYPE=docker\ 34 | -------------------------------------------------------------------------------- /CONTRIBUTING_CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: file-lines 3 | --- 4 | 5 | # CONTRIBUTING\_CN 6 | 7 | 非常感谢你考虑为 Lemon 做出贡献!作为一家资源有限的创业公司,社区的每一份贡献对我们来说都弥足珍贵。 8 | 9 | 我们需要保持敏捷和快速迭代,同时也希望确保贡献者能获得尽可能流畅的参与体验。这份贡献指南旨在帮助你熟悉代码库和我们的工作方式,让你可以尽快进入有趣的开发环节。 10 | 11 | 本指南和 Lemon 一样在不断完善中。如果有任何滞后于项目实际情况的地方,恳请谅解,我们也欢迎任何改进建议。 12 | 13 | 关于许可证,请花一分钟阅读我们简短的许可和贡献者协议。社区同时也遵循[行为准则](https://github.com/hexdocom/lemon/blob/main/CODE_OF_CONDUCT.md)。 14 | 15 | 加入我们,一起贡献,共同打造精彩项目!💡✨ 16 | 17 | 请记得在 PR 描述中关联现有 issue 或创建新的 issue。 18 | 19 | ### Bug 报告 20 | 21 | > \[!IMPORTANT] 22 | > \ 23 | > 提交 bug 报告时请务必包含以下信息: 24 | 25 | * 清晰描述性的标题 26 | * 详细的 bug 描述,包括任何错误信息 27 | * 复现步骤 28 | * 预期行为 29 | * 截图或视频(如果适用) 30 | 31 | 优先级划分: 32 | 33 | | 问题类型 | 优先级 | 34 | | ------------------------------ | ----- | 35 | | 核心功能 bug(云服务、登录失败、应用无法使用、安全漏洞) | 紧急 | 36 | | 非关键 bug、性能优化 | 中等优先级 | 37 | | 小修复(拼写错误、界面混乱但可用) | 低优先级 | 38 | 39 | ### 功能请求 40 | 41 | > \[!NOTE] 42 | > \ 43 | > 提交功能请求时请务必包含以下信息: 44 | 45 | * 清晰描述性的标题 46 | * 详细的功能描述 47 | * 功能使用场景 48 | * 其他相关上下文或截图 49 | 50 | ## 提交 PR 51 | 52 | ### 项目设置 53 | 54 | ### PR 提交流程 55 | 56 | 1. Fork 本仓库 57 | 2. 在提交 PR 之前,请先创建 issue 讨论你想要做的修改 58 | 3. 为你的修改创建一个新的分支 59 | 4. 请为你的修改添加相应的测试 60 | 5. 确保你的代码能通过现有的测试 61 | 6. 请在 PR 描述中关联相关 issue,格式为 `fixes #` 62 | 7. 等待合并! 63 | 64 | #### 前端 65 | 66 | 关于前端服务的设置,请参考 `frontend/README.md` 文件中的[详细指南](https://github.com/hexdocom/lemon/blob/main/frontend/WEB_README.md)。该文档提供了帮助你正确配置前端环境的详细说明。 67 | 68 | #### 后端 69 | 70 | 关于后端服务的设置,请参考 `API_README.md` 文件中的[详细说明](https://github.com/hexdocom/lemon/blob/main/API_README.md)。该文档包含了帮助你顺利运行后端的步骤说明。 71 | 72 | #### 其他注意事项 73 | 74 | 我们建议在开始设置之前仔细阅读本文档,因为它包含以下重要信息: 75 | 76 | * 前置条件和依赖项 77 | * 安装步骤 78 | * 配置细节 79 | * 常见问题解决方案 80 | 81 | 如果在设置过程中遇到任何问题,请随时联系我们。 82 | 83 | ## 获取帮助 84 | 85 | 如果你在贡献过程中遇到困难或有紧急问题,可以通过相关 GitHub issue 向我们提问,进行快速交流。 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Open Source License 2 | 3 | Lemon is licensed under a modified version of the Apache License 2.0, with the following additional conditions: 4 | 5 | 1. Lemon may be utilized commercially, including as a backend service for other applications or as an application development platform for enterprises. Should the conditions below be met, a commercial license must be obtained from the producer: 6 | 7 | a. Multi-tenant service: Unless explicitly authorized by Lemon in writing, you may not use the Lemon source code to operate a multi-tenant environment. 8 | - Tenant Definition: Within the context of Lemon, one tenant corresponds to one workspace. The workspace provides a separated area for each tenant's data and configurations. 9 | 10 | b. LOGO and copyright information: In the process of using Lemon's frontend, you may not remove or moLemon the LOGO or copyright information in the Lemon console or applications. This restriction is inapplicable to uses of Lemon that do not involve its frontend. 11 | - Frontend Definition: For the purposes of this license, the "frontend" of Lemon includes all components located in the `web/` directory when running Lemon from the raw source code, or the "web" image when running Lemon with Docker. 12 | 13 | 2. As a contributor, you should agree that: 14 | 15 | a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary. 16 | b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations. 17 | 18 | Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0. 19 | 20 | The interactive design of this product is protected by appearance patent. 21 | 22 | © 2025 Lemon, Inc. -------------------------------------------------------------------------------- /WEB_README.md: -------------------------------------------------------------------------------- 1 | ### Front End Configuration File 2 | 3 | Front-end configuration files are located infrontend/.envUnder the document. You can referfrontend/.env.exampleFile to learn about the available configuration items and their examples. 4 | 5 | #### Configuration Item Description 6 | 7 | * **VITE\_SERVICE\_URL** 8 | * Description: Defines the start address and port of the backend service. 9 | * Default:http://127.0.0.1:3000 10 | * Example: 11 | 12 | VITE\_SERVICE\_URL=http://127.0.0.1:3000\ 13 | 14 | 15 | * 16 | * Note: Modify the endpoint and port of the backend service. 17 | * **VITE\_PORT** 18 | * Description: Defines the port number of the front-end application. 19 | * Default:5005 20 | * Example: 21 | 22 | VITE\_PORT=5005\ 23 | 24 | 25 | * 26 | * Description: After the front end is started, you can passhttp://localhost:5005Access the Lemon app. 27 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../src/app'); 8 | var debug = require('debug')('demo:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | // app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app.callback()); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /browser_server/browser_use/browser/__pycache__/browser_agent.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexdocom/lemonai/2505c058c969da010d9ab480f7699326c20446bb/browser_server/browser_use/browser/__pycache__/browser_agent.cpython-311.pyc -------------------------------------------------------------------------------- /browser_server/browser_use/browser/__pycache__/browser_agent.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexdocom/lemonai/2505c058c969da010d9ab480f7699326c20446bb/browser_server/browser_use/browser/__pycache__/browser_agent.cpython-312.pyc -------------------------------------------------------------------------------- /browser_server/browser_use/utils/img.py: -------------------------------------------------------------------------------- 1 | """ 2 | Description: These Python modules are designed to capture detailed 3 | browser usage datafor analysis, with both server and client 4 | components working together to record and store the information. 5 | 6 | Author: Carlos A. Planchón 7 | https://github.com/carlosplanchon/ 8 | 9 | Adapt this code to your needs. 10 | 11 | Feedback is appreciated! 12 | """ 13 | 14 | ##################### 15 | # # 16 | # --- UTILS --- # 17 | # # 18 | ##################### 19 | 20 | import base64 21 | 22 | 23 | def b64_to_png(b64_string: str, output_file): 24 | """ 25 | Convert a Base64-encoded string to a PNG file. 26 | 27 | :param b64_string: A string containing Base64-encoded data 28 | :param output_file: The path to the output PNG file 29 | """ 30 | with open(output_file, 'wb') as f: 31 | f.write(base64.b64decode(b64_string)) -------------------------------------------------------------------------------- /browser_server/data/agentTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from langchain_openai import ChatOpenAI 4 | from browser_use import Agent 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | import asyncio 10 | 11 | 12 | # api_key = os.getenv("DEEPSEEK_API_KEY") 13 | llm = ChatOpenAI(model="deepseek-chat",base_url="https://api.deepseek.com/v1") 14 | 15 | async def main(): 16 | agent = Agent( 17 | task="找到当前的日本地图,将其浏览器页面截图并保存到screenshots文件夹,后缀为png", 18 | llm=llm, 19 | use_vision=False, 20 | # save_conversation_path = "logs/conversation" 21 | ) 22 | result = await agent.run() 23 | print(result) 24 | 25 | 26 | asyncio.run(main()) 27 | -------------------------------------------------------------------------------- /browser_server/data/agent_screenshot.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | import base64 4 | from datetime import datetime 5 | import asyncio 6 | from browser_use.browser.browser import Browser, BrowserConfig 7 | 8 | load_dotenv() 9 | 10 | 11 | async def test_take_full_page_screenshot(): 12 | browser = Browser(config=BrowserConfig( 13 | browser_instance_path=r"D:\soft\Google\Chrome\Application\chrome.exe", 14 | headless=False, disable_security=True)) 15 | 16 | async with await browser.new_context() as context: 17 | page = await context.get_current_page() 18 | # Go to a test page 19 | await page.goto('https://www.google.com') 20 | 21 | await asyncio.sleep(3) 22 | # Take full page screenshot 23 | screenshot_b64 = await context.take_screenshot(full_page=True) 24 | await asyncio.sleep(3) 25 | 26 | # 创建screenshots目录(如果不存在) 27 | screenshots_dir = os.path.join(os.path.dirname(__file__), 'screenshots') 28 | os.makedirs(screenshots_dir, exist_ok=True) 29 | 30 | # 生成带时间戳的文件名 31 | timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') 32 | screenshot_path = os.path.join(screenshots_dir, f'screenshot_{timestamp}.png') 33 | 34 | # 将base64字符串解码并保存为图片文件 35 | with open(screenshot_path, 'wb') as f: 36 | f.write(base64.b64decode(screenshot_b64)) 37 | 38 | print(f'Screenshot saved to: {screenshot_path}') 39 | 40 | # Verify screenshot is not empty and is valid base64 41 | assert screenshot_b64 is not None 42 | assert isinstance(screenshot_b64, str) 43 | assert len(screenshot_b64) > 0 44 | 45 | # Test we can decode the base64 string 46 | try: 47 | base64.b64decode(screenshot_b64) 48 | except Exception as e: 49 | print(f'Error decoding base64 string: {e}') 50 | 51 | await browser.close() 52 | 53 | asyncio.run(test_take_full_page_screenshot()) -------------------------------------------------------------------------------- /browser_server/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "browseruse" 3 | version = "0.1.0" 4 | description = "a web server for browser-use" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "browser-use[memory]>=0.1.47", 9 | "fastapi>=0.115.12", 10 | "pyobjtojson>=0.3", 11 | "uvicorn>=0.34.2", 12 | ] 13 | -------------------------------------------------------------------------------- /browser_server/readme.md: -------------------------------------------------------------------------------- 1 | 项目使用python环境 2 | 3 | # 安装 4 | 5 | ```bash 6 | cd /path/to/browserUse 7 | ``` 8 | 9 | 环境安装有多种方式,这里列出两种供参考 10 | 11 | ## uv安装: 12 | 13 | 推荐使用uv管理python环境 14 | 15 | 创建虚拟环境 16 | 17 | ``` 18 | uv venv 19 | ``` 20 | 21 | 激活虚拟环境 22 | 23 | + Linux/macOS: 24 | 25 | ``` 26 | source .venv/bin/activate 27 | ``` 28 | 29 | + windows 30 | 31 | ``` 32 | .venv\Scripts\activate 33 | ``` 34 | 35 | 安装依赖 36 | 37 | + pyproject.toml: 38 | 39 | ``` 40 | uv pip install . 41 | ``` 42 | 43 | + requirements.txt: 44 | 45 | ``` 46 | uv pip install -r requirements.txt 47 | ``` 48 | 49 | ## Pip安装: 50 | 51 | 首先激活对应的虚拟环境 52 | 53 | 然后执行 54 | 55 | ``` 56 | pip install -r requirements.txt 57 | ``` 58 | 59 | 依赖安装 60 | 61 | ## 浏览器插件安装: 62 | 63 | ```bash 64 | #需要激活项目虚拟环境 65 | patchright install chromium --with-deps --no-shell 66 | ``` 67 | 68 | 69 | 70 | # 启动 71 | 72 | ```bash 73 | cd /path/to/browserUse 74 | ``` 75 | 76 | ```bash 77 | 激活虚拟环境 78 | ``` 79 | 80 | ``` 81 | python browser_use\server.py 82 | ``` 83 | 84 | 85 | 86 | # 注意 87 | 88 | 模型必须支持工具调用和 function calling,vision模式仅支持gpt-4o 89 | -------------------------------------------------------------------------------- /browser_server/requirements.txt: -------------------------------------------------------------------------------- 1 | browser-use[memory]>=0.1.47 2 | fastapi>=0.115.12 3 | pyobjtojson>=0.3 4 | uvicorn>=0.34.2 5 | patchright>=1.52.0 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | lemon: 4 | image: hexdolemonai/lemon:latest 5 | container_name: lemon-app 6 | environment: 7 | - DOCKER_HOST_ADDR=host.docker.internal 8 | - ACTUAL_HOST_WORKSPACE_PATH=${WORKSPACE_BASE:-$PWD/workspace} 9 | ports: 10 | - "5005:5005" 11 | extra_hosts: 12 | - "host.docker.internal:host-gateway" 13 | volumes: 14 | - /var/run/docker.sock:/var/run/docker.sock 15 | - ~/.cache:/.cache 16 | - ${WORKSPACE_BASE:-$PWD/workspace}:/app/workspace 17 | - ${WORKSPACE_BASE:-$PWD/data}:/app/data 18 | stdin_open: true 19 | tty: true 20 | command: make run 21 | -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | VITE_SERVICE_URL=http://127.0.0.1:3000 2 | VITE_PORT=5005 -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | docs 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | *.development 28 | .trae/* 29 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": [ 6 | "src/*" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-vue3-template", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@ant-design/icons-vue": "^7.0.1", 13 | "@microsoft/fetch-event-source": "^2.0.1", 14 | "@types/prismjs": "^1.26.5", 15 | "@vueuse/core": "^13.0.0", 16 | "@xterm/xterm": "^5.5.0", 17 | "ant-design-vue": "~4.2.6", 18 | "axios": "^1.7.7", 19 | "driver.js": "^1.3.6", 20 | "highlight.js": "^11.11.1", 21 | "html2pdf": "^0.0.11", 22 | "html2pdf.js": "^0.10.3", 23 | "less": "^4.3.0", 24 | "less-loader": "^12.2.0", 25 | "markdown-it": "^14.1.0", 26 | "markdown-it-attrs": "^4.3.1", 27 | "marked": "^15.0.8", 28 | "md5": "^2.3.0", 29 | "mermaid": "^11.6.0", 30 | "mitt": "^3.0.1", 31 | "pinia": "^2.2.6", 32 | "pinia-plugin-persistedstate": "^3.2.3", 33 | "prismjs": "^1.30.0", 34 | "socket.io-client": "^4.8.1", 35 | "uuid": "^11.1.0", 36 | "vue": "^3.5.13", 37 | "vue-i18n": "^11.1.3", 38 | "vue-router": "^4.4.5", 39 | "xterm-addon-fit": "^0.8.0" 40 | }, 41 | "devDependencies": { 42 | "@svgr/core": "^8.1.0", 43 | "@vitejs/plugin-vue": "^5.2.0", 44 | "sass": "^1.81.0", 45 | "unplugin-vue-components": "^0.27.4", 46 | "vite": "^5.4.11", 47 | "vite-plugin-svg-icons": "^2.0.1", 48 | "vite-plugin-svgr": "^4.3.0", 49 | "vite-svg-loader": "^5.1.0" 50 | }, 51 | "packageManager": "pnpm@9.15.5+sha512.845196026aab1cc3f098a0474b64dfbab2afe7a1b4e91dd86895d8e4aa32a7a6d03049e2d0ad770bbe4de023a7122fb68c1a1d6e0d033c7076085f9d5d4800d4" 52 | } 53 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/assets/experience/action.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/experience/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/experience/left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/experience/plan.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/experience/updown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/click.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/code.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/googleDriver.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/locale.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/txt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/yes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/fileClass/zip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/code.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/maxmize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/minmize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/moreOptions.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/pdfExport.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/filePreview/right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/image/lemon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexdocom/lemonai/2505c058c969da010d9ab480f7699326c20446bb/frontend/src/assets/image/lemon.jpg -------------------------------------------------------------------------------- /frontend/src/assets/logout.svg: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/assets/message/browse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/message/collapse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/message/deleteFile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/message/failure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/message/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/message/stop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/message/success.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/message/think.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /frontend/src/assets/sidebar/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/chat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/collect.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/collected.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/command.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/leftList.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/menuSearch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/menuSwitch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/more.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/searchFile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/share.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/vscode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/MessageFileList/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 46 | -------------------------------------------------------------------------------- /frontend/src/components/browser/image.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 46 | 47 | -------------------------------------------------------------------------------- /frontend/src/components/browser/video.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/components/fileClass/fileSvg.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 61 | 62 | -------------------------------------------------------------------------------- /frontend/src/components/markdown/markdown-it-markmap.js: -------------------------------------------------------------------------------- 1 | import { Transformer, fillTemplate } from 'markmap-lib'; 2 | const transformer = new Transformer(); 3 | // import { Markmap } from 'markmap-view/dist/index.esm.js'; 4 | import * as markmap from 'markmap-view'; 5 | const { Markmap } = markmap; 6 | 7 | if (!window.markmapHash) { 8 | window.markmapHash = {}; 9 | } 10 | 11 | function markmapPlugin(md, options = {}) { 12 | const defaultFenceRenderer = md.renderer.rules.fence; 13 | 14 | md.renderer.rules.fence = (tokens, idx, options, env, slf) => { 15 | 16 | const token = tokens[idx]; 17 | 18 | if (token.info === 'mindmap') { 19 | try { 20 | console.log("markmapHash", markmapHash); 21 | const { root } = transformer.transform(token.content.trim()); 22 | const domID = Date.now().toString(16); 23 | setTimeout(() => { 24 | // const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 25 | // const el = document.getElementById(domID); 26 | const svg = document.getElementById(domID); 27 | const mm = Markmap.create(svg, {}, root); 28 | mm.fit(); 29 | markmapHash[domID] = mm; 30 | }, 100); 31 | return `
`; 32 | 33 | } catch (ex) { 34 | console.log('error', ex); 35 | return `
${ex}
` 36 | } 37 | } 38 | 39 | return defaultFenceRenderer(tokens, idx, options, env, slf) 40 | }; 41 | } 42 | 43 | export default markmapPlugin -------------------------------------------------------------------------------- /frontend/src/locals/index.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | import { createI18n } from 'vue-i18n' 3 | import en from './lang/en' 4 | import zh from './lang/zh' 5 | import de from './lang/de' 6 | import es from './lang/es' 7 | import fr from './lang/fr' 8 | import ja from './lang/ja' 9 | import kr from './lang/kr' 10 | import pt from './lang/pt' 11 | import tr from './lang/tr' 12 | import tw from './lang/tw' 13 | import vi from './lang/vi' 14 | 15 | const messages = { 16 | en, 17 | zh, 18 | de, 19 | es, 20 | fr, 21 | ja, 22 | kr, 23 | pt, 24 | tr, 25 | tw, 26 | vi 27 | } 28 | const language = (navigator.language || 'en').toLocaleLowerCase() // 这是获取浏览器的语言 29 | const i18n = createI18n({ 30 | locale: localStorage.getItem('lang') || language.split('-')[0] || 'en', // 首先从缓存里拿,没有的话就用浏览器语言, 31 | // locale: 'en', // 首先从缓存里拿,没有的话就用浏览器语言, 32 | fallbackLocale: 'en', // 设置备用语言 33 | messages, 34 | legacy: false, // 处理 Uncaught SyntaxError: Not available in legacy mode 的问题 35 | }) 36 | 37 | export default i18n 38 | 39 | -------------------------------------------------------------------------------- /frontend/src/locals/utils/index.js: -------------------------------------------------------------------------------- 1 | export const initDictionary = (list) => { 2 | let obj = {}; 3 | list.forEach(item => { 4 | obj[item.source_text] = item.target_text || ""; 5 | }); 6 | localStorage.setItem('dictionary', JSON.stringify(obj)); 7 | }; 8 | 9 | export const translateFunc = (key) => { 10 | const list = JSON.parse(localStorage.getItem('dictionary')) || {} 11 | return list[key] || key; 12 | }; 13 | 14 | export default { 15 | initDictionary, 16 | translateFunc, 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import './style.scss'; 3 | //ant 4 | import Antd from 'ant-design-vue'; 5 | import 'ant-design-vue/dist/reset.css'; // 引入重置样式 6 | import App from './App.vue'; 7 | const app = createApp(App); 8 | 9 | app.use(Antd); 10 | 11 | import router from "./router/index.js"; 12 | app.use(router); 13 | 14 | import store from "./store"; 15 | app.use(store); 16 | 17 | import i18n from './locals'; 18 | app.use(i18n); 19 | // 配置全局 t 函数 20 | app.config.globalProperties.$t = i18n.global.t; 21 | 22 | app.mount('#app') 23 | -------------------------------------------------------------------------------- /frontend/src/services/chat.js: -------------------------------------------------------------------------------- 1 | import http from "@/utils/http.js"; 2 | 3 | const service = { 4 | async list() { 5 | const uri = "/api/conversation"; 6 | const res = await http.get(uri); 7 | return res || {}; 8 | }, 9 | async create(message) { 10 | const uri = "/api/conversation"; 11 | const response = await http.post(uri, { 12 | content: message 13 | }); 14 | return response || {}; 15 | }, 16 | //PATCH 17 | async update(conversationId, title = "") { 18 | const uri = `/api/conversation/${conversationId}`; 19 | const response = await http.put(uri, { 20 | title: title 21 | }); 22 | return response || {}; 23 | }, 24 | async get(conversationId) { 25 | const uri = `/api/conversation/${conversationId}`; 26 | const response = await http.get(uri); 27 | return response || {}; 28 | }, 29 | async remove(conversationId) { 30 | const uri = `/api/conversation/${conversationId}`; 31 | const response = await http.del(uri); 32 | return response || {}; 33 | }, 34 | //query 35 | async query(query) { 36 | const uri = `/api/conversation/query`; 37 | const response = await http.post(uri, { 38 | query: query 39 | }); 40 | return response || {}; 41 | }, 42 | //favorite 43 | async favorite(conversationId) { 44 | const uri = `/api/conversation/favorite`; 45 | const response = await http.post(uri, { 46 | conversation_id: conversationId 47 | }); 48 | return response || {}; 49 | }, 50 | //unfavorite 51 | async unfavorite(conversationId) { 52 | const uri = `/api/conversation/unfavorite`; 53 | const response = await http.post(uri, { 54 | conversation_id: conversationId 55 | }); 56 | return response || {}; 57 | }, 58 | ///api/message/list 59 | async messageList(conversationId) { 60 | const uri = `/api/message/list?conversation_id=${conversationId}`; 61 | const response = await http.get(uri); 62 | return response || {}; 63 | }, 64 | 65 | async stop(conversationId) { 66 | const uri = `/api/agent/stop`; 67 | const response = await http.post(uri, { 68 | conversation_id: conversationId 69 | }); 70 | return response || {}; 71 | }, 72 | } 73 | 74 | export default service; -------------------------------------------------------------------------------- /frontend/src/services/default-model-setting.js: -------------------------------------------------------------------------------- 1 | import http from '@/utils/http.js' 2 | 3 | 4 | const service = { 5 | // 获取可选模型信息 6 | async getModels() { 7 | const uri = `/api/model/enabled` 8 | const response = await http.get(uri) 9 | return response || {}; 10 | }, 11 | 12 | // 获取类型模型信息 13 | async getModelBySetting() { 14 | const uri = `/api/default_model_setting` 15 | const response = await http.get(uri) 16 | return response || {}; 17 | }, 18 | //更新模型 19 | async updateModel(data) { 20 | const uri = `/api/default_model_setting` 21 | const response = await http.put(uri, data) 22 | return response.data || {}; 23 | }, 24 | //api/default_model_setting/check 25 | async checkModel() { 26 | const uri = `/api/default_model_setting/check` 27 | const response = await http.get(uri) 28 | return response || {}; 29 | }, 30 | 31 | } 32 | export default service -------------------------------------------------------------------------------- /frontend/src/services/experience.js: -------------------------------------------------------------------------------- 1 | import http from '@/utils/http.js' 2 | 3 | const service = { 4 | async getExperienceByType(type) { 5 | const uri = `/api/experience/list` 6 | const params = { 7 | type: type 8 | } 9 | const response = await http.get(uri, params) 10 | return response || {}; 11 | }, 12 | async createExperience(params) { 13 | const uri = `/api/experience` 14 | const response = await http.post(uri, params) 15 | return response || {}; 16 | }, 17 | async updateExperience(params) { 18 | const uri = `/api/experience` 19 | const response = await http.put(uri, params) 20 | return response || {}; 21 | }, 22 | async deleteExperience(id) { 23 | const uri = `/api/experience` 24 | const params = { 25 | id: id 26 | } 27 | const response = await http.del(uri,params) 28 | return response || {}; 29 | } 30 | } 31 | 32 | export default service -------------------------------------------------------------------------------- /frontend/src/services/platforms.js: -------------------------------------------------------------------------------- 1 | import http from '@/utils/http.js' 2 | import { getDefaults } from 'marked'; 3 | 4 | const service = { 5 | // 获取用户平台信息 6 | async getPlatforms() { 7 | const uri = `/api/platform` 8 | const response = await http.get(uri) 9 | return response || {}; 10 | }, 11 | 12 | // 新增平台信息 13 | async insertPlatform(platformData) { 14 | const uri = `/api/platform` 15 | const response = await http.post(uri, platformData) 16 | return response.data || {}; 17 | }, 18 | 19 | // 更新平台信息 20 | async updatePlatform(platformData) { 21 | const uri = `/api/platform/${platformData.id}` 22 | const response = await http.put(uri, platformData) 23 | return response.data || {}; 24 | }, 25 | 26 | // 删除平台信息 27 | async deletePlatform(platform_id) { 28 | const uri = `/api/platform/${platform_id}` 29 | const response = await http.del(uri) 30 | return response.data || {} 31 | }, 32 | 33 | // 获取模型列表 34 | async getModels(platformId) { 35 | const uri = `/api/model/list/${platformId}` 36 | const response = await http.get(uri) 37 | return response || [] 38 | }, 39 | 40 | // 删除模型 41 | async deleteModel(modelId) { 42 | const uri = `/api/model/${modelId}` 43 | const response = await http.del(uri, { id: modelId }) 44 | return response.data || {} 45 | }, 46 | 47 | // 新增模型 48 | async insertModel(modelData) { 49 | const uri = `/api/model` 50 | const response = await http.post(uri, modelData) 51 | return response.data || {} 52 | }, 53 | 54 | // 更新模型 55 | async updateModel(modelData) { 56 | // console.log(modelData) 57 | const uri = `/api/model/${modelData.id}` 58 | const response = await http.put(uri, modelData) 59 | return response.data || {} 60 | }, 61 | 62 | } 63 | 64 | export default service -------------------------------------------------------------------------------- /frontend/src/services/resume.js: -------------------------------------------------------------------------------- 1 | import http from "@/utils/http.js"; 2 | 3 | const service = { 4 | async uploadFile(form) { 5 | const uri = "/chain/api/resume/upload"; 6 | const res = await http.post(uri, form, { 7 | "Content-Type": "multipart/form-data" 8 | }); 9 | return res.data || {}; 10 | } 11 | } 12 | 13 | export default service; -------------------------------------------------------------------------------- /frontend/src/services/search-engine.js: -------------------------------------------------------------------------------- 1 | import http from "@/utils/http"; 2 | 3 | 4 | const service = { 5 | async getSearchEngineConfig() { 6 | const url = "/api/search_provider_setting" 7 | const response = await http.get(url) 8 | return response || {} 9 | }, 10 | async getSearchEngineTemplates() { 11 | const url = "/api/search_provider_setting/templates" 12 | const response = await http.get(url) 13 | return response || {} 14 | }, 15 | async getSearchEngineConfigs() { 16 | const url = "/api/search_provider_setting/configs" 17 | const response = await http.get(url) 18 | return response || {} 19 | }, 20 | async updateSearchEngineConfig(config) { 21 | const url = "/api/search_provider_setting/" 22 | const response = await http.put(url, config) 23 | return response.data || {} 24 | } 25 | } 26 | 27 | export default service; 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/src/services/setting.js: -------------------------------------------------------------------------------- 1 | import http from '@/utils/http.js' 2 | 3 | const service = { 4 | async get() { 5 | const uri = '/api/settings' 6 | const res = await http.get(uri) 7 | return { 8 | modelName: res.data?.llm_model || 'GPT-4', 9 | modelUrl: res.data?.llm_base_url || '', 10 | apiKey: res.data?.llm_api_key || '' 11 | } 12 | }, 13 | 14 | async save(settings) { 15 | const uri = '/api/settings' 16 | await http.post(uri, { 17 | llm_model: settings.modelName, 18 | llm_base_url: settings.modelUrl, 19 | llm_api_key: settings.apiKey 20 | }) 21 | } 22 | } 23 | 24 | export default service -------------------------------------------------------------------------------- /frontend/src/services/sse.js: -------------------------------------------------------------------------------- 1 | import { fetchEventSource } from '@microsoft/fetch-event-source'; 2 | import base64 from "@/utils/base64.js"; 3 | const env = import.meta.env; 4 | console.log('env', env); 5 | 6 | const sse = (uri, options, onTokenStream = (answer, ch) => { }, onOpenStream = () => { }, answer, throttledScrollToBottom = () => { }, abortController = new AbortController()) => { 7 | const nodeToken = localStorage.getItem('node_token'); 8 | return new Promise((resolve, reject) => { 9 | 10 | let content = '' 11 | const fes = fetchEventSource(uri, { 12 | method: 'POST', 13 | body: JSON.stringify(options), 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | 'node-token': nodeToken || '', 17 | }, 18 | openWhenHidden: true, 19 | signal: abortController.signal, 20 | onopen(response) { 21 | if (response.ok) { 22 | onOpenStream(answer, response); 23 | return; // ev 24 | } 25 | }, 26 | onmessage(ev) { 27 | const ch = decodeBase64(ev.data); 28 | content += ch; 29 | onTokenStream(answer, ch); // 回调处理流式消息 30 | throttledScrollToBottom(); 31 | }, 32 | onerror(err) { 33 | console.log('触发了 ============ sse.error ============ ', err); 34 | abortController.abort(); 35 | //抛出异常 36 | reject(err); 37 | throw err; 38 | } 39 | }); 40 | 41 | fes.then((response) => { 42 | console.log('fes.response', response); 43 | resolve(content); 44 | }).catch((error) => { 45 | abortController.abort(); 46 | reject(error); 47 | }) 48 | }) 49 | } 50 | 51 | 52 | const decodeBase64 = (encodedString) => { 53 | const decodedString = decodeURIComponent( 54 | atob(encodedString) 55 | .split("") 56 | .map(function (char) { 57 | return "%" + ("00" + char.charCodeAt(0).toString(16)).slice(-2); 58 | }) 59 | .join("") 60 | ); 61 | return decodedString; 62 | }; 63 | 64 | export default sse; -------------------------------------------------------------------------------- /frontend/src/services/workspace.js: -------------------------------------------------------------------------------- 1 | import http from '@/utils/http.js' 2 | 3 | const service = { 4 | // 获取vscode地址 5 | async getVsCodeUrl(conversationId) { 6 | const uri = `/api/runtime/vscode-url` 7 | const response = await http.get(uri, { conversation_id: conversationId }) 8 | return response || {}; 9 | }, 10 | 11 | // 获取文件列表 12 | async getFiles(conversationId,path){ 13 | const baseUrl = `/api/conversations/${conversationId}/list-files`; 14 | const url = path ? `${baseUrl}?path=${encodeURIComponent(path)}` : baseUrl; 15 | const response = await http.get(url); 16 | return response.data || []; 17 | }, 18 | 19 | // 获取文件内容 20 | async getFile(path) { 21 | const baseUrl = `/api/file/read`; 22 | const response = await http.post(baseUrl, { 23 | path: path 24 | }); 25 | // Check if response.data is a string; if so, return it, otherwise convert to string 26 | return response.data|| ''; 27 | } 28 | 29 | } 30 | 31 | 32 | 33 | 34 | export default service -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author YuGao 540846283@qq.com 3 | */ 4 | // https://pinia.vuejs.org/zh/core-concepts/ 5 | import { createPinia } from 'pinia' 6 | // https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/ 7 | import { createPersistedState } from 'pinia-plugin-persistedstate' 8 | 9 | const pinia = createPinia() 10 | pinia.use(createPersistedState()) 11 | 12 | export default pinia; 13 | -------------------------------------------------------------------------------- /frontend/src/store/modules/demo.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useDemoStore = defineStore('demo', { 4 | state: () => ({ 5 | count: 0, 6 | }), 7 | actions: { 8 | setCount(count) { 9 | this.count = count; 10 | } 11 | }, 12 | persist: true, 13 | }) 14 | -------------------------------------------------------------------------------- /frontend/src/style.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | box-sizing: border-box; 15 | } 16 | 17 | div { 18 | box-sizing: border-box; 19 | } 20 | 21 | #app { 22 | background-color: rgba(255, 255, 255, 1); 23 | color: #242424; 24 | } 25 | 26 | a { 27 | font-weight: 500; 28 | color: #646cff; 29 | text-decoration: inherit; 30 | } 31 | a:hover { 32 | color: #535bf2; 33 | } 34 | 35 | body { 36 | margin: 0; 37 | display: flex; 38 | place-items: center; 39 | min-width: 320px; 40 | min-height: 100vh; 41 | } 42 | 43 | p { 44 | margin: 0; 45 | padding: 0; 46 | } 47 | 48 | h1 { 49 | font-size: 3.2em; 50 | line-height: 1.1; 51 | } 52 | 53 | #app { 54 | width: 100%; 55 | min-width: 1024px; 56 | // max-width: 1280px; 57 | height: 100vh; 58 | margin: 0 auto; 59 | } 60 | 61 | //适配移动端 62 | @media screen and (max-width: 768px) { 63 | #app { 64 | width: 100%; 65 | min-width: 375px; 66 | height: 100vh; 67 | margin: 0 auto; 68 | } 69 | 70 | .ant-modal-body { 71 | height: 80% !important; 72 | overflow-y: auto !important; 73 | } 74 | } 75 | 76 | 77 | 78 | .resume-box { 79 | border: 1px solid #f5f5f5; 80 | border-radius: 8px; 81 | padding: 10px; 82 | flex: 1; 83 | } 84 | 85 | @media (prefers-color-scheme: light) { 86 | :root { 87 | color: #213547; 88 | background-color: #ffffff; 89 | } 90 | a:hover { 91 | color: #747bff; 92 | } 93 | button { 94 | background-color: #f9f9f9; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /frontend/src/utils/base64.js: -------------------------------------------------------------------------------- 1 | export const decode = (encoded) => { 2 | const decodedString = decodeURIComponent( 3 | atob(encoded) 4 | .split("") 5 | .map(function (char) { 6 | return "%" + ("00" + char.charCodeAt(0).toString(16)).slice(-2); 7 | }) 8 | .join("") 9 | ); 10 | return decodedString; 11 | }; 12 | 13 | export default { 14 | decode, 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/src/utils/emitter.js: -------------------------------------------------------------------------------- 1 | import mitt from "mitt"; 2 | 3 | const emitter = mitt() 4 | 5 | export default emitter -------------------------------------------------------------------------------- /frontend/src/utils/markdown.js: -------------------------------------------------------------------------------- 1 | import { marked } from 'marked'; // 引入 marked 2 | 3 | export const renderMarkdown = (content) => { 4 | // 创建自定义 renderer 5 | const renderer = new marked.Renderer(); 6 | 7 | // 重写 renderer 的 link 方法,添加 target="_blank" 和 rel="noopener noreferrer" 8 | renderer.link = function (href, title, text) { 9 | const link = marked.Renderer.prototype.link.call(this, href, title, text); 10 | return link.replace(' { 2 | const date = new Date(timestamp); 3 | const now = new Date(); 4 | const diff = now.getTime() - date.getTime(); 5 | const diffSeconds = Math.floor(diff / 1000); 6 | const diffMinutes = Math.floor(diff / (1000 * 60)); 7 | const diffHours = Math.floor(diff / (1000 * 60 * 60)); 8 | const diffDays = Math.floor(diff / (1000 * 60 * 60 * 24)); 9 | 10 | if (diffSeconds < 60) { 11 | return `${diffSeconds} ${t('lemon.message.secondsAgo')}`; 12 | }else if (diffMinutes < 60) { 13 | return `${diffMinutes} ${t('lemon.message.minutesAgo')}`; 14 | } else if (diffHours < 24) { 15 | return `${diffHours} ${t('lemon.message.hoursAgo')}`; 16 | } else if (diffDays < 30) { 17 | return `${diffDays} ${t('lemon.message.daysAgo')}`; 18 | } else if (diffDays < 365) { 19 | return `${Math.floor(diffDays / 30)} ${t('lemon.message.monthsAgo')}`; 20 | } else { 21 | return `${Math.floor(diffDays / 365)} ${t('lemon.message.yearsAgo')}`; 22 | } 23 | }; 24 | 25 | export default { 26 | formatTime 27 | } -------------------------------------------------------------------------------- /frontend/src/view/demo/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 39 | 40 | 41 | 42 | 47 | -------------------------------------------------------------------------------- /frontend/src/view/demo/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 53 | 54 | 70 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/components/ConfirmPrompt.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/components/ConversationList.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/components/LoadingDots.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/components/Suggestion.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 64 | 65 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/components/SuggestionCard.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 46 | 47 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/message/Action.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/message/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | 38 | -------------------------------------------------------------------------------- /frontend/src/view/lemon/sidebar/Header.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 39 | 40 | -------------------------------------------------------------------------------- /frontend/src/view/setting/ContentSide.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/view/setting/basic.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/view/setting/default-model.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 60 | 61 | -------------------------------------------------------------------------------- /frontend/src/view/setting/defaultModelSetting.vue: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /frontend/src/view/setting/mcp.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /frontend/src/view/setting/search.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite'; 2 | const env = loadEnv('development', process.cwd()); 3 | import vue from '@vitejs/plugin-vue'; 4 | import Components from 'unplugin-vue-components/vite'; 5 | import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'; 6 | import path from 'path'; 7 | import svgLoader from 'vite-svg-loader'; 8 | 9 | console.log(env); 10 | 11 | // https://vitejs.dev/config/ 12 | export default defineConfig({ 13 | plugins: [ 14 | vue(), 15 | svgLoader(), 16 | Components({ 17 | resolvers: [ 18 | AntDesignVueResolver({ 19 | importStyle: 'less', 20 | }), 21 | ], 22 | }), 23 | ], 24 | resolve: { 25 | alias: { 26 | '@': path.resolve(__dirname, 'src'), 27 | '~@': path.resolve(__dirname, 'src'), 28 | }, 29 | }, 30 | server: { 31 | port: env.VITE_PORT || 5005, 32 | host: '0.0.0.0', 33 | strictPort: true, 34 | proxy: { 35 | '/api': { 36 | target: env.VITE_SERVICE_URL || 'http://127.0.0.1:3000', 37 | protocol: 'http', 38 | changeOrigin: true, 39 | ws: true, 40 | }, 41 | }, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@src/*": [ 6 | "src/*" 7 | ], 8 | // Optional: set alias for type folders 9 | "@types/*": [ 10 | "types/*" 11 | ], 12 | }, 13 | "checkJs": true, 14 | "target": "es2020", 15 | "module": "commonjs", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "allowSyntheticDefaultImports": true, 19 | }, 20 | "include": [ 21 | "**/*.js", 22 | "**/*.d.ts", 23 | "src/**/*.d.ts" 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | "dist" 28 | ] 29 | } -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "logs/", 4 | "public/", 5 | "temp/", 6 | "cache/", 7 | "data/", 8 | "node_modules/", 9 | "*.test.js", 10 | "frontend", 11 | "workspace" 12 | ] 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-object", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node bin/www", 7 | "dev": "./node_modules/.bin/nodemon bin/www", 8 | "prd": "pm2 start bin/www", 9 | "test": "mocha ./test/**/*.test.js" 10 | }, 11 | "dependencies": { 12 | "@dqbd/tiktoken": "^1.0.21", 13 | "@playwright/test": "^1.52.0", 14 | "axios": "^1.9.0", 15 | "chai": "^4.3.4", 16 | "cheerio": "^1.0.0", 17 | "debug": "^4.1.1", 18 | "dockerode": "^4.0.6", 19 | "dotenv": "^16.5.0", 20 | "fast-xml-parser": "^4.5.3", 21 | "jsonwebtoken": "^9.0.2", 22 | "koa": "^2.7.0", 23 | "koa-body": "^6.0.1", 24 | "koa-convert": "^1.2.0", 25 | "koa-json": "^2.0.2", 26 | "koa-logger": "^3.2.0", 27 | "koa-onerror": "^4.1.0", 28 | "koa-router": "^7.4.0", 29 | "koa-static": "^5.0.0", 30 | "koa2-swagger-ui": "^5.11.0", 31 | "minimist": "^1.2.8", 32 | "mocha": "^11.1.0", 33 | "module-alias": "^2.2.3", 34 | "mysql2": "^3.14.0", 35 | "node-xlsx": "^0.24.0", 36 | "pino": "^9.6.0", 37 | "playwright": "^1.52.0", 38 | "sequelize": "^6.37.7", 39 | "sinon": "^20.0.0", 40 | "sqlite3": "^5.1.7", 41 | "supertest": "^7.1.0", 42 | "swagger-jsdoc": "^6.2.8", 43 | "uuid": "^11.1.0" 44 | }, 45 | "devDependencies": { 46 | "@types/node": "^22.15.2", 47 | "nodemon": "^1.19.1" 48 | }, 49 | "_moduleAliases": { 50 | "@src": "src" 51 | }, 52 | "packageManager": "pnpm@9.15.5+sha512.845196026aab1cc3f098a0474b64dfbab2afe7a1b4e91dd86895d8e4aa32a7a6d03049e2d0ad770bbe4de023a7122fb68c1a1d6e0d033c7076085f9d5d4800d4" 53 | } 54 | -------------------------------------------------------------------------------- /public/default_data/default_search_provider.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Tavily", 4 | "logo_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB8ElEQVR4Ae2VMUzqUBSG2afiPnSfWADeKyZ9w1PH7nFo1F32RZaiUUy7qmL3iUmjFHMn48hm2bpPXRS1pfz21BQ10BaCl4mT/GEg8H0992+bm2W2dIgbJ+hp156jd7xCbtmz2YQZCqBx40O/DyUYhKXB/x+jQPCxQNeDbr3Xlyaw0YQzIUBhEPnDT3FA4EmBKIwnOy4eEgUo1luNX/FOwDIEKC63QhIwW4DyrvASqIcF7G1OK+G95xrh7Wjw7kHUhUOIUwRU7uCVwEpgJfAbAm/9o/2B3WDPtqYuLtBE7/jWZ/Tw0S1fnuW3A1tDnBdbM1/tw/nfoLIOQalnP++zBKL0NXfw1Jj/BYbyn/1RUXJHJcmBuqtEf95vtGm96dGQEGfmbaD09yAEg4JtFThvuTBN4flJM1IAs4W2MSf8M6Yp+g/VuvewjoXy+I/9XHNZYqjIYir8/KpH34+sPAusPBaIO7wTlE94pSrHsOici5KeBKf1ex2hEHTXzMwkwEfdfBtt4avYQVmqfRNAGnye8k7AO3nHtwR5Ssur7UXgKQJuDPc7a0Z81RNDa/9tOM2wKyh0FNFVJw0qFTEZHsWh1ud4zbC0rqTAxxLcBIJi1UiB8xegez8B7uLiiuGsVeN6BMHOXm181pctM/xUv4D85wOQhOrhYVDW5wAAAABJRU5ErkJggg==", 5 | "base_config_schema": { 6 | "api_key": "" 7 | } 8 | }, 9 | { 10 | "name":"Bing", 11 | "logo_url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcBAMAAACAI8KnAAAAKlBMVEVHcEwQbr4Qbr4Qbr8Qbr4Qbr8Qbr4Qbr4Qbr4Qbr4Qbr4Qbr4QcL8Qbr5MVxUvAAAADnRSTlMAPY68zaFk/+FT8XcVLTF2jDkAAACSSURBVHgBY6AyYFR2TUDihpeXV0yA87jKq1aVVyMkSybwaFUIQHm85bYMDNzlMVAuUzlQH495L5TLXAUit9dCuSxg7vRCuOwBkOxaKJetPABonrsNlMvTvvoAw+VyuLumV4VlL6+Cc7nby8vLl3fCnSXUXm6dgXA1z13ZA2ztK1F8OatqAjKXc7kDivQUAaqGKQCR2iWFIziCkQAAAABJRU5ErkJggg==", 12 | "base_config_schema":{ 13 | 14 | } 15 | }, 16 | { 17 | "name":"Baidu", 18 | "logo_url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAAb1BMVEX///+7vfNna+fHyPXW1/cAAN4fKeDx8vyQk+wcJ+ApMuEPHuAJGt/IyvX7/P6rrfDs7fwAE98/RuPe3/l/gurV1vdwdOh5femxs/Gcnu63uPJgZOZ0eOmLjuw3PuKFiOvv7/xXXOWvsfGmqPBZXuUVxkiOAAAA2ElEQVR4Ac2PxQHDMBAE17CGk0kUMIb6bzHwMhaQEWsEd/hjgjACojA4cjETpum3iw9klouKIiV5diyLsiwOZFXHDZWuKq3YxHW1kqagbeoG+HaWhVk6qyV3vwd+V1wu2i6k15KcgDN5Bk6JaI8FoniB0yLK4UIlWJJeWwTsjIgO0F5TbGhJZCoRtgdpsuurIexyNw5bNzBChSluiYEbe+OIO6vJX5gOOW8rWRaCnunk258sypVsqB6/x5+/LmeDFb3W4/0uNPfXqHtsqLvOPbx/uK6r8a98AHl4DLrdn11VAAAAAElFTkSuQmCC", 19 | "base_config_schema":{ 20 | 21 | } 22 | } 23 | ] -------------------------------------------------------------------------------- /public/img/Lemon_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexdocom/lemonai/2505c058c969da010d9ab480f7699326c20446bb/public/img/Lemon_logo.png -------------------------------------------------------------------------------- /public/img/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexdocom/lemonai/2505c058c969da010d9ab480f7699326c20446bb/public/img/example.png -------------------------------------------------------------------------------- /public/img/qr_code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexdocom/lemonai/2505c058c969da010d9ab480f7699326c20446bb/public/img/qr_code.jpg -------------------------------------------------------------------------------- /public/schemas/conversation.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "id": { 4 | "type": "integer" 5 | }, 6 | "conversation_id": { 7 | "type": "string" 8 | }, 9 | "selected_repository": { 10 | "type": "string" 11 | }, 12 | "title": { 13 | "type": "string" 14 | }, 15 | "create_at": { 16 | "type": "string" 17 | }, 18 | "update_at": { 19 | "type": "string" 20 | }, 21 | "is_favorite": { 22 | "type": "boolean" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /public/schemas/default_model_setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "id": { 4 | "type": "integer" 5 | }, 6 | "setting_type": { 7 | "type": "string" 8 | }, 9 | "model_id": { 10 | "type": "string" 11 | }, 12 | "config": { 13 | "type": "object" 14 | }, 15 | "create_at": { 16 | "type": "string" 17 | }, 18 | "update_at": { 19 | "type": "string" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /public/schemas/experience.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "id": { 4 | "type": "integer" 5 | }, 6 | "title": { 7 | "type": "string" 8 | }, 9 | "goal": { 10 | "type": "string" 11 | }, 12 | "content": { 13 | "type": "string" 14 | }, 15 | "type": { 16 | "type": "string" 17 | }, 18 | "is_enabled": { 19 | "type": "boolean" 20 | }, 21 | "create_at": { 22 | "type": "string" 23 | }, 24 | "update_at": { 25 | "type": "string" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /public/schemas/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "id": { 4 | "type": "integer" 5 | }, 6 | "url": { 7 | "type": "string" 8 | }, 9 | "name": { 10 | "type": "string" 11 | }, 12 | "create_at": { 13 | "type": "string" 14 | }, 15 | "update_at": { 16 | "type": "string" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /public/schemas/model.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "id": { 4 | "type": "integer" 5 | }, 6 | "logo_url": { 7 | "type": "string" 8 | }, 9 | "model_id": { 10 | "type": "string" 11 | }, 12 | "model_name": { 13 | "type": "string" 14 | }, 15 | "group_name": { 16 | "type": "string" 17 | }, 18 | "model_types": { 19 | "type": "string" 20 | }, 21 | "create_at": { 22 | "type": "string" 23 | }, 24 | "update_at": { 25 | "type": "string" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /public/schemas/model_enable.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "id": { 4 | "type": "integer" 5 | }, 6 | "platform_name": { 7 | "type": "string" 8 | }, 9 | "logo_url": { 10 | "type": "string" 11 | }, 12 | "model_id": { 13 | "type": "string" 14 | }, 15 | "model_name": { 16 | "type": "string" 17 | }, 18 | "group_name": { 19 | "type": "string" 20 | }, 21 | "model_types": { 22 | "type": "string" 23 | }, 24 | "create_at": { 25 | "type": "string" 26 | }, 27 | "update_at": { 28 | "type": "string" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /public/schemas/platform.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "id": { 4 | "type": "integer" 5 | }, 6 | "name": { 7 | "type": "string" 8 | }, 9 | "logo_url": { 10 | "type": "string" 11 | }, 12 | "source_type": { 13 | "type": "string" 14 | }, 15 | "api_key": { 16 | "type": "string" 17 | }, 18 | "api_url": { 19 | "type": "string" 20 | }, 21 | "api_version": { 22 | "type": "string" 23 | }, 24 | "key_obtain_url": { 25 | "type": "string" 26 | }, 27 | "is_enabled": { 28 | "type": "boolean" 29 | }, 30 | "create_at": { 31 | "type": "string" 32 | }, 33 | "update_at": { 34 | "type": "string" 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /public/schemas/provider.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "properties": { 6 | "id": { 7 | "type": "integer", 8 | "description": "Provider ID" 9 | }, 10 | "name": { 11 | "type": "string", 12 | "description": "Provider name" 13 | }, 14 | "logo_url": { 15 | "type": "string", 16 | "description": "URL of the provider's logo" 17 | }, 18 | "base_config_schema": { 19 | "type": "string", 20 | "description": "Base configuration schema for the provider" 21 | }, 22 | "create_at": { 23 | "type": "string", 24 | "format": "date-time", 25 | "description": "Creation timestamp" 26 | }, 27 | "update_at": { 28 | "type": "string", 29 | "format": "date-time", 30 | "description": "Update timestamp" 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /public/schemas/provider_setting_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "id": { 4 | "type": "integer" 5 | }, 6 | "provider_id": { 7 | "type": "integer" 8 | }, 9 | "provider_name": { 10 | "type": "string" 11 | }, 12 | "base_config": { 13 | "type": "object" 14 | }, 15 | "logo_url": { 16 | "type": "string" 17 | }, 18 | "api_key": { 19 | "type": "string" 20 | }, 21 | "include_date": { 22 | "type": "boolean" 23 | }, 24 | "cover_provider_search": { 25 | "type": "boolean" 26 | }, 27 | "enable_enhanced_mode": { 28 | "type": "boolean" 29 | }, 30 | "result_count": { 31 | "type": "integer" 32 | }, 33 | "blacklist": { 34 | "type": "string" 35 | }, 36 | "create_at": { 37 | "type": "string" 38 | }, 39 | "update_at": { 40 | "type": "string" 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/agent/AgenticAgent.run.js: -------------------------------------------------------------------------------- 1 | require('module-alias/register'); 2 | require('dotenv').config(); 3 | // https://github.com/GoogleChromeLabs/ndb 4 | 5 | const AgenticAgent = require("./AgenticAgent") 6 | 7 | const run = async () => { 8 | const agent = new AgenticAgent(); 9 | const r = await agent.run("Please read and analyze the Excel file about large model application statistics in the working directory, display its contents on a web page, support filtering between domestic and international entries, and provide access to corresponding official website links and API platform links"); 10 | // console.log(r); 11 | process.exit(0); 12 | } 13 | 14 | run(); -------------------------------------------------------------------------------- /src/agent/code-act/code-act.run.js: -------------------------------------------------------------------------------- 1 | require('module-alias/register'); 2 | require('dotenv').config(); 3 | const completeCodeAct = require('./code-act'); 4 | 5 | const run = async () => { 6 | const task = { 7 | requirement: `List the files in the working directory, find README.md, read it, and output its content`, 8 | status: 'pending' 9 | } 10 | const context = {} 11 | const r = await completeCodeAct(task, context); 12 | console.log('result', r); 13 | process.exit(0); 14 | } 15 | 16 | run(); -------------------------------------------------------------------------------- /src/agent/code-act/thinking.js: -------------------------------------------------------------------------------- 1 | const resolveThinkingPrompt = require("./thinking.prompt"); 2 | const resolveThinking = require("@src/utils/thinking"); 3 | 4 | const call = require("@src/utils/llm"); 5 | const DEVELOP_MODEL = 'assistant'; 6 | 7 | const thinking = async (requirement, context = {}) => { 8 | const { memory, retryCount } = context; 9 | // console.log('memory', memory); 10 | const summarize = false; 11 | const messages = await memory.getMessages(summarize); 12 | if (retryCount > 0) { 13 | // Retry with user reply 14 | console.log('retryCount', retryCount); 15 | // messages.pop(); 16 | } 17 | 18 | // If last message is assistant, return directly, support quickly playback and run action 19 | const message = messages[messages.length - 1]; 20 | if (message && message.role === 'assistant') { 21 | return message.content; 22 | } 23 | 24 | // Use LLM thinking to instruct next action 25 | let prompt = ''; 26 | if (messages.length == 0) { 27 | prompt = await resolveThinkingPrompt(requirement, context); 28 | } 29 | const options = { 30 | messages: messages.map(item => { 31 | return { role: item.role, content: item.content } 32 | }) 33 | } 34 | const content = await call(prompt, context.conversation_id, DEVELOP_MODEL, options); 35 | // console.log('content', content); 36 | if (prompt) { 37 | await memory.addMessage('user', prompt); 38 | } 39 | await memory.addMessage('assistant', content); 40 | 41 | if (content && content.startsWith('')) { 42 | const { thinking: _, content: output } = resolveThinking(content); 43 | return output; 44 | } 45 | 46 | return content; 47 | } 48 | 49 | module.exports = exports = thinking; -------------------------------------------------------------------------------- /src/agent/memory/BaseMemory.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexdocom/lemonai/2505c058c969da010d9ab480f7699326c20446bb/src/agent/memory/BaseMemory.js -------------------------------------------------------------------------------- /src/agent/memory/index.js: -------------------------------------------------------------------------------- 1 | const LocalMemory = require('./LocalMemory') 2 | 3 | module.exports = { 4 | LocalMemory, 5 | } -------------------------------------------------------------------------------- /src/agent/planning/index.js: -------------------------------------------------------------------------------- 1 | require("module-alias/register"); 2 | require("dotenv").config(); 3 | 4 | const call = require("@src/utils/llm"); 5 | const resolvePlanningPrompt = require("@src/agent/prompt/plan"); 6 | 7 | const planning = async (goal, files, previousResult, conversation_id) => { 8 | const planning_prompt = await resolvePlanningPrompt(goal, files, previousResult, conversation_id); 9 | console.log("\n==== planning prompt ====", planning_prompt); 10 | const tasks = await call(planning_prompt, conversation_id, 'assistant', { 11 | response_format: 'json', 12 | temperature: 0, 13 | }); 14 | console.log("\n==== planning result ===="); 15 | console.log(tasks); 16 | const clean_tasks = tasks.filter(item => { 17 | return item.tools && item.tools.length > 0; 18 | }) || []; 19 | return clean_tasks; 20 | }; 21 | 22 | module.exports = exports = planning; 23 | -------------------------------------------------------------------------------- /src/agent/planning/index.test.js: -------------------------------------------------------------------------------- 1 | require("module-alias/register"); 2 | require("dotenv").config(); 3 | 4 | const planning_model = 'provider#doubao#doubao-deepseek-v3'; 5 | const call = require("@src/utils/llm"); 6 | const resolvePlanningPrompt = require("@src/agent/prompt/plan"); 7 | 8 | const planning = async (goal) => { 9 | return [ 10 | { 11 | "id": "1745478628199_692", 12 | "title": "查找目标Excel文件", 13 | "description": "Use the terminal_run tool to execute the search command to locate the Excel file about large model application statistics in the working directory", 14 | "tool": "terminal_run" 15 | }, 16 | { 17 | "id": "1745485679318_600", 18 | "title": "Read the Excel file content", 19 | "description": "Use the read_file tool to read the content of the confirmed target Excel file", 20 | "tool": "read_file" 21 | }, 22 | { 23 | "id": "1745485679318_601", 24 | "title": "Generate a web template", 25 | "description": "Read the file content and use the write_code tool to create a basic HTML template, including the domestic and international screening functions", 26 | "tool": "write_code" 27 | }, 28 | { 29 | "id": "1745485679318_602", 30 | "title": "Deploy the test environment", 31 | "description": "Use the terminal_run tool to start the local test server", 32 | "tool": "terminal_run" 33 | }, 34 | // { 35 | // "title": "Function verification", 36 | // "description": "Use the BrowserCode tool to thoroughly test the filtering functions and link jump of the web page", 37 | // "tool": "BrowserCode" 38 | // } 39 | ] 40 | const prompt = await resolvePlanningPrompt(goal); 41 | const content = await call(prompt, planning_model); 42 | console.log("\n==== conclusion result ===="); 43 | console.log(content); 44 | return content; 45 | }; 46 | 47 | module.exports = exports = planning; 48 | -------------------------------------------------------------------------------- /src/agent/planning/plan.run.js: -------------------------------------------------------------------------------- 1 | require('module-alias/register'); 2 | require('dotenv').config(); 3 | 4 | const planning = require('./index'); 5 | 6 | const run = async () => { 7 | const goal = 'Read and analyze the Excel file about large model application statistics in the working directory, display its contents on a web page, support filtering between domestic and international entries, and provide access to corresponding official website links and API platform links'; 8 | const result = await planning(goal); 9 | console.log(result); 10 | 11 | process.exit(0); 12 | } 13 | 14 | run(); -------------------------------------------------------------------------------- /src/agent/prompt/auto_reply.js: -------------------------------------------------------------------------------- 1 | 2 | const resolveAutoReplyPrompt = async (question) => { 3 | 4 | const prompt = ` 5 | You are a helpful assistant that generates concise. Your name is Lemon. Lemon is a helpful AI agent that can interact with a computer to solve tasks using bash terminal, file editor, and browser. Given a user message, 6 | Simply and politely reply to the user, saying that you will solve their current problem and ask them to wait a moment 7 | 8 | user message is: 9 | 10 | ${question} 11 | ` 12 | 13 | return prompt; 14 | } 15 | 16 | 17 | module.exports = resolveAutoReplyPrompt; -------------------------------------------------------------------------------- /src/agent/prompt/generate_result.js: -------------------------------------------------------------------------------- 1 | 2 | const resolveResultPrompt = (goal, tasks) => { 3 | 4 | let newTasks = tasks.map((task) => { 5 | return { 6 | title: task.title, 7 | description: task.description, 8 | status: task.status, 9 | result: task.result 10 | } 11 | }); 12 | const prompt = ` 13 | You are a helpful AI assistant named Lemon. Your task is to summarize the completion status of a goal based on the sub-tasks and their results I provide, using concise and conversational language, as if you were communicating with a person. 14 | 15 | I will provide you with: 16 | 1. The overall goal. 17 | 2. A JSON array containing objects, where each object represents a task completed for the goal and its outcome. 18 | 19 | Please analyze the goal and the results of the sub-tasks in the JSON array, and then tell me how well the overall goal has been achieved. 20 | **Crucially, please detect the language of the 'goal' you receive and ensure your entire summary is provided in that same language.** 21 | Your summary should focus on the accomplishments, expressed in natural and fluent language, just like you're reporting progress to me. 22 | 23 | Please wait for me to provide the goal and the task information. 24 | 25 | goal:${goal} 26 | tasks: ${JSON.stringify(newTasks)} 27 | ` 28 | 29 | return prompt; 30 | } 31 | 32 | 33 | module.exports = resolveResultPrompt; -------------------------------------------------------------------------------- /src/agent/prompt/generate_title.js: -------------------------------------------------------------------------------- 1 | 2 | const resolveGenerateTitlePrompt = async (truncated_message) => { 3 | 4 | const prompt = ` 5 | You are a helpful assistant that generates concise, descriptive titles for conversations with Lemon. Your name is Lemon. Lemon is a helpful AI agent that can interact with a computer to solve tasks using bash terminal, file editor, and browser. Given a user message (which may be truncated), generate a concise, descriptive title for the conversation. Return only the title, with no additional text, quotes, or explanations. 6 | Reply in the language used in the message 7 | Generate a title for a conversation that starts with this message: 8 | 9 | ${truncated_message} 10 | ` 11 | 12 | return prompt; 13 | } 14 | 15 | 16 | module.exports = resolveGenerateTitlePrompt; -------------------------------------------------------------------------------- /src/agent/prompt/generate_todo.js: -------------------------------------------------------------------------------- 1 | 2 | const resolveTodoPrompt = (tasks_data) => { 3 | 4 | const prompt =` 5 | Generate a todo.md file in Markdown format based on the following task data: 6 | 7 | - The file should start with ## TODO List. 8 | - Each task should be a single line starting with '- [ ]' if its 'status' is '"pending"' or '- [x]' if its 'status' is '"done"'. 9 | - After the checkbox, include the 'title', followed by a colon ':' and the 'description'. 10 | - Keep the output concise and clean, using only valid Markdown syntax. 11 | - Return only the Markdown content as a string with no explanation. 12 | 13 | Example format: 14 | ## TODO List 15 | - [ ] Task Title: Task description 16 | 17 | Here is the task data: 18 | 19 | 20 | ${tasks_data} 21 | ` 22 | 23 | return prompt; 24 | } 25 | 26 | 27 | module.exports = resolveTodoPrompt; -------------------------------------------------------------------------------- /src/agent/prompt/plan.test.js: -------------------------------------------------------------------------------- 1 | require('module-alias/register'); 2 | require('dotenv').config(); 3 | const resolvePlanPrompt = require('@src/agent/prompt/plan'); 4 | 5 | const run = async () => { 6 | const goal = 'Read and analyze the Excel file about large model application statistics in the working directory, display its contents on a web page, support filtering between domestic and international entries, and provide access to corresponding official website links and API platform links'; 7 | const prompt = await resolvePlanPrompt(goal); 8 | console.log(prompt); 9 | } 10 | 11 | run(); -------------------------------------------------------------------------------- /src/agent/prompt/tool.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具调用提示模板生成器 3 | * 根据工具目录中的工具定义生成工具调用的提示模板 4 | */ 5 | const tools = require("@src/agent/tools/index.js"); 6 | // 加载系统扩展工具 7 | const systemTools = require("@src/tools/index"); 8 | Object.assign(tools, systemTools); 9 | 10 | /** 11 | * 生成工具列表的提示模板 12 | * @returns {Promise} 工具列表的提示模板 13 | */ 14 | const resolveToolPrompt = async () => { 15 | 16 | let toolDescription = ""; 17 | // 遍历所有工具并生成它们的描述 18 | for (const [toolName, tool] of Object.entries(tools)) { 19 | if (!tool || !tool.name || !tool.description || !tool.params) { 20 | console.warn(`工具 ${toolName} 定义不完整,跳过`); 21 | continue; 22 | } 23 | // 格式化工具定义为JSON字符串 24 | const toolDefinition = { 25 | description: tool.description, 26 | name: tool.name, 27 | params: tool.params 28 | }; 29 | // 添加工具定义到提示中 30 | toolDescription += ` 31 | ${JSON.stringify(toolDefinition)} 32 | 33 | `; 34 | } 35 | // 使用模板字符串构建工具提示 36 | const prompt = ` 37 | 38 | You are provided with tools to complete user's task and proposal. Here is a list of tools you can use: 39 | ${toolDescription} 40 | 41 | 42 | 43 | Follow these guidelines regarding tool calls 44 | - The conversation history, or tool_call history may refer to tools that are no longer available. NEVER call tools that are not explicitly provided. 45 | - You MUST only use the tools explicitly provided in the tool list. Do not treat file names or code functions as tool names. The available tool names: 46 | - ${Object.keys(tools).join('\n - ')} 47 | 48 | 49 | `; 50 | 51 | return prompt; 52 | } 53 | 54 | module.exports = resolveToolPrompt -------------------------------------------------------------------------------- /src/agent/prompt/tool.test.js: -------------------------------------------------------------------------------- 1 | require('module-alias/register'); 2 | require('dotenv').config(); 3 | const resolveToolPrompt = require('@src/agent/prompt/tool'); 4 | 5 | const run = async () => { 6 | const prompt = await resolveToolPrompt(); 7 | console.log(prompt); 8 | } 9 | 10 | run(); -------------------------------------------------------------------------------- /src/agent/reflection/index.js: -------------------------------------------------------------------------------- 1 | // https://www.promptingguide.ai/zh/techniques/reflexion 2 | const conversation = require('@src/routers/conversation'); 3 | const llmEvaluate = require('./llm.evaluate'); 4 | const { resolveXML } = require("@src/utils/resolve"); 5 | 6 | /** 7 | * 1. Evaluate based on environment execution 8 | * 2. Evaluate based on large language model 9 | */ 10 | const STATUS = { 11 | SUCCESS: 'success', 12 | FAILURE: 'failure', 13 | } 14 | 15 | const reflection = async (requirement, action_result = {}, conversation_id) => { 16 | 17 | // 1. evaluate action result 18 | const { status, content } = action_result; 19 | // If Action execute failed, return error message 20 | if (status === STATUS.FAILURE && action_result.error) { 21 | return { 22 | status: STATUS.FAILURE, 23 | comments: action_result.error, 24 | } 25 | } 26 | 27 | if (status === STATUS.SUCCESS) { 28 | return { 29 | status: STATUS.SUCCESS, 30 | comments: action_result.content, 31 | } 32 | } 33 | 34 | // 2. evaluate action result by llm [暂缓执行] 35 | const evaluation = await llmEvaluate(requirement, content, conversation_id); 36 | const result = resolveXML(evaluation); 37 | return result.evaluation; 38 | } 39 | 40 | module.exports = exports = reflection; -------------------------------------------------------------------------------- /src/agent/reflection/llm.evaluate.js: -------------------------------------------------------------------------------- 1 | // 评估 2 | const resolveEvaluatePrompt = async (requirement = '', result = '') => { 3 | const prompt = `Please act as a professional review expert, fully understand the user's requirements and expected results, compare and analyze the execution results, evaluate whether the execution results meet the user's requirements 4 | 1. If the execution result fully meets the expected result, return success 5 | 2. If the execution result cannot be directly delivered, return failure, and return feedback, missing content, and suggestions for optimization 6 | 3. If the execution result partially meets or fails to execute the key steps, return partial, and return suggestions for补充遗漏内容 7 | 8 | === Task Goal === 9 | ${requirement} 10 | === END === 11 | 12 | === Code Execution Result === 13 | ${result} 14 | === END === 15 | 16 | === Return Format === 17 | 18 | success/failure 19 | 20 | // evaluation result 21 | 22 | 23 | 24 | Start:` 25 | return prompt; 26 | } 27 | 28 | const call = require("@src/utils/llm"); 29 | const evaluate_model = 'assistant'; 30 | const evaluate = async (requirement, result, conversation_id) => { 31 | const prompt = await resolveEvaluatePrompt(requirement, result); 32 | console.log('\n === evaluation prompt ===\n', prompt); 33 | // process.exit(0); 34 | const content = await call(prompt, conversation_id, evaluate_model); 35 | return content; 36 | } 37 | 38 | module.exports = exports = evaluate; -------------------------------------------------------------------------------- /src/agent/tools/browser.js: -------------------------------------------------------------------------------- 1 | const browser = { 2 | name: "browser", 3 | description: "Interact with the browser. Use it ONLY when you need to interact with a webpage.", 4 | params: { 5 | type: "object", 6 | properties: { 7 | question: { 8 | description: "What you want to do with a browser", 9 | type: "string" 10 | } 11 | }, 12 | required: ["question"] 13 | }, 14 | getActionDescription({ question }) { 15 | return question; 16 | } 17 | }; 18 | 19 | module.exports = browser; -------------------------------------------------------------------------------- /src/agent/tools/browser_use.js: -------------------------------------------------------------------------------- 1 | const BrowserCode = { 2 | name: "browser_use", 3 | description: "Use the headless browser to access the specified URL, and optionally execute the provided JavaScript code snippet to extract or interact with page content.", 4 | params: { 5 | type: "object", 6 | properties: { 7 | url: { 8 | description: "The target website URL to visit.", 9 | type: "string" 10 | }, 11 | browser_code: { 12 | description: "The JavaScript code snippet to execute in the page context,用于提取信息或与页面交互。代码应返回值。", 13 | type: "string" 14 | } 15 | }, 16 | required: ["url", "browser_code"] 17 | } 18 | }; 19 | 20 | module.exports = BrowserCode; -------------------------------------------------------------------------------- /src/agent/tools/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const tools = {}; 5 | 6 | const ignored = new Set(['browser_use']); 7 | 8 | // Read all files in the current directory 9 | fs.readdirSync(__dirname).filter(file => { 10 | // Filter out non-JS files and the index file itself 11 | return (file.indexOf('.') !== 0) && (file !== path.basename(__filename)) && (file.slice(-3) === '.js'); 12 | }).forEach(file => { 13 | // Derive the tool name from the filename (e.g., browser_use.js -> browser_use) 14 | const toolName = path.basename(file, '.js'); 15 | // Require the file and add it to the tools object 16 | try { 17 | // handle ignored tools 18 | if (ignored.has(toolName)) return; 19 | // add tool 20 | tools[toolName] = require(path.join(__dirname, file)); 21 | } catch (error) { 22 | console.error(`Error loading tool ${toolName} from ${file}:`, error); 23 | } 24 | }); 25 | 26 | module.exports = tools; -------------------------------------------------------------------------------- /src/agent/tools/read_file.js: -------------------------------------------------------------------------------- 1 | const read_file = { 2 | name: "read_file", 3 | description: "Read the content of the specified file path and return, support txt, md, xlsx, json, etc.", 4 | params: { 5 | type: "object", 6 | properties: { 7 | path: { 8 | description: "The path of the file to read.", 9 | type: "string" 10 | } 11 | }, 12 | required: ["path"] 13 | } 14 | }; 15 | 16 | module.exports = read_file; -------------------------------------------------------------------------------- /src/agent/tools/terminal_run.js: -------------------------------------------------------------------------------- 1 | const TerminalRun = { 2 | name: "terminal_run", 3 | // description: "Execute the specified command in the terminal and return the result", 4 | description:"Execute the specified command in the terminal and return the result. For reading .docx and .doc and .pdf and .xlsx files:\n\n- For .docx files, try using 'pandoc -t plain' or 'antiword '.\n- For .pdf files, try using 'pdftotext -' to output the text to standard output.\n\nPrioritize using 'pandoc' if available due to its broader format support. If these tools are not available, try other suitable command-line tools for reading these file types.", 5 | params: { 6 | type: "object", 7 | properties: { 8 | command: { 9 | description: "The command to execute", 10 | type: "string" 11 | }, 12 | args: { 13 | description: "The list of command parameters", 14 | type: "string", 15 | }, 16 | cwd: { 17 | description: "The working directory of the command execution", 18 | type: "string" 19 | } 20 | }, 21 | required: ["command"] 22 | } 23 | }; 24 | 25 | module.exports = TerminalRun; -------------------------------------------------------------------------------- /src/agent/tools/web_search.js: -------------------------------------------------------------------------------- 1 | const WebSearch = { 2 | name: "web_search", // Snake_case is common for LLM function names 3 | description: `Use this tool to search the web for information`, 4 | params: { 5 | type: "object", 6 | properties: { 7 | query: { 8 | type: "string", 9 | description: "the search key words split with space", 10 | }, 11 | num_results: { 12 | type: "integer", 13 | description: "Optional. The desired number of search results (default: 3).", 14 | } 15 | }, 16 | required: ["query"], // Only 'query' is mandatory 17 | }, 18 | }; 19 | 20 | module.exports = WebSearch; -------------------------------------------------------------------------------- /src/agent/tools/write_code.js: -------------------------------------------------------------------------------- 1 | const WriteCode = { 2 | name: "write_code", 3 | description: "Write the code content to the specified file path.", 4 | params: { 5 | type: "object", 6 | properties: { 7 | path: { 8 | description: "The path of the file to write.", 9 | type: "string" 10 | }, 11 | content: { 12 | description: "The code content to write.", 13 | type: "string" 14 | } 15 | }, 16 | required: ["path", "content"] 17 | } 18 | }; 19 | 20 | module.exports = WriteCode; -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | require("module-alias/register"); 2 | require('dotenv').config(); 3 | 4 | const Koa = require('koa') 5 | const app = new Koa() 6 | const json = require('koa-json') 7 | const onerror = require('koa-onerror') 8 | const { koaBody } = require('koa-body'); 9 | const logger = require('koa-logger') 10 | 11 | const swagger = require('@src/swagger/swagger') // stores swagger.js, can be configured, I put it in the root directory 12 | const { koaSwagger } = require('koa2-swagger-ui') 13 | 14 | const router = require("@src/routers/index"); 15 | const wrapContext = require("@src/middlewares/wrap.context"); 16 | 17 | app.use(wrapContext); 18 | // error handler 19 | onerror(app) 20 | 21 | // middlewares 22 | app.use(koaBody({ 23 | multipart: true 24 | })) 25 | app.use(json()) 26 | app.use(logger()) 27 | 28 | app.use(async (ctx, next) => { 29 | console.log(`Request URL: ${ctx.url}`); 30 | await next(); 31 | }); 32 | const path = require('path'); 33 | 34 | const publicPath = path.join(__dirname, '../public'); 35 | app.use(require('koa-static')(publicPath)) 36 | 37 | // logger 38 | app.use(async (ctx, next) => { 39 | const start = Date.now() 40 | await next() 41 | const ms = Date.now() - start 42 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`) 43 | }) 44 | 45 | // routes 46 | app.use(router) 47 | app.use(swagger.routes()); 48 | app.use(swagger.allowedMethods()); 49 | app.use(koaSwagger({ 50 | routePrefix: '/swagger', // interface documentation access address 51 | swaggerOptions: { 52 | url: '/swagger.json', // example path to json 其实就是之后swagger-jsdoc生成的文档地址 53 | } 54 | })) 55 | 56 | 57 | // error-handling 58 | app.on('error', (err, ctx) => { 59 | console.error('server error', err, ctx) 60 | }); 61 | 62 | module.exports = app 63 | -------------------------------------------------------------------------------- /src/completion/README.md: -------------------------------------------------------------------------------- 1 | # LLM 大模型调用封装 2 | 3 | ## chat/completions SSE 处理逻辑 4 | 5 | > 标准参数配置(以 deepseek 为例) 6 | 7 | ### 请求发起 http.post 8 | 9 | - url: https://api.deepseek.com/chat/completions 10 | - key: API_KEY 11 | - model: deepseek-chat 12 | 13 | ```js 14 | const { url, API_KEY, model, temperature = 0 } = config; 15 | const config = { 16 | method: "post", 17 | maxBodyLength: Infinity, 18 | url, 19 | headers: { 20 | Authorization: `Bearer ${API_KEY}`, 21 | "Content-Type": "application/json", 22 | }, 23 | data: { 24 | model, // 调用模型 25 | messages, // chat 提示词 26 | stream: true, // 流式输出 27 | temperature, 28 | }, 29 | responseType: "stream", 30 | }; 31 | 32 | const response = await axios.request(config).catch((err) => { 33 | return err; 34 | }); 35 | return response; 36 | ``` 37 | 38 | ### 返回消息处理(SSE 流式处理) 39 | 40 | - splitter: \n\n 41 | - messageToValue: 读取 JSON.parse(message.split("data:")[1]).choices[0].delta.content 42 | 43 | ## 代理调用 44 | -------------------------------------------------------------------------------- /src/completion/calc.token.js: -------------------------------------------------------------------------------- 1 | const { get_encoding, encoding_for_model } = require("@dqbd/tiktoken"); 2 | 3 | const calcToken = (text, modelName = "gpt-3.5-turbo") => { 4 | const enc = encoding_for_model(modelName); 5 | const inputs = enc.encode(text); 6 | enc.free(); 7 | return inputs.length 8 | } 9 | 10 | module.exports = exports = calcToken; 11 | 12 | // const len = calcToken("hello world"); 13 | // console.log(len); -------------------------------------------------------------------------------- /src/completion/handle.error.js: -------------------------------------------------------------------------------- 1 | const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); 2 | 3 | const isError = (value) => { 4 | return value instanceof Error; 5 | } 6 | 7 | const friendlyContent = `Sorry, there was an error with the request. I am currently unable to answer your question`; 8 | 9 | const resolveStatusContent = (status) => { 10 | return `Sorry, the current request interface ${status} is abnormal, please check the service deployment` 11 | } 12 | 13 | const handleError = async (error, onTokenStream) => { 14 | if (isError(error)) { 15 | const res = error.response || {} 16 | // let content = friendlyContent; 17 | let content = error.message.toString(); 18 | if (res.status) { 19 | content = resolveStatusContent(res.status); 20 | } 21 | for (const ch of content) { 22 | onTokenStream(ch); 23 | await delay(10); 24 | } 25 | return content; 26 | } 27 | return null; 28 | } 29 | 30 | module.exports = exports = handleError; -------------------------------------------------------------------------------- /src/completion/index.js: -------------------------------------------------------------------------------- 1 | const Azure = require('./llm.azure'); 2 | const OpenAI = require('./llm.openai'); 3 | const GLM3 = require('./llm.glm3'); 4 | const QWen = require('./llm.qwen'); 5 | const Qwen72bChat = require('./llm.qwen-72b-chat'); 6 | const Ollama = require('./llm.ollama'); 7 | 8 | const map = { 9 | 'azure': Azure, 10 | 'openai': OpenAI, 11 | 'glm3': GLM3, 12 | 'qwen': Qwen72bChat, 13 | 'qwen.ali': QWen, 14 | 'ollama': Ollama, 15 | } 16 | 17 | const createLLMInstance = (type, onTokenStream) => { 18 | // console.log('createLLMInstance.type', type) 19 | const LLM = map[type]; 20 | // console.log('createLLMInstance.LLM', LLM); 21 | const llm = new LLM(onTokenStream); 22 | console.log(type, 'llm', llm); 23 | return llm; 24 | } 25 | 26 | module.exports = exports = createLLMInstance -------------------------------------------------------------------------------- /src/completion/llm.config.js: -------------------------------------------------------------------------------- 1 | const BaseLLM = require('./llm.base') 2 | 3 | class ConfigLLM extends BaseLLM { 4 | 5 | constructor(config = {}, onTokenStream) { 6 | super(onTokenStream) 7 | const { url, model, splitter = '\n\n', api_key, appid } = config; 8 | this.splitter = splitter; 9 | this.CHAT_COMPLETION_URL = url; 10 | this.API_KEY = api_key; 11 | this.model = model; 12 | if (appid) { 13 | this.appid = appid; 14 | } 15 | } 16 | } 17 | 18 | module.exports = exports = ConfigLLM; -------------------------------------------------------------------------------- /src/completion/log.record.js: -------------------------------------------------------------------------------- 1 | const LLMLogs = require("@src/models/LLMLogs"); 2 | 3 | const calcToken = require('./calc.token'); 4 | 5 | const calcTokenInput = (prompt, messages) => { 6 | let content = prompt; 7 | for (const message of messages) { 8 | content += message.content; 9 | } 10 | return calcToken(content); 11 | } 12 | 13 | const recordLLMLogs = async (model, userId, chainId, prompt, content, messages = []) => { 14 | const logValue = { 15 | model, 16 | userId, 17 | chainId, 18 | prompt, 19 | content, 20 | messages, 21 | tokenInput: calcTokenInput(prompt, messages), 22 | tokenOutput: calcToken(content), 23 | createTime: new Date(), 24 | updateTime: new Date() 25 | } 26 | // console.log('log.value', logValue); 27 | const r = await LLMLogs.create(logValue); 28 | return r; 29 | } 30 | 31 | module.exports = exports = recordLLMLogs; -------------------------------------------------------------------------------- /src/completion/resolveServiceConfig.js: -------------------------------------------------------------------------------- 1 | const configs = require('./configs.js') 2 | 3 | const resolveServiceConfig = async (channel, service = '') => { 4 | const config = configs.find(item => { 5 | return item.channel === channel && item.service === service 6 | }); 7 | return config || {} 8 | } 9 | 10 | module.exports = exports = resolveServiceConfig -------------------------------------------------------------------------------- /src/logging/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 日志模块入口文件 3 | * 导出 Logger 类,方便其他模块导入使用 4 | */ 5 | 6 | const Logger = require('./logger'); 7 | 8 | module.exports = { 9 | Logger 10 | }; -------------------------------------------------------------------------------- /src/middlewares/wrap.context.js: -------------------------------------------------------------------------------- 1 | const responseWrap = (response) => { 2 | response.success = function (data, msg = "成功", status = 200) { 3 | const res = { 4 | data, 5 | code: 0, 6 | msg, 7 | }; 8 | this.body = res; 9 | this.status = status; 10 | }; 11 | 12 | response.fail = function (data, msg = "接口错误", status = 200) { 13 | const res = { 14 | data, 15 | code: 1, 16 | msg, 17 | }; 18 | this.body = res; 19 | this.status = status; 20 | }; 21 | 22 | response.file = function (fileName, stream) { 23 | response.set('Content-Type', 'text/csv; charset=utf-8') 24 | response.set( 25 | 'Content-Disposition', 26 | `attachment; filename=${encodeURIComponent(fileName)}` 27 | ) 28 | this.body = stream 29 | }; 30 | }; 31 | 32 | module.exports = exports = async (ctx, next) => { 33 | responseWrap(ctx.response); 34 | await next(); 35 | }; 36 | -------------------------------------------------------------------------------- /src/models/BaseModel.js: -------------------------------------------------------------------------------- 1 | const { Model, DataTypes } = require('sequelize'); 2 | const Sequelize = require('sequelize'); 3 | 4 | /** 5 | * https://sequelize.org/ 6 | * https://sequelize.org/api/v6/class/src/model.js~model#static-method-init 7 | */ 8 | class BaseModel extends Model { 9 | static init(attributes = {}, options = {}) { 10 | // console.log(arguments); 11 | // 定义公共字段 12 | const commonFields = { 13 | create_time: { 14 | type: DataTypes.DATE, 15 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), 16 | comment: 'Create Time' 17 | }, 18 | update_time: { 19 | type: DataTypes.DATE, 20 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), 21 | onUpdate: Sequelize.literal('CURRENT_TIMESTAMP'), 22 | comment: 'Update Time' 23 | }, 24 | delete_time: { 25 | type: DataTypes.DATE, 26 | comment: 'Delete Time' 27 | }, 28 | }; 29 | 30 | // 合并公共字段和特定字段 31 | const mergedAttributes = { 32 | id: { 33 | type: DataTypes.INTEGER, 34 | primaryKey: true, 35 | autoIncrement: true, 36 | }, 37 | ...attributes, 38 | ...commonFields 39 | }; 40 | 41 | // 设置默认配置 42 | const defaultOptions = { 43 | timestamps: false, 44 | ...options 45 | }; 46 | // console.log(mergedAttributes, defaultOptions); 47 | super.init(mergedAttributes, defaultOptions); 48 | } 49 | } 50 | 51 | module.exports = exports = BaseModel; -------------------------------------------------------------------------------- /src/models/Conversation.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class ConversationTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | comment: 'Model ID' 13 | }, 14 | conversation_id: { 15 | type: DataTypes.STRING, 16 | allowNull: false, 17 | comment: 'Conversation ID' 18 | }, 19 | selected_repository: { 20 | type: DataTypes.STRING, 21 | allowNull: true, 22 | comment: 'Associated Code Repository' 23 | }, 24 | title: { 25 | type: DataTypes.STRING, 26 | allowNull: false, 27 | comment: 'Title' 28 | }, 29 | content: { 30 | type: DataTypes.TEXT, 31 | allowNull: false, 32 | comment: 'Content' 33 | }, 34 | input_tokens: { 35 | type: DataTypes.INTEGER, 36 | allowNull: true, 37 | comment: 'Input Tokens', 38 | defaultValue: 0 39 | }, 40 | output_tokens: { 41 | type: DataTypes.INTEGER, 42 | allowNull: true, 43 | comment: 'Output Tokens', 44 | defaultValue: 0 45 | }, 46 | create_at: { 47 | type: DataTypes.DATE, 48 | allowNull: false, 49 | defaultValue: DataTypes.NOW, 50 | comment: 'Create Time' 51 | }, 52 | update_at: { 53 | type: DataTypes.DATE, 54 | allowNull: false, 55 | defaultValue: DataTypes.NOW, 56 | comment: 'Update Time' 57 | }, 58 | is_favorite: { 59 | type: DataTypes.BOOLEAN, 60 | allowNull: false, 61 | defaultValue: false, 62 | comment: 'Is Favorite' 63 | }, 64 | }; 65 | 66 | ConversationTable.init(fields, { 67 | sequelize, 68 | modelName: 'conversation' 69 | }); 70 | 71 | 72 | module.exports = exports = ConversationTable; -------------------------------------------------------------------------------- /src/models/DefaultModelSetting.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class DefaultModelSettingTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | comment: 'ID' 13 | }, 14 | setting_type: { 15 | type: DataTypes.STRING, 16 | allowNull: false, 17 | comment: 'Setting Type' 18 | }, 19 | model_id: { 20 | type: DataTypes.STRING(255), 21 | allowNull: false, 22 | comment: 'Model ID' 23 | }, 24 | config: { 25 | type: DataTypes.JSON, 26 | allowNull: false, 27 | comment: 'Configuration Information' 28 | }, 29 | create_at: { 30 | type: DataTypes.DATE, 31 | allowNull: false, 32 | defaultValue: DataTypes.NOW, 33 | comment: 'Create Time' 34 | }, 35 | update_at: { 36 | type: DataTypes.DATE, 37 | allowNull: false, 38 | defaultValue: DataTypes.NOW, 39 | comment: 'Update Time' 40 | } 41 | }; 42 | 43 | DefaultModelSettingTable.init(fields, { 44 | sequelize, 45 | modelName: 'default_model_setting' 46 | }); 47 | 48 | module.exports = exports = DefaultModelSettingTable; -------------------------------------------------------------------------------- /src/models/Experience.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class ExperienceTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | comment: 'ID' 13 | }, 14 | type: { 15 | type: DataTypes.STRING(255), 16 | allowNull: false, 17 | comment: 'type' 18 | }, 19 | title: { 20 | type: DataTypes.STRING(255), 21 | allowNull: false, 22 | comment: 'title' 23 | }, 24 | goal: { 25 | type: DataTypes.TEXT('long'), 26 | comment: 'goal' 27 | }, 28 | content: { 29 | type: DataTypes.TEXT('long'), 30 | comment: 'Content' 31 | }, 32 | is_enabled: { 33 | type: DataTypes.BOOLEAN, 34 | allowNull: false, 35 | defaultValue: false, 36 | comment: 'Enabled' 37 | }, 38 | create_at: { 39 | type: DataTypes.DATE, 40 | allowNull: false, 41 | defaultValue: DataTypes.NOW, 42 | comment: 'Created At' 43 | }, 44 | update_at: { 45 | type: DataTypes.DATE, 46 | allowNull: false, 47 | defaultValue: DataTypes.NOW, 48 | comment: 'Updated At' 49 | } 50 | }; 51 | 52 | ExperienceTable.init(fields, { 53 | sequelize, 54 | modelName: 'experience' 55 | }); 56 | 57 | module.exports = exports = ExperienceTable; -------------------------------------------------------------------------------- /src/models/File.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class FileTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | comment: 'Model ID' 13 | }, 14 | conversation_id: { 15 | type: DataTypes.STRING, 16 | allowNull: false, 17 | comment: 'Conversation ID' 18 | }, 19 | url: { 20 | type: DataTypes.STRING, 21 | allowNull: false, 22 | comment: 'url' 23 | }, 24 | name: { 25 | type: DataTypes.STRING, 26 | allowNull: false, 27 | comment: 'Title' 28 | }, 29 | create_at: { 30 | type: DataTypes.DATE, 31 | allowNull: false, 32 | defaultValue: DataTypes.NOW, 33 | comment: 'Create Time' 34 | }, 35 | update_at: { 36 | type: DataTypes.DATE, 37 | allowNull: false, 38 | defaultValue: DataTypes.NOW, 39 | comment: 'Update Time' 40 | } 41 | }; 42 | 43 | FileTable.init(fields, { 44 | sequelize, 45 | modelName: 'file' 46 | }); 47 | 48 | 49 | module.exports = exports = FileTable; -------------------------------------------------------------------------------- /src/models/LLMLogs.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { DataTypes } = require("sequelize"); 3 | const BaseModel = require('./BaseModel.js'); 4 | 5 | class LLMLogs extends BaseModel { } 6 | 7 | LLMLogs.init({ 8 | id: { 9 | type: DataTypes.BIGINT, 10 | primaryKey: true, 11 | autoIncrement: true, 12 | comment: 'ID' 13 | }, 14 | model: { 15 | type: DataTypes.STRING(100), 16 | comment: 'Model' 17 | }, 18 | prompt: { 19 | type: DataTypes.TEXT('long'), 20 | comment: 'Prompt' 21 | }, 22 | messages: { 23 | type: DataTypes.JSON, 24 | comment: 'Messages' 25 | }, 26 | content: { 27 | type: DataTypes.TEXT('long'), 28 | comment: 'Content' 29 | }, 30 | json: { 31 | type: DataTypes.JSON, 32 | comment: 'JSON' 33 | }, 34 | }, { 35 | sequelize, 36 | tableName: 'llm_logs', 37 | timestamps: false, 38 | comment: 'LLM Logs' 39 | }); 40 | 41 | module.exports = exports = LLMLogs; -------------------------------------------------------------------------------- /src/models/Message.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class MessageTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | comment: 'ID' 13 | }, 14 | role: { 15 | type: DataTypes.STRING(255), 16 | allowNull: false, 17 | comment: 'Role' 18 | }, 19 | uuid: { 20 | type: DataTypes.STRING(255), 21 | allowNull: true, 22 | comment: 'UUID' 23 | }, 24 | conversation_id: { 25 | type: DataTypes.STRING(255), 26 | allowNull: false, 27 | comment: 'Conversation ID' 28 | }, 29 | status: { 30 | type: DataTypes.STRING(255), 31 | allowNull: false, 32 | comment: 'Status' 33 | }, 34 | content: { 35 | type: DataTypes.TEXT, 36 | allowNull: false, 37 | comment: 'Content' 38 | }, 39 | timestamp: { 40 | type: DataTypes.BIGINT, 41 | allowNull: false, 42 | comment: 'Timestamp' 43 | }, 44 | meta: { 45 | type: DataTypes.JSON, 46 | allowNull: false, 47 | comment: 'Meta' 48 | }, 49 | comments: { 50 | type: DataTypes.STRING(255), 51 | allowNull: true, 52 | comment: 'Comments' 53 | }, 54 | memorized: { 55 | type: DataTypes.BOOLEAN, 56 | defaultValue: false, 57 | allowNull: true, 58 | comment: 'Memorized' 59 | }, 60 | create_at: { 61 | type: DataTypes.DATE, 62 | allowNull: false, 63 | defaultValue: DataTypes.NOW, 64 | comment: 'Created At' 65 | }, 66 | update_at: { 67 | type: DataTypes.DATE, 68 | allowNull: false, 69 | defaultValue: DataTypes.NOW, 70 | comment: 'Updated At' 71 | } 72 | }; 73 | 74 | MessageTable.init(fields, { 75 | sequelize, 76 | modelName: 'message' 77 | }); 78 | 79 | module.exports = exports = MessageTable; -------------------------------------------------------------------------------- /src/models/Model.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class ModelTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | comment: 'Model ID' 13 | }, 14 | logo_url: { 15 | type: DataTypes.STRING(255), 16 | allowNull: true, 17 | comment: 'Logo URL' 18 | }, 19 | platform_id: { 20 | type: DataTypes.INTEGER, 21 | allowNull: false, 22 | comment: 'Platform ID' 23 | }, 24 | model_id: { 25 | type: DataTypes.STRING(255), 26 | allowNull: false, 27 | comment: 'Model ID' 28 | }, 29 | model_name: { 30 | type: DataTypes.STRING(255), 31 | allowNull: false, 32 | comment: 'Model Name' 33 | }, 34 | group_name: { 35 | type: DataTypes.STRING(255), 36 | allowNull: false, 37 | comment: 'Group Name' 38 | }, 39 | model_types: { 40 | type: DataTypes.JSON, 41 | comment: 'Model Types' 42 | }, 43 | create_at: { 44 | type: DataTypes.DATE, 45 | allowNull: false, 46 | defaultValue: DataTypes.NOW, 47 | comment: 'Created At' 48 | }, 49 | update_at: { 50 | type: DataTypes.DATE, 51 | allowNull: false, 52 | defaultValue: DataTypes.NOW, 53 | comment: 'Updated At' 54 | } 55 | }; 56 | 57 | ModelTable.init(fields, { 58 | sequelize, 59 | modelName: 'model' 60 | }); 61 | 62 | module.exports = exports = ModelTable; -------------------------------------------------------------------------------- /src/models/Platform.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class PlatformTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | comment: 'Platform ID' 13 | }, 14 | name: { 15 | type: DataTypes.STRING(255), 16 | allowNull: false, 17 | comment: 'Platform Name' 18 | }, 19 | logo_url: { 20 | type: DataTypes.STRING(255), 21 | allowNull: true, 22 | comment: 'Logo URL' 23 | }, 24 | source_type: { 25 | type: DataTypes.STRING(50), 26 | allowNull: true, 27 | defaultValue: 'system', 28 | comment: 'Source Type' 29 | }, 30 | api_key: { 31 | type: DataTypes.STRING(255), 32 | allowNull: true, 33 | comment: 'API Key' 34 | }, 35 | api_url: { 36 | type: DataTypes.STRING(255), 37 | allowNull: true, 38 | comment: 'API URL' 39 | }, 40 | api_version: { 41 | type: DataTypes.STRING(50), 42 | comment: 'API Version' 43 | }, 44 | key_obtain_url: { 45 | type: DataTypes.STRING(255), 46 | comment: 'Key Obtain URL' 47 | }, 48 | is_enabled: { 49 | type: DataTypes.BOOLEAN, 50 | allowNull: false, 51 | defaultValue: false, 52 | comment: 'Enabled' 53 | }, 54 | create_at: { 55 | type: DataTypes.DATE, 56 | allowNull: false, 57 | defaultValue: DataTypes.NOW, 58 | comment: 'Created At' 59 | }, 60 | update_at: { 61 | type: DataTypes.DATE, 62 | allowNull: false, 63 | defaultValue: DataTypes.NOW, 64 | comment: 'Updated At' 65 | } 66 | }; 67 | 68 | PlatformTable.init(fields, { 69 | sequelize, 70 | modelName: 'platform' 71 | }); 72 | 73 | module.exports = exports = PlatformTable; -------------------------------------------------------------------------------- /src/models/SearchProvider.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class SearchProviderTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | }, 13 | name: { 14 | type: DataTypes.STRING(255), 15 | allowNull: false, 16 | comment: 'Search Engine Name' 17 | }, 18 | logo_url: { 19 | type: DataTypes.STRING(255), 20 | allowNull: false, 21 | comment: 'Logo URL' 22 | }, 23 | base_config_schema: { 24 | type: DataTypes.JSON, 25 | comment: 'Base Config Schema' 26 | }, 27 | create_at: { 28 | type: DataTypes.DATE, 29 | allowNull: false, 30 | defaultValue: DataTypes.NOW, 31 | comment: 'Created At' 32 | }, 33 | update_at: { 34 | type: DataTypes.DATE, 35 | allowNull: false, 36 | defaultValue: DataTypes.NOW, 37 | comment: 'Updated At' 38 | } 39 | }; 40 | 41 | SearchProviderTable.init(fields, { 42 | sequelize, 43 | modelName: 'search_provider', 44 | }); 45 | 46 | module.exports = exports = SearchProviderTable; -------------------------------------------------------------------------------- /src/models/Task.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class TaskTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | comment: 'Task ID' 13 | }, 14 | conversation_id: { 15 | type: DataTypes.STRING, 16 | allowNull: false, 17 | comment: 'Conversation ID' 18 | }, 19 | task_id: { 20 | type: DataTypes.STRING, 21 | allowNull: false, 22 | comment: 'Task ID' 23 | }, 24 | requirement: { 25 | type: DataTypes.TEXT, 26 | allowNull: false, 27 | }, 28 | status: { 29 | type: DataTypes.STRING, 30 | allowNull: false, 31 | }, 32 | error: { 33 | type: DataTypes.TEXT, 34 | allowNull: true, 35 | }, 36 | result: { 37 | type: DataTypes.TEXT, 38 | allowNull: true, 39 | }, 40 | memorized: { 41 | type: DataTypes.TEXT, 42 | allowNull: true, 43 | }, 44 | create_at: { 45 | type: DataTypes.DATE, 46 | allowNull: false, 47 | defaultValue: DataTypes.NOW, 48 | comment: 'Created At' 49 | }, 50 | update_at: { 51 | type: DataTypes.DATE, 52 | allowNull: false, 53 | defaultValue: DataTypes.NOW, 54 | comment: 'Updated At' 55 | } 56 | }; 57 | 58 | TaskTable.init(fields, { 59 | sequelize, 60 | modelName: 'task' 61 | }); 62 | 63 | 64 | module.exports = exports = TaskTable; -------------------------------------------------------------------------------- /src/models/UserProviderConfig.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class UserProviderConfigTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | }, 13 | provider_id: { 14 | type: DataTypes.INTEGER, 15 | allowNull: false, 16 | comment: 'Search Engine ID' 17 | }, 18 | base_config: { 19 | type: DataTypes.JSON, 20 | comment: 'User Custom Configuration' 21 | }, 22 | create_at: { 23 | type: DataTypes.DATE, 24 | allowNull: false, 25 | defaultValue: DataTypes.NOW, 26 | comment: 'Created At' 27 | }, 28 | update_at: { 29 | type: DataTypes.DATE, 30 | allowNull: false, 31 | defaultValue: DataTypes.NOW, 32 | comment: 'Updated At' 33 | } 34 | }; 35 | 36 | UserProviderConfigTable.init(fields, { 37 | sequelize, 38 | modelName: 'user_provider_config', 39 | }); 40 | 41 | module.exports = exports = UserProviderConfigTable; -------------------------------------------------------------------------------- /src/models/UserSearchSetting.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./index.js'); 2 | const { Model, DataTypes } = require("sequelize"); 3 | 4 | class UserSearchSettingTable extends Model { } 5 | 6 | const fields = { 7 | id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | allowNull: false, 12 | }, 13 | provider_id: { 14 | type: DataTypes.INTEGER, 15 | allowNull: false, 16 | comment: 'Search Engine ID' 17 | }, 18 | include_date: { 19 | type: DataTypes.BOOLEAN, 20 | allowNull: false, 21 | defaultValue: false, 22 | comment: 'Whether to automatically include date range filter in search results' 23 | }, 24 | cover_provider_search: { 25 | type: DataTypes.BOOLEAN, 26 | allowNull: false, 27 | defaultValue: false, 28 | comment: 'Whether to cover the original search logic of the service provider' 29 | }, 30 | enable_enhanced_mode: { 31 | type: DataTypes.BOOLEAN, 32 | allowNull: false, 33 | defaultValue: false, 34 | comment: 'Whether to enable AI enhanced search mode' 35 | }, 36 | result_count: { 37 | type: DataTypes.INTEGER, 38 | allowNull: false, 39 | defaultValue: 10, 40 | comment: 'Number of search results' 41 | }, 42 | blacklist: { 43 | type: DataTypes.TEXT, 44 | allowNull: true, 45 | comment: 'Blacklist' 46 | }, 47 | create_at: { 48 | type: DataTypes.DATE, 49 | allowNull: false, 50 | defaultValue: DataTypes.NOW, 51 | comment: 'Created At' 52 | }, 53 | update_at: { 54 | type: DataTypes.DATE, 55 | allowNull: false, 56 | defaultValue: DataTypes.NOW, 57 | comment: 'Updated At' 58 | } 59 | }; 60 | 61 | UserSearchSettingTable.init(fields, { 62 | sequelize, 63 | modelName: 'user_search_setting', 64 | }); 65 | 66 | module.exports = exports = UserSearchSettingTable; -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | const { Sequelize } = require('sequelize'); 2 | const path = require('path') 3 | 4 | const config = process.env; 5 | 6 | 7 | const sequelize = new Sequelize({ 8 | dialect: 'sqlite', 9 | storage: path.resolve(__dirname, '../../', config.STORAGE_PATH || 'data/database.sqlite'), 10 | define: { 11 | timestamps: false, 12 | freezeTableName: true, 13 | }, 14 | logging: false 15 | }); 16 | 17 | module.exports = exports = sequelize; 18 | -------------------------------------------------------------------------------- /src/routers/agent/README.md: -------------------------------------------------------------------------------- 1 | ## run agent 2 | 3 | ```shell 4 | curl -N --location 'http://localhost:3000/api/agent/run' \ 5 | --header 'Content-Type: application/json' \ 6 | --header 'Authorization: Bearer xxx' \ 7 | --data '{"question": "请查看工作目录中的文件, 找到 README.md 文件, 读取文件内容, 并输出内容"}' 8 | ``` 9 | -------------------------------------------------------------------------------- /src/routers/agent/index.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | const router = require("koa-router")(); 3 | 4 | router.prefix("/api/agent"); 5 | 6 | router.use(require('./run.js')); 7 | 8 | module.exports = router.routes(); 9 | -------------------------------------------------------------------------------- /src/routers/conversation/index.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | router.prefix("/api/conversation"); 4 | 5 | const modules = [ 6 | "conversation", 7 | "favorite" 8 | ] 9 | 10 | for (const module of modules) { 11 | try { 12 | router.use(require(`./${module}.js`)); 13 | } 14 | catch (error) { console.log(`load ${module} error`, error); } 15 | } 16 | 17 | module.exports = router.routes(); 18 | -------------------------------------------------------------------------------- /src/routers/default_model_setting/index.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | router.prefix("/api/default_model_setting"); 4 | 5 | const modules = [ 6 | "default_model_setting", 7 | ] 8 | 9 | for (const module of modules) { 10 | try { 11 | router.use(require(`./${module}.js`)); 12 | } 13 | catch (error) { console.log(`load ${module} error`, error); } 14 | } 15 | 16 | module.exports = router.routes(); 17 | -------------------------------------------------------------------------------- /src/routers/experience/index.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | router.prefix("/api/experience"); 4 | 5 | const modules = [ 6 | "experience", 7 | ] 8 | 9 | for (const module of modules) { 10 | try { 11 | router.use(require(`./${module}.js`)); 12 | } 13 | catch (error) { console.log(`load ${module} error`, error); } 14 | } 15 | 16 | module.exports = router.routes(); 17 | -------------------------------------------------------------------------------- /src/routers/file/index.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | router.prefix("/api/file"); 4 | 5 | const modules = [ 6 | "file", 7 | ] 8 | 9 | for (const module of modules) { 10 | try { 11 | router.use(require(`./${module}.js`)); 12 | } 13 | catch (error) { console.log(`load ${module} error`, error); } 14 | } 15 | 16 | module.exports = router.routes(); 17 | -------------------------------------------------------------------------------- /src/routers/index.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | const router = require("koa-router")(); 3 | 4 | // 默认首页 5 | router.get("/", async ({ response, request, redis }) => { 6 | response.body = "Hello, World !"; 7 | response.status = 200; 8 | }); 9 | 10 | const modules = [ 11 | "agent", 12 | 'conversation', 13 | 'file', 14 | 'platform', 15 | 'model', 16 | 'default_model_setting', 17 | 'search_provider_setting', 18 | 'runtime', 19 | 'message', 20 | 'experience' 21 | ]; 22 | 23 | for (const module of modules) { 24 | try { 25 | // console.log('module', module); 26 | router.use(require(`./${module}/index.js`)); 27 | } catch (error) { 28 | console.log(`load ${module} error`, error); 29 | } 30 | } 31 | 32 | module.exports = exports = router.routes(); 33 | -------------------------------------------------------------------------------- /src/routers/message/index.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | router.prefix("/api/message"); 4 | 5 | const modules = [ 6 | "message", 7 | ] 8 | 9 | for (const module of modules) { 10 | try { 11 | router.use(require(`./${module}.js`)); 12 | } 13 | catch (error) { console.log(`load ${module} error`, error); } 14 | } 15 | 16 | module.exports = router.routes(); 17 | -------------------------------------------------------------------------------- /src/routers/message/message.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | const Message = require("@src/models/Message"); 4 | 5 | 6 | // api/message/list?conversation_id=1234567890 7 | /** 8 | * @swagger 9 | * /api/message/list: 10 | * get: 11 | * summary: Get message list 12 | * tags: 13 | * - Message 14 | * description: This endpoint retrieves a list of messages. 15 | * parameters: 16 | * - in: query 17 | * name: conversation_id 18 | * required: true 19 | * schema: 20 | * type: string 21 | * description: The ID of the conversation to retrieve messages for. 22 | * responses: 23 | * 200: 24 | * description: A list of messages. 25 | */ 26 | router.get("/list", async ({ query, response }) => { 27 | const { conversation_id } = query; 28 | if (!conversation_id) { 29 | return response.error("Missing conversation_id"); 30 | } 31 | const messages = await Message.findAll({ 32 | where: { conversation_id: conversation_id } 33 | }); 34 | 35 | return response.success(messages); 36 | }); 37 | 38 | 39 | 40 | 41 | module.exports = exports = router.routes(); -------------------------------------------------------------------------------- /src/routers/model/index.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | router.prefix("/api/model"); 4 | 5 | const modules = [ 6 | "model", 7 | ] 8 | 9 | for (const module of modules) { 10 | try { 11 | router.use(require(`./${module}.js`)); 12 | } 13 | catch (error) { console.log(`load ${module} error`, error); } 14 | } 15 | 16 | module.exports = router.routes(); 17 | -------------------------------------------------------------------------------- /src/routers/platform/index.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | router.prefix("/api/platform"); 4 | 5 | const modules = [ 6 | "platform", 7 | ] 8 | 9 | for (const module of modules) { 10 | try { 11 | router.use(require(`./${module}.js`)); 12 | } 13 | catch (error) { console.log(`load ${module} error`, error); } 14 | } 15 | 16 | module.exports = router.routes(); 17 | -------------------------------------------------------------------------------- /src/routers/runtime/index.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | const router = require("koa-router")(); 3 | 4 | router.prefix("/api/runtime"); 5 | 6 | const modules = [ 7 | "runtime", 8 | ] 9 | 10 | for (const module of modules) { 11 | try { 12 | router.use(require(`./${module}.js`)); 13 | } 14 | catch (error) { console.log(`load ${module} error`, error); } 15 | } 16 | 17 | module.exports = router.routes(); 18 | -------------------------------------------------------------------------------- /src/routers/runtime/runtime.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | const DockerRuntime = require("@src/runtime/DockerRuntime.js"); 4 | 5 | 6 | /** 7 | * @swagger 8 | * /api/runtime/vscode-url: 9 | * get: 10 | * tags: 11 | * - Runtime 12 | * summary: Get the VSCode URL for the running container 13 | * description: | 14 | * This endpoint retrieves the URL for accessing the VSCode instance running in the Docker container. 15 | * parameters: 16 | * - in: query 17 | * name: conversation_id 18 | * required: true 19 | * schema: 20 | * type: string 21 | * description: The ID of the conversation to retrieve messages for. 22 | * responses: 23 | * 200: 24 | * description: Successfully retrieved the VSCode URL 25 | * content: 26 | * application/json: 27 | * schema: 28 | * type: object 29 | * properties: 30 | * data: 31 | * type: object 32 | * properties: 33 | * url: 34 | * type: string 35 | * description: URL for accessing the VSCode instance 36 | * code: 37 | * type: integer 38 | * description: Status code 39 | * msg: 40 | * type: string 41 | * description: Message 42 | */ 43 | router.get('/vscode-url', async ({ query, response }) => { 44 | const { conversation_id } = query; 45 | let dir_name = '' 46 | if (conversation_id) { 47 | dir_name = 'Conversation_' + conversation_id.slice(0, 6); 48 | } 49 | 50 | const runtime = new DockerRuntime() 51 | const container = await runtime.connect_container() 52 | const container_info = await container.inspect() 53 | 54 | const vscode_port = Object.keys(container_info.NetworkSettings.Ports)[1].split('/')[0] 55 | 56 | const vscode_url = `http://localhost:${vscode_port}?folder=/workspace/${dir_name}`; 57 | return response.success({ url: vscode_url }); 58 | }); 59 | 60 | module.exports = exports = router.routes(); -------------------------------------------------------------------------------- /src/routers/search_provider_setting/index.js: -------------------------------------------------------------------------------- 1 | const router = require("koa-router")(); 2 | 3 | router.prefix("/api/search_provider_setting"); 4 | 5 | const modules = [ 6 | "setting", 7 | ] 8 | 9 | for (const module of modules) { 10 | try { 11 | router.use(require(`./${module}.js`)); 12 | } 13 | catch (error) { console.log(`load ${module} error`, error); } 14 | } 15 | 16 | module.exports = router.routes(); 17 | -------------------------------------------------------------------------------- /src/runtime/DockerRuntime.d.ts: -------------------------------------------------------------------------------- 1 | // LocalRuntime.d.ts 2 | export interface Memory { 3 | addMessage( 4 | role: string, 5 | content: string, 6 | type: string, 7 | memorized: boolean 8 | ): Promise; 9 | } 10 | 11 | export interface ActionResult { 12 | status: "success" | "failure" | string; // Extended allowed values 13 | content?: string | any[]; // If content may be an array, also add 14 | error?: any; 15 | stderr?: any; // If there are additional properties, also add 16 | } 17 | 18 | export interface Action { 19 | type: string; 20 | params: Record; 21 | } 22 | 23 | export interface DockerRuntime { 24 | memory: Memory; 25 | constructor(options?: { memory: Memory }); 26 | handle_memory(result: ActionResult, action: Action): Promise; 27 | execute_action(action: Action): Promise; 28 | write_code(action: Action): Promise; 29 | read_file(action: Action): Promise; 30 | } -------------------------------------------------------------------------------- /src/runtime/LocalRuntime.test.js: -------------------------------------------------------------------------------- 1 | require('module-alias/register'); 2 | require('dotenv').config(); 3 | const { params } = require('../agent/tools/browser_use'); 4 | const LocalRuntime = require('./LocalRuntime'); 5 | const LocalMemory = require('@src/agent/memory/LocalMemory'); 6 | 7 | const { resolveActions } = require("@src/utils/resolve"); 8 | 9 | const run = async () => { 10 | const memory = new LocalMemory({ taskId: 'demo' }); 11 | const runtime = new LocalRuntime({ memory }) 12 | // const content = ` 13 | // . 14 | // ls 15 | // 16 | // ` 17 | // const content = "I will analyze the Excel file content and create a webpage to display the statistics of large model applications. First, I need to read the Excel file content, then generate the corresponding webpage code.\n\n\nLLM Large Model Dialogue Applications.xlsx\n\n\nPlease confirm if the Excel file content is correct, then I will continue to generate the webpage display code."; 18 | const content = ` 19 | ./ 20 | ls 21 | 22 | ` 23 | // const content = ` ./ nohup python3 -m http.server 8000 & ` 24 | 25 | const actions = resolveActions(content); 26 | const action = actions[0]; 27 | console.log(action); 28 | const r = await runtime.execute_action(action); 29 | console.log(r); 30 | } 31 | 32 | run(); -------------------------------------------------------------------------------- /src/runtime/browser.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | async function browser(action, uuid) { 4 | const host = 'localhost' 5 | const host_port = 9000 6 | 7 | const request = { 8 | method: 'POST', 9 | url: `http://${host}:${host_port}/api/browser/task`, 10 | data: { prompt: action.params.question, llm_config: action.params.llm_config }, 11 | }; 12 | const response = await axios(request); 13 | //extracted_content 14 | const result_content = response.data.data.history.task; 15 | return { 16 | uuid, 17 | status: 'success', 18 | content: result_content, 19 | meta: { 20 | action_type: 'browser', 21 | json: { browser_history: response.data.data.history.browser_history, browser_history_screenshot: response.data.data.history.browser_history_screenshot } 22 | } 23 | }; 24 | } 25 | 26 | 27 | module.exports = browser; -------------------------------------------------------------------------------- /src/runtime/plugins/vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorTheme": "Default Dark Modern", 3 | "workbench.startupEditor": "none" 4 | } 5 | -------------------------------------------------------------------------------- /src/runtime/runtime.util.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const WORKSPACE_DIR = path.join(__dirname, '../../', process.env.WORKSPACE_DIR || 'workspace'); 4 | const resolveWorkspaceDir = async () => { 5 | return WORKSPACE_DIR; 6 | } 7 | 8 | /** 9 | * restrict filepath to workspace dir 10 | * @param {string} filepath 11 | * @returns {Promise} 12 | */ 13 | const restrictFilepath = async (filepath) => { 14 | const workspace_dir = await resolveWorkspaceDir(); 15 | 16 | const resolvedPath = path.resolve(filepath); 17 | const resolvedWorkspace = path.resolve(workspace_dir); 18 | if (resolvedPath.startsWith(resolvedWorkspace)) { 19 | filepath = resolvedPath; 20 | } else { 21 | filepath = path.resolve(workspace_dir, filepath); 22 | } 23 | return filepath; 24 | } 25 | 26 | module.exports = { 27 | resolveWorkspaceDir, 28 | restrictFilepath 29 | } -------------------------------------------------------------------------------- /src/runtime/terminal_run.js: -------------------------------------------------------------------------------- 1 | const { exec, spawn } = require('child_process'); 2 | const { restrictFilepath } = require('./runtime.util'); 3 | 4 | const runCommand = (command, args, cwd) => { 5 | return new Promise((resolve, reject) => { 6 | if (Array.isArray(args)) { 7 | args = args.join(' '); 8 | } 9 | const fullCommand = `${command} ${args}`; 10 | console.log('fullCommand', fullCommand, 'cwd', cwd); 11 | 12 | // Handle nohup command 13 | if (command.includes('nohup')) { 14 | // Use shell to execute nohup command 15 | const child = spawn('sh', ['-c', fullCommand], { 16 | cwd, 17 | detached: true, 18 | stdio: ['ignore', 'ignore', 'ignore'] // Ignore all standard input output 19 | }); 20 | child.unref(); // Allow parent process to exit independently of child process 21 | resolve({ 22 | stdout: `Background process started, PID: ${child.pid}, output redirected to nohup.out`, 23 | stderr: '' 24 | }); 25 | } else { 26 | exec(fullCommand, { cwd }, (error, stdout, stderr) => { 27 | if (error) { 28 | reject({ error: error.message, stderr }); 29 | return; 30 | } 31 | resolve({ stdout, stderr }); 32 | }); 33 | } 34 | }); 35 | } 36 | 37 | const terminal_run = async (action, uuid) => { 38 | const { command, args = [], cwd = '.' } = action.params; 39 | const executionDir = await restrictFilepath(cwd); 40 | try { 41 | const result = await runCommand(command, args, executionDir); 42 | return { 43 | uuid, 44 | status: 'success', 45 | content: result.stdout || 'Execution result has no return content', 46 | stderr: result.stderr, 47 | meta: { 48 | action_type: action.type, 49 | } 50 | }; 51 | } catch (e) { 52 | console.error('Error executing command:', e); 53 | return { status: 'failure', error: e.stderr || e.message, content: '' }; 54 | } 55 | } 56 | 57 | module.exports = terminal_run; 58 | 59 | -------------------------------------------------------------------------------- /src/runtime/utils/system.js: -------------------------------------------------------------------------------- 1 | const net = require('net'); 2 | 3 | async function find_available_tcp_port(min_port = 30000, max_port = 39999, max_attempts = 10) { 4 | 5 | /** 6 | Find an available TCP port in a specified range. 7 | 8 | Args: 9 | min_port (int): The lower bound of the port range (default: 30000) 10 | max_port (int): The upper bound of the port range (default: 39999) 11 | max_attempts (int): Maximum number of attempts to find an available port (default: 10) 12 | 13 | Returns: 14 | int: An available port number, or -1 if none found after max_attempts 15 | 16 | */ 17 | let port = Math.floor(Math.random() * (max_port - min_port + 1)) + min_port; 18 | let attempts = 0; 19 | while (attempts < max_attempts) { 20 | return new Promise((resolve, reject) => { 21 | const server = net.createServer(); 22 | server.listen(port, () => { 23 | server.close(() => resolve(port)); 24 | }); 25 | server.on('error', () => { 26 | attempts++; 27 | if (attempts === max_attempts) { 28 | resolve(-1); 29 | } else { 30 | port = Math.floor(Math.random() * (max_port - min_port + 1)) + min_port; 31 | } 32 | }); 33 | }); 34 | } 35 | } 36 | 37 | module.exports = { 38 | find_available_tcp_port 39 | }; -------------------------------------------------------------------------------- /src/runtime/utils/tools.js: -------------------------------------------------------------------------------- 1 | const { restrictFilepath } = require('../runtime.util'); 2 | const fs = require('fs').promises;; 3 | const path = require('path'); 4 | 5 | const write_file = async (filepath, content) => { 6 | // Ensure the directory exists 7 | const dir = path.dirname(filepath); 8 | try { 9 | await fs.mkdir(dir, { recursive: true }); 10 | } catch (err) { 11 | if (err.code !== 'EXIST') { 12 | throw err; 13 | } 14 | } 15 | return fs.writeFile(filepath, content); 16 | } 17 | 18 | const write_code = async (action, uuid) => { 19 | let { path: filepath, content } = action.params; 20 | filepath = await restrictFilepath(filepath); 21 | await write_file(filepath, content); 22 | // const result = await executeCode(filepath); 23 | // return result; 24 | return { 25 | uuid, 26 | status: 'success', 27 | content: `File ${filepath} written successfully.`, 28 | meta: { 29 | action_type: action.type, 30 | filepath 31 | } 32 | }; 33 | } 34 | 35 | module.exports = { 36 | write_code 37 | }; -------------------------------------------------------------------------------- /src/swagger/swagger.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')() // Import router function 2 | const swaggerJSDoc = require('swagger-jsdoc') 3 | const path = require('path') 4 | const swaggerDefinition = { 5 | openapi: '3.0.0', 6 | info: { 7 | title: 'chataa', 8 | version: '1.0.0', 9 | description: 'API', 10 | }, 11 | }; 12 | const options = { 13 | swaggerDefinition, 14 | apis: [path.join(__dirname, '../routers/*/*.js')], // Write the address where the router with annotations is stored, best path.join() 15 | }; 16 | const swaggerSpec = swaggerJSDoc(options) 17 | // Get the generated annotations file through the router 18 | router.get('/swagger.json', async function (ctx) { 19 | ctx.set('Content-Type', 'application/json'); 20 | ctx.body = swaggerSpec; 21 | }) 22 | module.exports = router -------------------------------------------------------------------------------- /src/tools/browser.js: -------------------------------------------------------------------------------- 1 | const browser = { 2 | name: "browser", 3 | description: "Interact with the browser. Use it ONLY when you need to interact with a webpage.", 4 | params: { 5 | type: "object", 6 | properties: { 7 | question: { 8 | description: "What you want to do with a browser", 9 | type: "string" 10 | } 11 | }, 12 | required: ["question"] 13 | }, 14 | getActionDescription({ question }) { 15 | return question; 16 | } 17 | }; 18 | 19 | module.exports = browser; -------------------------------------------------------------------------------- /src/tools/impl/web_search/LocalSearch.run.js: -------------------------------------------------------------------------------- 1 | 2 | const LocalSearchServer = require('./LocalSearch'); 3 | 4 | (async () => { 5 | try { 6 | // 打开多个 URL,复用 browser 和 context 7 | // const result = await LocalSearchServer.search('Travel to Japan in April',{ uid: 'user1',max_results: 3 , engine : 'bing'}); 8 | // 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 9 | const tools = new LocalSearchServer() 10 | const result = await tools.search('北京今日天气',{ uid: 'user1',max_results: 3 , engine : 'baidu'}); 11 | let r = await tools.formatContent() 12 | console.log(r) 13 | /** 14 | 输入示例: 15 | args: 16 | query: string 17 | options: { 18 | uid: string, // 用户 ID default: 'default' 19 | max_results: number, // 最大结果数 default: 3 20 | engine: string, // 搜索引擎 bing or baidu; default: 'bing' 21 | } 22 | 输出示例: 23 | args: 24 | result:{ 25 | query: string 26 | results: list of { title: string, url: string, content: string } 27 | }; 28 | 29 | */ 30 | //console.log('结果', result); 31 | // // 保存 HTML 到文件 32 | console.log('\n\n\n-------------------------------------------------------------------------------------------------------------------------------------'); 33 | console.log('Closed user1 context.'); 34 | // 清理所有资源 35 | await tools.cleanup(); 36 | } catch (error) { 37 | console.error('Error:', error); 38 | } 39 | })(); -------------------------------------------------------------------------------- /src/tools/impl/web_search/TalivySearch.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | // const { resolveAxiosInstance } = require('@utils/network'); 3 | const HOST = 'https://api.tavily.com/search'; 4 | 5 | class TalivySearch { 6 | constructor({ key: API_KEY }) { 7 | this.API_KEY = API_KEY; 8 | this.baseUrl = HOST; 9 | } 10 | 11 | async search(query, options = {}) { 12 | const defaultOptions = { 13 | topic: 'general', 14 | search_depth: 'basic', 15 | max_results: 1, 16 | include_raw_content: true, 17 | include_images: false, 18 | include_image_descriptions: false, 19 | include_domains: [], 20 | exclude_domains: [] 21 | }; 22 | 23 | const requestOptions = { 24 | ...defaultOptions, 25 | ...options, 26 | query 27 | }; 28 | 29 | try { 30 | // @ts-ignore 31 | const response = await axios.post(this.baseUrl, requestOptions, { 32 | headers: { 33 | 'Authorization': `Bearer ${this.API_KEY}`, 34 | 'Content-Type': 'application/json' 35 | } 36 | }); 37 | 38 | this.result = response.data 39 | return this.result; 40 | } catch (error) { 41 | console.error('TalivySearch error:', error); 42 | throw error; 43 | } 44 | } 45 | 46 | async formatContent() { 47 | const { query, num_results, results = [] } = this.result; 48 | const list = [] 49 | for (const item of results) { 50 | const description = `URL: ${item.url}\nTitle: ${item.title}\nContent: ${item.content}\n`; 51 | list.push(description); 52 | } 53 | return list.join('======\n======'); 54 | } 55 | 56 | async formatJSON() { 57 | const { results = [] } = this.result; 58 | return results; 59 | } 60 | } 61 | 62 | module.exports = TalivySearch; -------------------------------------------------------------------------------- /src/tools/impl/web_search/TalivySearch.run.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | require('module-alias/register') 3 | 4 | const TalivySearch = require('@src/tools/impl/web_search/TalivySearch.js') 5 | 6 | const run = async () => { 7 | const tool = new TalivySearch({ key: process.env.TALIVY_API_KEY }) 8 | const result = await tool.search('北京今日天气', { max_results: 3 }) 9 | // console.log(JSON.stringify(result, null, 2)) 10 | const formatted = await tool.formatContent() 11 | console.log(formatted) 12 | } 13 | 14 | run() -------------------------------------------------------------------------------- /src/tools/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | /** 5 | * @typedef {import('types/Tool').Tool } Tool 6 | */ 7 | 8 | /** @type {Object.} */ 9 | const tools = {}; 10 | const ignored = new Set(['browser_use']); 11 | 12 | const files = fs.readdirSync(__dirname); 13 | const filterFn = file => { 14 | return (file.indexOf('.') !== 0) && (file !== path.basename(__filename)) && (file.slice(-3) === '.js'); 15 | } 16 | for (const file of files.filter(filterFn)) { 17 | try { 18 | const def = require(path.join(__dirname, file)); 19 | const tool = def.name || path.basename(file, '.js'); 20 | if (ignored.has(tool)) continue 21 | tools[tool] = def; 22 | } catch (error) { 23 | console.error(`Error loading from ${file}:`, error); 24 | } 25 | } 26 | 27 | module.exports = tools; -------------------------------------------------------------------------------- /src/tools/read_file.js: -------------------------------------------------------------------------------- 1 | const read_file = { 2 | name: "read_file", 3 | description: "Read the content of a specified file path and return, supports txt, md, xlsx, json formats", 4 | params: { 5 | type: "object", 6 | properties: { 7 | path: { 8 | description: "The path of the file to read.", 9 | type: "string" 10 | } 11 | }, 12 | required: ["path"] 13 | }, 14 | getActionDescription({ path }) { 15 | return path; 16 | } 17 | }; 18 | 19 | module.exports = read_file; -------------------------------------------------------------------------------- /src/tools/terminal_run.js: -------------------------------------------------------------------------------- 1 | const TerminalRun = { 2 | name: "terminal_run", 3 | description: "Execute a specified command in the terminal and return the result, use nohup http.server to serve web html file", 4 | params: { 5 | type: "object", 6 | properties: { 7 | command: { 8 | description: "The command to execute", 9 | type: "string" 10 | }, 11 | args: { 12 | description: "Command arguments list", 13 | type: "string", 14 | }, 15 | cwd: { 16 | description: "Command working directory", 17 | type: "string" 18 | } 19 | }, 20 | required: ["command"] 21 | }, 22 | getActionDescription({ command, args = "", cwd }) { 23 | return `${command} ${args}`; 24 | } 25 | }; 26 | 27 | module.exports = TerminalRun; -------------------------------------------------------------------------------- /src/tools/write_code.js: -------------------------------------------------------------------------------- 1 | const WriteCode = { 2 | name: "write_code", 3 | description: "Write html/node/python code to complete task, use python3 code with PyPDF2 to read PDF files", 4 | params: { 5 | type: "object", 6 | properties: { 7 | path: { 8 | description: "The path of the file to write.", 9 | type: "string" 10 | }, 11 | content: { 12 | description: "The code content to write.", 13 | type: "string" 14 | } 15 | }, 16 | required: ["path", "content"] 17 | }, 18 | getActionDescription({ path }) { 19 | return path; 20 | } 21 | }; 22 | 23 | module.exports = WriteCode; -------------------------------------------------------------------------------- /src/utils/default_model.js: -------------------------------------------------------------------------------- 1 | require("module-alias/register"); 2 | const DefaultModelSetting = require('@src/models/DefaultModelSetting'); 3 | const Model = require('@src/models/Model'); 4 | const Plantform = require('@src/models/Platform'); 5 | 6 | const _defaultModelCache = {}; 7 | 8 | const _fetchDefaultModel = async (type = 'assistant') => { 9 | const defaultModelSetting = await DefaultModelSetting.findOne({ where: { setting_type: type } }); 10 | if (!defaultModelSetting) return null; 11 | const model = await Model.findOne({ where: { id: defaultModelSetting.dataValues.model_id } }); 12 | if (!model) return null; 13 | const model_name = model.dataValues.model_id; 14 | const platform = await Plantform.findOne({ where: { id: model.dataValues.platform_id } }); 15 | if (!platform) return null; 16 | 17 | const api_key = platform.dataValues.api_key; 18 | const base_url = platform.dataValues.api_url 19 | let api_url = platform.dataValues.api_url; 20 | if (type === 'assistant') { 21 | api_url = platform.dataValues.api_url + '/chat/completions'; 22 | } 23 | const platform_name = platform.dataValues.name; 24 | 25 | return { model_name, platform_name, api_key, api_url, base_url: base_url }; 26 | }; 27 | 28 | const getDefaultModel = async (type = 'assistant') => { 29 | if (_defaultModelCache[type]) { 30 | return _defaultModelCache[type]; 31 | } 32 | const modelInfo = await _fetchDefaultModel(type); 33 | if (modelInfo) { 34 | _defaultModelCache[type] = modelInfo; 35 | } 36 | return modelInfo; 37 | }; 38 | 39 | const updateDefaultModel = async (type = 'assistant') => { 40 | const modelInfo = await _fetchDefaultModel(type); 41 | if (modelInfo) { 42 | _defaultModelCache[type] = modelInfo; 43 | } 44 | return modelInfo; 45 | }; 46 | 47 | module.exports = { 48 | getDefaultModel, 49 | updateDefaultModel, 50 | }; -------------------------------------------------------------------------------- /src/utils/function.call.js: -------------------------------------------------------------------------------- 1 | require('module-alias/register'); 2 | require('dotenv').config(); 3 | 4 | const tools = require("@src/tools/index"); 5 | 6 | const convertTool = (tool) => { 7 | const fn = { 8 | type: 'function', 9 | function: { 10 | name: tool.name, 11 | description: tool.description, 12 | parameters: tool.params 13 | } 14 | } 15 | return fn; 16 | } 17 | 18 | const resolveFunctionCall = async () => { 19 | const list = Object.values(tools); 20 | // @ts-ignore 21 | return list.map(convertTool); 22 | } 23 | 24 | module.exports = exports = resolveFunctionCall; -------------------------------------------------------------------------------- /src/utils/json.js: -------------------------------------------------------------------------------- 1 | const resolveThinking = require('@src/utils/thinking.js') 2 | 3 | const parseJSON = (content) => { 4 | 5 | content = content.trim(); 6 | if (content.startsWith('')) { 7 | const { thinking: _, content: output } = resolveThinking(content); 8 | content = output; 9 | } 10 | 11 | // 检查是否包含 JSON 代码块 12 | const jsonBlockMatch = content.match(/```json\s*\n?([\s\S]*?)\n?\s*```/); 13 | if (jsonBlockMatch) { 14 | try { 15 | const jsonContent = jsonBlockMatch[1].trim(); 16 | // console.log('Extracted JSON:', jsonContent); 17 | return JSON.parse(jsonContent); 18 | } catch (parseErr) { 19 | console.log('JSON parse error in code block:', parseErr); 20 | throw new Error(`parseJSON failed (code block): ${parseErr.message}`); 21 | } 22 | } 23 | 24 | // 尝试直接解析整个内容 25 | try { 26 | return JSON.parse(content); 27 | } catch (err) { 28 | if (content === 'ERR_BAD_REQUEST') { 29 | throw new Error(`Large model call failed`); 30 | } else { 31 | console.log('Direct parse error:', err); 32 | throw new Error(`parseJSON failed: ${err.message}`); 33 | } 34 | } 35 | } 36 | 37 | module.exports = exports = parseJSON; 38 | 39 | // const fs = require('fs'); 40 | // const filepath = require('path').resolve(__dirname, './json.txt'); 41 | // const content = fs.readFileSync(filepath, 'utf8'); 42 | // console.log((content)); 43 | // const json = parseJSON(content); 44 | // console.log('json', json); -------------------------------------------------------------------------------- /src/utils/jwt.js: -------------------------------------------------------------------------------- 1 | // https://github.com/auth0/node-jsonwebtoken 2 | const jwt = require('jsonwebtoken'); 3 | const JWT_SECRET = process.env.JWT_SECRET || 'local'; 4 | 5 | const encodeToken = (info = {}) => { 6 | const token = jwt.sign(info, JWT_SECRET); 7 | return token; 8 | } 9 | 10 | const decodeToken = token => { 11 | try { 12 | const decoded = jwt.verify(token, JWT_SECRET); 13 | return decoded; 14 | } catch (error) { 15 | return null; 16 | } 17 | } 18 | 19 | module.exports = exports = { 20 | encodeToken, 21 | decodeToken 22 | }; -------------------------------------------------------------------------------- /src/utils/message.js: -------------------------------------------------------------------------------- 1 | // utils/message.js 2 | const MessageTable = require('@src/models/Message'); 3 | class Message { 4 | /** 5 | * 构建消息格式 6 | * @param {Object} params 参数对象 7 | * @param {('success'|'failure'|'running')} params.status 成功或失败 8 | * @param {string} [params.content] 文本内容 9 | * @param {string} [params.task_id] 10 | * @param {('plan'|'task'|'auto_reply'|'finish'|'search'|'file'|'terminal'|'todo'|'browser'|'question'|'finish_summery'|'')} [params.action_type] 11 | * @param {string} [params.filepath] 12 | * @param {string} [params.url] 13 | * @param {Array} [params.json] 14 | * @param {string} [params.comments] 15 | * @param {boolean} [params.memorized] 16 | * @param {string} [params.role] 17 | * @param {string} [params.uuid] 18 | * @param {string} [params.meta_content] 19 | * @returns {Object} 20 | */ 21 | static format({ status, content = '', task_id = '', action_type = '', filepath = '', url = '', json = [], comments = '', memorized = '', uuid = '', role = 'assistant', meta_content = '' }) { 22 | return { 23 | role, 24 | uuid, 25 | status, 26 | content, 27 | comments, 28 | memorized, 29 | timestamp: new Date().valueOf(), 30 | meta: { 31 | task_id, 32 | action_type, 33 | filepath, 34 | url, 35 | json, 36 | content: meta_content 37 | } 38 | }; 39 | } 40 | 41 | /** 42 | * 存储消息到数据库 43 | * @param {Object} messageData 消息数据(与 format 返回结构相同) 44 | * @returns {Promise} 45 | */ 46 | static async saveToDB(messageData, conversation_id) { 47 | try { 48 | return await MessageTable.create({ 49 | role: messageData.role, 50 | uuid: messageData.uuid, 51 | conversation_id: conversation_id, 52 | timestamp: messageData.timestamp, 53 | status: messageData.status, 54 | content: messageData.content, 55 | meta: JSON.stringify(messageData.meta), 56 | comments: messageData.comments, 57 | memorized: messageData.memorized, 58 | }); 59 | } catch (err) { 60 | console.error('保存消息失败:', err); 61 | throw err; 62 | } 63 | } 64 | } 65 | 66 | module.exports = Message; 67 | -------------------------------------------------------------------------------- /src/utils/network.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | const proxy = { 4 | protocol: process.env.PROXY_PROTOCOL || 'http', 5 | host: process.env.PROXY_HOST, 6 | port: process.env.PROXY_PROT 7 | } 8 | 9 | const resolveAxiosInstance = () => { 10 | if (process.env.PROXY_HOST) { 11 | const instance = axios.create({ 12 | proxy: proxy 13 | }); 14 | return instance; 15 | } 16 | return axios.create({}); 17 | } 18 | 19 | module.exports = exports = { 20 | resolveAxiosInstance 21 | } -------------------------------------------------------------------------------- /src/utils/planning.js: -------------------------------------------------------------------------------- 1 | const call = require('@src/utils/llm.js'); 2 | const resolveTodoPrompt = require('@src/agent/prompt/generate_todo.js'); 3 | 4 | const getTodoMd = async (taskData) => { 5 | const markdownOutput = convertArrayToMarkdownTodo(taskData); 6 | return markdownOutput 7 | 8 | // const prompt = resolveTodoPrompt(JSON.stringify(taskData)); 9 | // return await call(prompt) 10 | } 11 | 12 | function convertArrayToMarkdownTodo(data) { 13 | let markdown = "## TODO List\n"; 14 | data.forEach(item => { 15 | // 假设 "pending" 状态对应未勾选,其他状态(如果存在)可以按需求处理 16 | const checkbox = item.status === "pending" ? "[ ]" : "[x]"; 17 | markdown += `- ${checkbox} ${item.title}: ${item.description}\n`; 18 | }); 19 | return markdown; 20 | } 21 | 22 | module.exports = exports = { 23 | getTodoMd 24 | } -------------------------------------------------------------------------------- /src/utils/resolve.js: -------------------------------------------------------------------------------- 1 | // https://github.com/NaturalIntelligence/fast-xml-parser 2 | const { XMLParser, XMLBuilder, XMLValidator } = require("fast-xml-parser"); 3 | 4 | const resolveXML = (content) => { 5 | const parser = new XMLParser({ 6 | stopNodes: ["write_code.content"], 7 | ignoreAttributes: false, 8 | }); 9 | const result = parser.parse(content); 10 | // console.log(result) 11 | return result; 12 | } 13 | 14 | const resolveActions = xml => { 15 | try { 16 | const resolved = resolveXML(xml); 17 | const actions = [] 18 | for (let key in resolved) { 19 | const value = resolved[key]; 20 | const action = { 21 | type: key, 22 | params: value 23 | } 24 | actions.push(action); 25 | } 26 | return actions; 27 | } catch (err) { 28 | console.log(err); 29 | return []; 30 | } 31 | } 32 | 33 | module.exports = { 34 | resolveXML, 35 | resolveActions 36 | }; -------------------------------------------------------------------------------- /src/utils/stream.util.js: -------------------------------------------------------------------------------- 1 | const { PassThrough } = require("stream"); 2 | const { v4: uuidv4 } = require("uuid"); 3 | 4 | const handleStream = (responseType = 'sse', response, debug = true) => { 5 | const stream = new PassThrough(); 6 | let onTokenStream = new Function(); 7 | 8 | if (responseType === "openai-sse") { 9 | // 设置响应头 response 10 | response.type = "text/event-stream"; 11 | response.set("Cache-Control", "no-cache"); 12 | response.set("Connection", "keep-alive"); 13 | onTokenStream = (token, model = "gpt") => { 14 | debug && process.stdout.write(token); 15 | if (typeof token === "object") { 16 | token = JSON.stringify(token); 17 | } 18 | const encoded = JSON.stringify({ 19 | id: uuidv4(), 20 | object: "chat.completion.chunk", 21 | created: parseInt((Date.now() / 1000).toFixed(0)), 22 | model: model, 23 | choices: [ 24 | { 25 | index: 0, 26 | delta: { role: "assistant", content: token }, 27 | finish_reason: null, 28 | } 29 | ] 30 | }); 31 | stream.write(`data: ${encoded}\n\n`); 32 | }; 33 | } 34 | 35 | if (responseType === 'sse') { 36 | // 设置响应头 response 37 | response.type = "text/event-stream"; 38 | response.set("Cache-Control", "no-cache"); 39 | response.set("Connection", "keep-alive"); 40 | onTokenStream = (token) => { 41 | // console.log('token', token); 42 | if (typeof token === 'object') { 43 | token = JSON.stringify(token); 44 | debug && process.stdout.write(token); 45 | } 46 | const encoded = Buffer.from(token).toString("base64"); 47 | // const encoded = token; 48 | stream.write("event: message\n"); 49 | stream.write(`data: ${encoded}\n\n`); 50 | }; 51 | } 52 | 53 | if (responseType === 'stream') { 54 | // 设置响应头 55 | response.set("Content-Type", "text/plain"); 56 | response.set("Transfer-Encoding", "chunked"); 57 | onTokenStream = (token) => { 58 | stream.write(token); 59 | }; 60 | } 61 | 62 | return { stream, onTokenStream }; 63 | } 64 | 65 | module.exports = exports = handleStream -------------------------------------------------------------------------------- /src/utils/thinking.js: -------------------------------------------------------------------------------- 1 | // resolve thinking content and output content 2 | const resolveThinking = (content) => { 3 | content = content.trim(); 4 | let thinking = ''; 5 | let output = ''; 6 | if (content.startsWith('') && content.indexOf('') !== -1) { 7 | const end = content.indexOf(''); 8 | thinking = content.slice(0, end + 8).trim(); 9 | output = content.slice(end + 8).trim(); 10 | } 11 | return { thinking, content: output }; 12 | } 13 | 14 | module.exports = resolveThinking; -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | const validateOptions = (options, rules) => { 2 | // console.log(Object.keys(rules)); 3 | for (const key in rules) { 4 | const rule = rules[key]; 5 | // 自定义校验规则 6 | if (typeof rule.message === 'function') { 7 | const message = rule.message(options, key) 8 | if (message) { 9 | return message 10 | } 11 | } 12 | // 必填校验 13 | if (rule.required && !options[key]) { 14 | return rule.message || `请告诉我 ${key} 的相关信息`; 15 | } 16 | } 17 | } 18 | 19 | module.exports = exports = { 20 | validateOptions 21 | }; -------------------------------------------------------------------------------- /types/LocalRuntime.d.ts: -------------------------------------------------------------------------------- 1 | // LocalRuntime.d.ts 2 | export interface Memory { 3 | addMessage( 4 | role: string, 5 | content: string, 6 | type: string, 7 | memorized: boolean, 8 | meta: any 9 | ): Promise; 10 | } 11 | 12 | export interface ActionResult { 13 | uuid?: string; 14 | status: "success" | "failure" | string; // Extended allowed values 15 | content: string; // Text content 16 | error?: any; 17 | stderr?: any; // Additional properties can be added if needed 18 | memorized?: boolean; // Whether the result is memorized 19 | meta?: any; // Extended properties 20 | } 21 | 22 | export interface Action { 23 | type: string; 24 | params: Record; 25 | } 26 | 27 | export interface LocalRuntime { 28 | memory: Memory; 29 | constructor(options?: { memory: Memory }); 30 | handle_memory(result: ActionResult, action: Action): Promise; 31 | execute_action(action: Action): Promise; 32 | write_code(action: Action): Promise; 33 | read_file(action: Action): Promise; 34 | } 35 | --------------------------------------------------------------------------------