├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── bug_report.zh.yml └── workflows │ ├── build-mac-x64.yml │ ├── build.yml │ ├── check-version.yml │ ├── doc-translate.yml │ ├── docker-release.yml │ ├── docker.yml │ ├── npm-publish-release.yml │ ├── npm-publish.yml │ └── test-build.yml ├── .gitignore ├── .vscode └── launch.json ├── ChangeLog.md ├── ChangeLog.zh.md ├── LICENSE ├── README.md ├── README.zh.md ├── common └── data.ts ├── dependencies └── .gitignore ├── docker ├── electron │ ├── Dockerfile │ ├── build.sh │ ├── docker-compose.yml │ └── start.sh └── no_electron │ ├── Dockerfile │ ├── Dockerfile-Alpha │ ├── docker-compose.yml │ └── hg.Dockerfile ├── docs ├── .gitignore ├── .vuepress │ └── config.js ├── README.md ├── doc.md └── guide │ ├── advanced.md │ ├── builtin-mcp.md │ ├── image-1.png │ ├── image-10.png │ ├── image-11.png │ ├── image-12.png │ ├── image-13.png │ ├── image-14.png │ ├── image-15.png │ ├── image-16.png │ ├── image-17.png │ ├── image-2.png │ ├── image-3.png │ ├── image-4.png │ ├── image-5.png │ ├── image-6.png │ ├── image-7.png │ ├── image-8.png │ ├── image-9.png │ ├── image.png │ ├── mcp.md │ ├── quick-start.md │ └── var.md ├── electron ├── .gitignore ├── README.md ├── build │ ├── mac │ │ └── entitlements.mac.plist │ ├── notarize.js │ └── remove-x64-latest-mac-yml.js ├── cli.js ├── package-lock.json ├── package.json ├── package.nodejs.json ├── task.mts ├── ts │ ├── command.mts │ ├── command_history.mts │ ├── common │ │ ├── autoLauncher.mts │ │ ├── checkport.mts │ │ ├── closeport.mts │ │ ├── data.mts │ │ ├── dataConstructor.mts │ │ ├── event.ts │ │ ├── execFallback.mts │ │ ├── model.mts │ │ ├── progress.mts │ │ ├── request.mts │ │ ├── types.mts │ │ ├── util.mts │ │ └── webdav.mts │ ├── const.mts │ ├── demo.mts │ ├── dependencies.mts │ ├── es6.mts │ ├── first.mts │ ├── langchain │ │ ├── index.mts │ │ ├── loader │ │ │ └── pdf.mts │ │ ├── myhnswlib.mts │ │ ├── stores │ │ │ └── doc │ │ │ │ ├── base.mts │ │ │ │ └── in_memory.mts │ │ ├── textsplitters.mts │ │ └── vectorStore.mts │ ├── main.mts │ ├── main_no_electron.mts │ ├── mcp │ │ ├── claude.mts │ │ ├── config.mts │ │ ├── servers │ │ │ ├── KnowledgeBase │ │ │ │ └── index.mts │ │ │ ├── Task │ │ │ │ └── index.mts │ │ │ ├── express.mts │ │ │ ├── gateway │ │ │ │ └── index.mts │ │ │ ├── hyper_tools │ │ │ │ ├── electron.mts │ │ │ │ ├── index.mts │ │ │ │ ├── lib.mts │ │ │ │ ├── no_electron.mts │ │ │ │ ├── web1.mts │ │ │ │ └── web2.mts │ │ │ ├── index.mts │ │ │ ├── settings │ │ │ │ ├── index.mts │ │ │ │ ├── lib.mts │ │ │ │ └── settings.mts │ │ │ └── terminal │ │ │ │ ├── index.mts │ │ │ │ ├── lib.mts │ │ │ │ └── terminal.mts │ │ ├── task.mts │ │ └── utils.mts │ ├── mcpGateWay.mts │ ├── message_service.mts │ ├── mianWindow.mts │ ├── polyfills │ │ ├── electron.mts │ │ ├── electron_autoupdate.mts │ │ ├── index.mts │ │ ├── no_electron.mts │ │ └── polyfills.mts │ ├── preload.ts │ ├── rag │ │ ├── lib.mts │ │ └── vectorStore.mts │ ├── upload.mts │ └── websocket.mts ├── tsconfig.json ├── types │ └── index.d.ts ├── webpack.config.js └── webpack.no_electron.js ├── images ├── image1.png ├── image11.png ├── image12.png ├── image13.png ├── image14.png ├── image2.png ├── image20.png ├── image21.png ├── image22.png ├── image3.png ├── image30.png ├── image31.png ├── image32.png ├── image33.png ├── image34.png ├── image35.png ├── image36.png ├── image40.png ├── image41.png ├── image42.png ├── image43.png ├── image44.png ├── image45.png ├── image46.png ├── image47.png ├── image48.png ├── image50.png ├── image51.png ├── image52.png ├── image53.png ├── image55.png ├── image60.png ├── image61.png └── image62.png ├── package-lock.json ├── package.json ├── task.mts ├── translate.mts ├── tsconfig.json └── web ├── .gitignore ├── .prettierrc ├── README.md ├── eval ├── common.ts └── markdown.ts ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── iconfont.js ├── index.html ├── logo.png ├── logo2.png ├── manifest.json ├── offline.html └── robots.txt ├── src ├── App.css ├── App.tsx ├── common │ ├── agent.ts │ ├── ai │ │ ├── ai.ts │ │ └── prompt.ts │ ├── ai_provider │ │ └── anthropic.ts │ ├── call.ts │ ├── callReplaceCommand.tsx │ ├── code.tsx │ ├── config.ts │ ├── const.ts │ ├── context.ts │ ├── data.ts │ ├── dndTable.tsx │ ├── event.ts │ ├── index.ts │ ├── input_plus.tsx │ ├── loading.js │ ├── mcp.ts │ ├── mcptool.ts │ ├── mock.ts │ ├── numberStep.tsx │ ├── openai-compatibility.ts │ ├── openai.ts │ ├── openai_image_base64.txt │ ├── progress.tsx │ ├── request.ts │ ├── resolve.ts │ ├── selectFile.tsx │ ├── service.ts │ ├── sleep.ts │ └── util.tsx ├── components │ ├── ai.ts │ ├── assistant_tool_content.tsx │ ├── editor.tsx │ ├── icon.tsx │ ├── input_ai.tsx │ ├── messages.tsx │ ├── my_sender.tsx │ ├── pre.tsx │ ├── provider_list.tsx │ └── user_content.tsx ├── i18n.json ├── i18n.ts ├── index.tsx ├── layout.tsx ├── logo.svg ├── pages │ ├── Agent │ │ ├── create.tsx │ │ └── index.tsx │ ├── TestPage │ │ └── index.tsx │ ├── Toolbox │ │ ├── Toolbox.tsx │ │ ├── Transcribe.tsx │ │ ├── generateImage.tsx │ │ └── generateSpeech.tsx │ ├── chat │ │ ├── attachR.tsx │ │ ├── chat.tsx │ │ ├── component.tsx │ │ ├── index.tsx │ │ ├── promptsModal.tsx │ │ └── sortableItem.tsx │ ├── hyperAgent │ │ ├── TaskList.tsx │ │ ├── TaskResults.tsx │ │ ├── callAgentHistory.tsx │ │ ├── index.tsx │ │ └── newTaskModal.tsx │ ├── hypertools │ │ └── hypertools.tsx │ ├── knowledgeBase │ │ ├── index.tsx │ │ ├── knowledgeBase.tsx │ │ ├── knowledgeBaseModal.tsx │ │ └── knowledgeBaseResourceModal.tsx │ ├── market │ │ ├── gateway.tsx │ │ └── market.tsx │ ├── setting │ │ ├── index.tsx │ │ ├── sync.tsx │ │ └── terminal.tsx │ ├── variableList │ │ └── variableList.tsx │ └── workspace │ │ ├── chatspace.tsx │ │ ├── sessions.tsx │ │ └── workspace.tsx ├── router.tsx ├── sw.js └── tailwind.css ├── tailwind.config.js ├── task.mts ├── tests └── 1.mts ├── tsconfig.json ├── types └── index.d.ts ├── webpack.config.js └── webpack.eval.js /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛Bug Report (English) 2 | description: Create a report to help us improve 3 | title: '[Bug]: ' 4 | labels: ['bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Regarding the bug of the dialogue, click Settings, turn on the development mode, ![Download configuration](https://github.com/user-attachments/assets/7537941a-1e2d-41a8-abb6-2569fe040067), download the dialogue configuration and send it to me 10 | 11 | - type: dropdown 12 | id: platform 13 | attributes: 14 | label: Platform 15 | description: Which platform are you using? 16 | options: 17 | - Windows 18 | - macOS 19 | - Linux 20 | - Node.js 21 | - Docker 22 | validations: 23 | required: true 24 | 25 | - type: input 26 | id: version 27 | attributes: 28 | label: Version 29 | description: What version are you running? 30 | placeholder: e.g., v1.5.0 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | id: description 36 | attributes: 37 | label: Bug Description 38 | description: Please describe the issue in as much detail as possible. If possible, provide screenshots or screen recordings to help us better understand the problem. 39 | placeholder: Tell us what happened... (Remember to attach screenshots/recordings if applicable) 40 | validations: 41 | required: true 42 | 43 | - type: textarea 44 | id: additional 45 | attributes: 46 | label: Additional Information 47 | description: Anything that can give us more insight into the issue you are experiencing -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.zh.yml: -------------------------------------------------------------------------------- 1 | name: 🐛错误报告 (中文) 2 | description: 创建一个报告以帮助我们改进 3 | title: '[错误]: ' 4 | labels: ['bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 关于对话的Bug,点击设置,开启开发模式,![下载配置](https://github.com/user-attachments/assets/7537941a-1e2d-41a8-abb6-2569fe040067),下载对话配置发给我 10 | 11 | 12 | - type: dropdown 13 | id: platform 14 | attributes: 15 | label: 平台 16 | description: 您正在使用哪个平台? 17 | options: 18 | - Windows 19 | - macOS 20 | - Linux 21 | - Node.js 22 | - Docker 23 | validations: 24 | required: true 25 | 26 | - type: input 27 | id: version 28 | attributes: 29 | label: 版本 30 | description: 您正在运行的版本是什么? 31 | placeholder: 例如 v1.5.0 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: description 37 | attributes: 38 | label: 错误描述 39 | description: 描述问题时请尽可能详细。请尽可能提供截图或屏幕录制,以帮助我们更好地理解问题。 40 | placeholder: 告诉我们发生了什么...(记得附上截图/录屏,如果适用) 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | id: additional 46 | attributes: 47 | label: 附加信息 48 | description: 任何能让我们对你所遇到的问题有更多了解的东西 49 | -------------------------------------------------------------------------------- /.github/workflows/check-version.yml: -------------------------------------------------------------------------------- 1 | name: 检查版本 2 | 3 | on: 4 | workflow_call: 5 | outputs: 6 | should_release: 7 | description: "是否需要发布新版本" 8 | value: ${{ jobs.check.outputs.should_release }} 9 | package_version: 10 | description: "package.json 的版本" 11 | value: ${{ jobs.check.outputs.package_version }} 12 | 13 | jobs: 14 | check: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | should_release: ${{ steps.compare-versions.outputs.should_release }} 18 | package_version: ${{ steps.package-version.outputs.package_version }} 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: 获取package.json版本 23 | id: package-version 24 | run: | 25 | PACKAGE_VERSION=$(node -p "require('./package.json').version") 26 | echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT 27 | 28 | - name: 获取最新release版本 29 | id: latest-release 30 | run: | 31 | LATEST_RELEASE=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//') 32 | echo "latest_release=${LATEST_RELEASE:-''}" >> $GITHUB_OUTPUT 33 | continue-on-error: true 34 | 35 | - name: 获取npm上的最新版本 36 | id: npm-version 37 | run: | 38 | PACKAGE_NAME=$(node -p "require('./package.json').name") 39 | NPM_VERSION=$(npm view $PACKAGE_NAME version 2>/dev/null || echo "") 40 | echo "npm_version=${NPM_VERSION:-''}" >> $GITHUB_OUTPUT 41 | continue-on-error: true 42 | 43 | - name: 比较版本 44 | id: compare-versions 45 | run: | 46 | PACKAGE_VERSION="${{ steps.package-version.outputs.package_version }}" 47 | LATEST_RELEASE="${{ steps.latest-release.outputs.latest_release }}" 48 | NPM_VERSION="${{ steps.npm-version.outputs.npm_version }}" 49 | 50 | echo "Package version from package.json: $PACKAGE_VERSION" 51 | echo "Latest GitHub release version: $LATEST_RELEASE" 52 | echo "Latest NPM registry version: $NPM_VERSION" 53 | 54 | # Check if we need to release based on version comparisons 55 | if [ -z "$LATEST_RELEASE" ] || [ "$LATEST_RELEASE" = "null" ] || [ "$PACKAGE_VERSION" != "$LATEST_RELEASE" ]; then 56 | if [ -z "$NPM_VERSION" ] || [ "$NPM_VERSION" = "null" ] || [ "$PACKAGE_VERSION" != "$NPM_VERSION" ]; then 57 | echo "New version detected, should release" 58 | echo "should_release=true" >> $GITHUB_OUTPUT 59 | else 60 | echo "Package version already on NPM, skipping release" 61 | echo "should_release=false" >> $GITHUB_OUTPUT 62 | fi 63 | else 64 | echo "Package version matches an existing release, skipping" 65 | echo "should_release=false" >> $GITHUB_OUTPUT 66 | fi -------------------------------------------------------------------------------- /.github/workflows/doc-translate.yml: -------------------------------------------------------------------------------- 1 | name: doc-translate 2 | 3 | # on: [push, pull_request] 4 | on: 5 | workflow_dispatch: {} 6 | push: 7 | branches: 8 | - doc 9 | 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | doc-translate: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Check out git repository 19 | uses: actions/checkout@v4 20 | 21 | # - name: Check if commit message is "action-generated" 22 | # id: check_commit_message 23 | # run: | 24 | # COMMIT_MESSAGE=$(git log -1 --pretty=format:%s) 25 | # echo "Commit message: $COMMIT_MESSAGE" 26 | # if [[ "$COMMIT_MESSAGE" == "action-generated" ]]; then 27 | # echo "Skipping workflow for action-generated commit" 28 | # echo "is_generated=true" >> $GITHUB_OUTPUT 29 | # else 30 | # echo "is_generated=false" >> $GITHUB_OUTPUT 31 | # fi 32 | 33 | # - name: Exit workflow if action-generated commit 34 | # if: steps.check_commit_message.outputs.is_generated == 'true' 35 | # run: exit 78 36 | 37 | - name: Install Node.js 38 | uses: actions/setup-node@v3.0.0 39 | with: 40 | node-version: "20" 41 | registry-url: https://registry.npmjs.org/ 42 | 43 | - uses: pnpm/action-setup@v4 44 | with: 45 | version: 9 46 | 47 | - name: Install -g 48 | run: npm install -g cross-env zx 49 | 50 | - name: Install Dependencies 51 | run: npm install 52 | 53 | 54 | - run: npx tsx ./translate.mts --test 55 | env: 56 | apiKey: ${{secrets.apiKey}} 57 | baseURL: ${{secrets.baseURL}} 58 | 59 | - run: | 60 | git config user.name "github-actions[bot]" 61 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 62 | git add . 63 | git commit -m "action-generated" 64 | git push 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/npm-publish-release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Release-Node.js Package 5 | 6 | on: 7 | workflow_dispatch: {} 8 | push: 9 | # tags: 10 | # - "v*" 11 | branches: 12 | - stable 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [ubuntu-latest] 22 | 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - name: Check out git repository 26 | uses: actions/checkout@v3.0.0 27 | 28 | - name: Install Node.js 29 | uses: actions/setup-node@v4.0.0 30 | with: 31 | node-version: "20" 32 | registry-url: https://registry.npmjs.org/ 33 | 34 | 35 | - name: Install -g 36 | run: npm install -g cross-env zx 37 | 38 | # 根目录的依赖缓存 39 | - name: Cache root dependencies 40 | uses: actions/cache@v3 41 | id: cache-root 42 | with: 43 | path: node_modules 44 | key: ${{ matrix.os }}-node-${{ hashFiles('**/package-lock.json') }} 45 | restore-keys: | 46 | ${{ matrix.os }}-node- 47 | 48 | # 安装根目录依赖(如果缓存未命中) 49 | - name: Install root dependencies 50 | if: steps.cache-root.outputs.cache-hit != 'true' 51 | run: npm install 52 | 53 | # web 目录的依赖缓存 54 | - name: Cache web dependencies 55 | uses: actions/cache@v3 56 | id: cache-web 57 | with: 58 | path: web/node_modules 59 | key: ${{ matrix.os }}-node-web-${{ hashFiles('web/package-lock.json') }} 60 | restore-keys: | 61 | ${{ matrix.os }}-node-web- 62 | 63 | # 安装 web 目录依赖(如果缓存未命中) 64 | - name: Install web dependencies 65 | if: steps.cache-web.outputs.cache-hit != 'true' 66 | run: npm install 67 | working-directory: ./web 68 | 69 | # electron 目录的依赖缓存 70 | - name: Cache electron dependencies 71 | uses: actions/cache@v3 72 | id: cache-electron 73 | with: 74 | path: electron/node_modules 75 | key: ${{ matrix.os }}-node-electron-${{ hashFiles('electron/package-lock.json') }} 76 | restore-keys: | 77 | ${{ matrix.os }}-node-electron- 78 | 79 | # 安装 electron 目录依赖(如果缓存未命中) 80 | - name: Install electron dependencies 81 | if: steps.cache-electron.outputs.cache-hit != 'true' 82 | run: npm install 83 | working-directory: ./electron 84 | 85 | - run: npm run updateVersion 86 | - run: npm run prod_node 87 | 88 | 89 | 90 | - run: cd electron && npm publish --access=public 91 | env: 92 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 93 | 94 | - name: SET GITHUB AND reset 95 | run: | 96 | git config user.name "github-actions[bot]" 97 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 98 | git reset --hard HEAD 99 | env: 100 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 101 | 102 | -------------------------------------------------------------------------------- /.github/workflows/test-build.yml: -------------------------------------------------------------------------------- 1 | name: test-build 2 | 3 | # on: [push, pull_request] 4 | on: 5 | workflow_dispatch: {} 6 | push: 7 | branches-ignore: 8 | - doc 9 | - alpha 10 | - stable 11 | - main 12 | 13 | jobs: 14 | test-build: 15 | name: build and release electron app 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [windows-latest] 20 | 21 | runs-on: ${{ matrix.os }} 22 | 23 | steps: 24 | - name: Check out git repository 25 | uses: actions/checkout@v3.0.0 26 | 27 | - name: Install Node.js 28 | uses: actions/setup-node@v4.0.0 29 | with: 30 | node-version: "20" 31 | 32 | 33 | - name: Install -g 34 | run: npm install -g cross-env zx 35 | 36 | - name: Install python-setuptools 37 | if: matrix.os == 'macos-latest' 38 | run: brew install python-setuptools 39 | 40 | # 根目录的依赖缓存 41 | - name: Cache root dependencies 42 | uses: actions/cache@v3 43 | id: cache-root 44 | with: 45 | path: node_modules 46 | key: ${{ matrix.os }}-node-${{ hashFiles('**/package-lock.json') }} 47 | restore-keys: | 48 | ${{ matrix.os }}-node- 49 | 50 | # 安装根目录依赖(如果缓存未命中) 51 | - name: Install root dependencies 52 | if: steps.cache-root.outputs.cache-hit != 'true' 53 | run: npm install 54 | 55 | # web 目录的依赖缓存 56 | - name: Cache web dependencies 57 | uses: actions/cache@v3 58 | id: cache-web 59 | with: 60 | path: web/node_modules 61 | key: ${{ matrix.os }}-node-web-${{ hashFiles('web/package-lock.json') }} 62 | restore-keys: | 63 | ${{ matrix.os }}-node-web- 64 | 65 | # 安装 web 目录依赖(如果缓存未命中) 66 | - name: Install web dependencies 67 | if: steps.cache-web.outputs.cache-hit != 'true' 68 | run: npm install 69 | working-directory: ./web 70 | 71 | # electron 目录的依赖缓存 72 | - name: Cache electron dependencies 73 | uses: actions/cache@v3 74 | id: cache-electron 75 | with: 76 | path: electron/node_modules 77 | key: ${{ matrix.os }}-node-electron-${{ hashFiles('electron/package-lock.json') }} 78 | restore-keys: | 79 | ${{ matrix.os }}-node-electron- 80 | 81 | # 安装 electron 目录依赖(如果缓存未命中) 82 | - name: Install electron dependencies 83 | if: steps.cache-electron.outputs.cache-hit != 'true' 84 | run: npm install 85 | working-directory: ./electron 86 | 87 | - run: npm run prod_node 88 | 89 | - name: Build Electron App (macOS only) 90 | if: matrix.os == 'macos-latest' 91 | run: npm run prod 92 | env: 93 | APPLE_ID: ${{ secrets.APPLE_ID }} 94 | APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} 95 | CSC_LINK: ${{ secrets.CSC_LINK }} 96 | CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} 97 | APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} 98 | # GH_TOKEN: ${{ secrets.GH_TOKEN }} 99 | USE_HARD_LINKS: false 100 | MYRUNENV: github 101 | 102 | 103 | - name: Build Electron App (widnows+linux) 104 | if: matrix.os != 'macos-latest' 105 | run: npm run prod 106 | env: 107 | # GH_TOKEN: ${{ secrets.GH_TOKEN }} 108 | MYRUNENV: github 109 | 110 | - name: upload artifacts 111 | uses: actions/upload-artifact@v4 112 | with: 113 | name: ${{ matrix.os }} 114 | path: electron/dist 115 | 116 | # - name: Publish Release 117 | # uses: softprops/action-gh-release@v2 118 | # if: startsWith(github.ref, 'refs/tags/') 119 | # with: 120 | # files: | 121 | # dist/* 122 | # env: 123 | # GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | tmp/ 25 | inputs/ 26 | outputs/ 27 | __pycache__/ 28 | dist/ 29 | .env 30 | *.log 31 | .continuerules 32 | .continue/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}/electron", 9 | "runtimeExecutable": "${workspaceFolder}/electron/node_modules/.bin/electron", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceFolder}/electron/node_modules/.bin/electron.cmd" 12 | }, 13 | "args": [ 14 | "." 15 | ], 16 | "outputCapture": "std" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Open Source License 2 | 3 | © 2024 Liao Donghuo. 4 | 5 | HyperChat is licensed under the Apache License 2.0, with the following additional conditions: 6 | 7 | 1. HyperChat 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: 8 | 9 | a. Multi-user service: Unless explicitly authorized by HyperChat in writing, you may not use the HyperChat source code to operate a multi-user environment. 10 | - User Definition: Login and register 11 | 12 | b. LOGO and copyright information: In the process of using HyperChat's frontend, you may not remove or modify the LOGO or copyright information in the HyperChat console or applications. This restriction is inapplicable to uses of HyperChat that do not involve its frontend. 13 | - Frontend Definition: For the purposes of this license, the "frontend" of HyperChat includes all components located in the `web/` directory when running HyperChat from the raw source code, or the "web" image when running HyperChat with Docker. 14 | 15 | Please contact 0laopo0@gmail.com by email to inquire about licensing matters. 16 | 17 | 2. As a contributor, you should agree that: 18 | 19 | a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary. 20 | b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations. 21 | 22 | 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. 23 | 24 | The interactive design of this product is protected by appearance patent. 25 | 26 | ---------- 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. -------------------------------------------------------------------------------- /dependencies/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /docker/electron/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM consol/debian-xfce-vnc 2 | 3 | ARG RELEASE=1.1.1 4 | ARG ARCH=linux-x64 5 | 6 | ENV RELEASE=${RELEASE} 7 | ENV ARCH=${ARCH} 8 | 9 | RUN wget https://github.com/BigSweetPotatoStudio/HyperChat/releases/download/v${RELEASE}/HyperChat-${RELEASE}-${ARCH}.tar.gz 10 | RUN tar -zxvf HyperChat-${RELEASE}-${ARCH}.tar.gz 11 | RUN rm HyperChat-${RELEASE}-${ARCH}.tar.gz 12 | RUN mv HyperChat-${RELEASE}-${ARCH} ./HyperChat 13 | RUN chmod +x ./HyperChat/hyper-chat 14 | 15 | 16 | RUN echo "\n" >> wm_startup.sh 17 | RUN echo "nohup ./HyperChat/hyper-chat &" >> wm_startup.sh 18 | # RUN echo "nohup chromium --remote-debugging-port=9222 &" >> wm_startup.sh 19 | RUN echo "nohup firefox -marionette -start-debugger-server 2828 &" >> wm_startup.sh 20 | 21 | CMD ["tail", "-f", "/dev/null"] 22 | -------------------------------------------------------------------------------- /docker/electron/build.sh: -------------------------------------------------------------------------------- 1 | RELEASE=1.1.1 2 | ARCH=linux-x64 3 | 4 | docker build \ 5 | --build-arg RELEASE=${RELEASE} \ 6 | --build-arg ARCH=${ARCH} \ 7 | -t hyperchat:${RELEASE} . 8 | -------------------------------------------------------------------------------- /docker/electron/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | ubuntu-desktop: 5 | image: hyperchat 6 | ports: 7 | - "6080:80" 8 | 9 | restart: unless-stopped -------------------------------------------------------------------------------- /docker/electron/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Starting HyperChat..." 3 | 4 | 5 | docker rm hyperchat && docker run --name hyperchat -p 6080:80 -v /dev/shm:/dev/shm hyperchat 6 | 7 | 8 | docker rm hyperchat && docker run --name hyperchat -p 5901:5901 -p 6901:6901 -p 16200:16100 -v C:\\Users\\0laop\\Documents\\HyperChat:~/Documents/HyperChat -e VNC_PASSWORDLESS=true hyperchat 9 | 10 | # docker run -it --user 0 -p 6911:6901 consol/debian-xfce-vnc 11 | 12 | # # Start the VNC server if it's not running 13 | # if [ ! -e /tmp/.X1-lock ]; then 14 | # /usr/bin/vncserver :1 -geometry 1280x800 -depth 24 15 | # fi 16 | 17 | # Wait for X server to be ready 18 | # echo "Waiting for X server to be ready..." 19 | # until xdpyinfo -display :1 >/dev/null 2>&1; do 20 | # sleep 0.5 21 | # done 22 | # echo "X server is ready" 23 | 24 | # # Export display for GUI applications 25 | # export DISPLAY=:1 26 | 27 | # # Run HyperChat 28 | # echo "Starting HyperChat..." 29 | # /root/HyperChat/hyper-chat --no-sandbox 30 | 31 | # Keep container running 32 | -------------------------------------------------------------------------------- /docker/no_electron/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | # Update package lists 4 | RUN apt-get update 5 | 6 | # Install curl and other dependencies 7 | RUN apt-get install -y curl 8 | 9 | # Add NodeSource repository for Node.js LTS 10 | RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - 11 | # Install Node.js and npm 12 | RUN apt-get install -y nodejs 13 | 14 | # Install Python and pip 15 | RUN apt-get install -y python3 python3-pip 16 | # With pip. 17 | RUN pip install uv 18 | 19 | COPY ../../electron/ /root/hyperchat 20 | 21 | ENV NODE_ENV=production 22 | 23 | CMD ["node", "/root/hyperchat/js/main_no_electron.js"] 24 | -------------------------------------------------------------------------------- /docker/no_electron/Dockerfile-Alpha: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | # Update package lists 4 | RUN apt-get update 5 | 6 | # Install curl and other dependencies 7 | RUN apt-get install -y curl 8 | 9 | # Add NodeSource repository for Node.js LTS 10 | RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - 11 | # Install Node.js and npm 12 | RUN apt-get install -y nodejs 13 | 14 | # Install Python and pip 15 | RUN apt-get install -y python3 python3-pip 16 | # With pip. 17 | RUN pip install uv 18 | 19 | COPY ../../electron/ /root/hyperchat 20 | 21 | ENV NODE_ENV=production 22 | 23 | CMD ["node", "/root/hyperchat/js/main_no_electron.js"] 24 | -------------------------------------------------------------------------------- /docker/no_electron/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | hyperchat-mini: 5 | image: hyperchat-mini 6 | ports: 7 | - "16100:16100" 8 | volumes: 9 | - "~/Documents/HyperChat:/root/Documents/HyperChat" 10 | restart: unless-stopped -------------------------------------------------------------------------------- /docker/no_electron/hg.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | # Update package lists 4 | RUN apt-get update 5 | 6 | # Install curl and other dependencies 7 | RUN apt-get install -y curl 8 | 9 | # Add NodeSource repository for Node.js LTS 10 | RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - 11 | # Install Node.js and npm 12 | RUN apt-get install -y nodejs 13 | 14 | # Install Python and pip 15 | RUN apt-get install -y python3 python3-pip 16 | # With pip. 17 | RUN pip install uv 18 | 19 | RUN npm i -g @dadigua/hyper-chat 20 | 21 | ENV NODE_ENV=production 22 | 23 | # Set up a new user named "user" with user ID 1000 24 | RUN useradd -m -u 1000 user 25 | 26 | # Switch to the "user" user 27 | USER user 28 | 29 | # Set home to the user's home directory 30 | ENV HOME=/home/user \ 31 | PATH=/home/user/.local/bin:$PATH 32 | 33 | WORKDIR $HOME/app 34 | 35 | CMD ["npx", "hyper-chat", "--appDataDir=/home/user/app/HyperChat"] -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # VuePress 默认临时文件目录 2 | .vuepress/.temp 3 | # VuePress 默认缓存目录 4 | .vuepress/.cache 5 | # VuePress 默认构建生成的静态文件目录 6 | .vuepress/dist -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | import { viteBundler } from '@vuepress/bundler-vite' 2 | import { defaultTheme } from '@vuepress/theme-default' 3 | import { defineUserConfig } from 'vuepress' 4 | 5 | export default defineUserConfig({ 6 | bundler: viteBundler(), 7 | theme: defaultTheme({ 8 | // 默认主题配置 9 | navbar: [ 10 | { 11 | text: '首页', 12 | link: '/', 13 | }, 14 | ], 15 | sidebar: [ 16 | { 17 | text: '项目介绍', 18 | link: '/', 19 | }, 20 | { 21 | text: '指南', 22 | children: [ 23 | { 24 | text: '快速开始', 25 | link: '/guide/quick-start.md', 26 | }, 27 | { 28 | text: '安装MCP', 29 | link: '/guide/mcp.md', 30 | }, 31 | { 32 | text: '内置MCP', 33 | link: '/guide/builtin-mcp.md', 34 | }, 35 | { 36 | text: '进阶', 37 | link: '/guide/advanced.md', 38 | children: [ 39 | { 40 | text: '变量', 41 | link: '/guide/var.md', 42 | }, 43 | 44 | ], 45 | }, 46 | ], 47 | }, 48 | ], 49 | }), 50 | port: 18080, 51 | }) -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | lang: zh-CN 3 | title: HyperChat 4 | description: 5 | --- 6 | 7 | # HyperChat 8 | 9 | HyperChat 设计理念是: 10 | 11 | * 1.一个开源的Chat客户端,支持MCP,可以使用各家LLM的API,实现最好的Chat体验。 12 | * 2.实现本机Agent + 任务系统,由AI帮忙完成任务。 13 | -------------------------------------------------------------------------------- /docs/doc.md: -------------------------------------------------------------------------------- 1 | 2 | 这个项目在3个月前就开始开发了,去年`Claude`才出MCP协议的时候,我就觉这个很厉害,然后,当时`Claude`账号立马也被封了,就搞了这个开源项目,目标是最佳的Chat体验+实现AI生产力。 3 | 4 | 介绍一下这个Chat项目的核心吧:我理解的 `Agent =智能(大模型)+Tool+记忆`。所以这样才能帮人完成任务`Task`来实现生产力,这个软件也是贯彻这思想。 5 | 6 | 1. HyperChat添加Agent时候,支持选择MCP Tool,通过系统提示词,定义Agent![image](https://github.com/user-attachments/assets/3fa6ec84-7e29-4df1-a149-3f672332629d) 7 | 2. 支持添加Task,定时通过发送消息的方式,调用Agent完成任务,同时支持Web Api方式调用 ![image](https://github.com/user-attachments/assets/de5328f9-3b7c-483d-956f-c76aebd564cd) 8 | 3. 支持Agent Call Agent,把Agent看做一个智能体,同时通过内置的MCP,通过发消息的方式,调用另一个Agent(比如,这个4o-mini,本来答不对9.8和9.11的,但是,我要他call `Gemini Think` Agent回答,他就会了。) ![image](https://github.com/user-attachments/assets/4201af2d-8fe2-427a-8ca4-a25595fc2aa8) 9 | 10 | 这就是这个软件是核心了,虽然他是一个Chat软件,但是核心是Agent,创建Agent和它聊天+调用工具。ps:我上面说的`Agent =智能(大模型)+Tool+记忆`,还有记忆没有很好的解决,目前没啥好用的办法,RAG的知识库实在是不咋地,以后会关注技术进步。 11 | 12 | 13 | HyperChat特点 14 | 1. 一切皆MCP的概念,内置了4个常用的MCP,把知识库封装成MCP,别的应该没这么搞 15 | - 常用工具(网页+谷歌+Bing搜索+获取时间) 16 | - 知识库,(使用那个知识库都可以让大模型选择,还可以让它把内容添加到知识库) 17 | - Agent相关 (支持Call Agent,新建Task) 18 | - 命令行 (命令行MCP,为什么内置?这个mcp很难安装,你可以让他执行命令 or ssh,很好用)![image](https://github.com/user-attachments/assets/954c6eaf-7c38-47de-bccd-a89dc48ef438) 19 | 20 | 2. 基于Web,可以脱离electron,可以用命令行运行`npx -y @dadigua/hyper-chat`,或者docker。 21 | - 支持了H5适配,可以端口映射,通过Web在任何设备访问,包括手机。 22 | - 可设置访问密码,安全访问。 23 | - 可以通过Web接口调用软件的功能,比如,你可以把它当成一个MCP Tool网关 24 | 25 | 3. 极致的Chat理念,现在其他的Chat软件输入框一大堆按钮,我总是感觉这样不太对劲,比如连个网搜索,还要点一下联网搜索按钮,首先我认为应该是模型决定回答这个问题是不是要联网么,这里我把搜索封装一个MCP工具,让模型决定是否调用工具,如何模型决定错了,我加上提示词让他记得使用工具。`就像龙珠里面许愿一样,发送消息就够了。`所以这里我想到了必须支持快速输入提示词,HyperChat通过一个@,可以快速输入Agent名称+快速输入短语,完成提示词 ![image](https://github.com/user-attachments/assets/0b42b361-a85e-40b3-9604-37b67be59f13)![image](https://github.com/user-attachments/assets/2a38f7bc-41fe-4360-bc74-aabddbdc73de) 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/guide/advanced.md: -------------------------------------------------------------------------------- 1 | --- 2 | lang: zh-CN 3 | title: 高级用法 4 | description: 5 | --- 6 | 7 | #### HyperChat 高级用法 8 | 9 | * 变量使用 10 | * 定时任务 -------------------------------------------------------------------------------- /docs/guide/builtin-mcp.md: -------------------------------------------------------------------------------- 1 | --- 2 | lang: zh-CN 3 | title: 内置MCP 4 | description: 内置MCP的安装和使用指南 5 | --- 6 | 7 | #### HyperChat 内置MCP目前有5个 8 | 为了帮助大家使用,以下是内置MCP的详细介绍: 9 | ![alt text](image-13.png) 10 | 11 | ### 1. **hyper_tools** 12 | hypertools 内置3个工具 13 | - current_time: 获取当前时间 14 | - fetch 打开页面,获取页面数据 15 | - search 使用搜索引擎搜索,支持谷歌和必应 16 | 17 | #### hyper_tools设置 18 | ![alt text](image-15.png) 19 | * Web_Tools_Platform可以设置,使用chrome打开页面,或者electron打开页面,或者禁用关于网页相关的内容。 20 | ![alt text](image-17.png) 21 | * SearchEngine,设置搜索引擎,支持谷歌和必应 22 | ![alt text](image-16.png) 23 | * 其他还可以设置,是否调用远程的chrome打开网页,等 24 | 25 | 26 | ### 2. **hyper_knowledge_base** 27 | 这个是内置知识库相关的mcp,可以通过mcp添加知识库内容,不过,内置的知识库还是实验版本,可以等以后完善。 28 | 29 | ### 3. **hyper_agent** 30 | -------------------------------------------------------------------------------- /docs/guide/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-1.png -------------------------------------------------------------------------------- /docs/guide/image-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-10.png -------------------------------------------------------------------------------- /docs/guide/image-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-11.png -------------------------------------------------------------------------------- /docs/guide/image-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-12.png -------------------------------------------------------------------------------- /docs/guide/image-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-13.png -------------------------------------------------------------------------------- /docs/guide/image-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-14.png -------------------------------------------------------------------------------- /docs/guide/image-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-15.png -------------------------------------------------------------------------------- /docs/guide/image-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-16.png -------------------------------------------------------------------------------- /docs/guide/image-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-17.png -------------------------------------------------------------------------------- /docs/guide/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-2.png -------------------------------------------------------------------------------- /docs/guide/image-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-3.png -------------------------------------------------------------------------------- /docs/guide/image-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-4.png -------------------------------------------------------------------------------- /docs/guide/image-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-5.png -------------------------------------------------------------------------------- /docs/guide/image-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-6.png -------------------------------------------------------------------------------- /docs/guide/image-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-7.png -------------------------------------------------------------------------------- /docs/guide/image-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-8.png -------------------------------------------------------------------------------- /docs/guide/image-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image-9.png -------------------------------------------------------------------------------- /docs/guide/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/image.png -------------------------------------------------------------------------------- /docs/guide/mcp.md: -------------------------------------------------------------------------------- 1 | --- 2 | lang: zh-CN 3 | title: MCP安装 4 | description: 5 | --- 6 | 7 | #### HyperChat 安装MCP,支持`stdio`,`sse`,`streamableHttp`,支持启用,禁用,同步MCP。 8 | 9 | 10 | #### 点击添加 11 | ![alt text](image-7.png) 12 | 13 | ### stdio类型的添加 14 | HyperChat添加mcp,和其他的客户端一样,只是把,`command` 和 `args` ,放在一起填入,用空格分开。如图,一一对应。 15 | 16 | ``` 17 | { 18 | "command": "npx", 19 | "args": [ 20 | "-y", 21 | "@amap/amap-maps-mcp-server" 22 | ], 23 | "env": { 24 | "AMAP_MAPS_API_KEY": "xxxxxxxxxxxxxxxxxxxxxxxxx" 25 | }, 26 | } 27 | ``` 28 | 29 | ![alt text](image-8.png) 30 | 31 | ### `sse`和`streamableHttp`类型的添加 32 | 33 | ![alt text](image-9.png) 34 | 35 | 36 | ### MCP启用,禁用 37 | ![alt text](image-10.png) 38 | 39 | ### MCP同步 40 | 通过MCP,同步这mcp配置 41 | ![alt text](image-12.png) 42 | 43 | -------------------------------------------------------------------------------- /docs/guide/quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | lang: zh-CN 3 | title: 快速开始 4 | description: 5 | --- 6 | 7 | 8 | ### 添加LLM的ApiKey. 9 | 10 | ![alt text](image.png) 11 | 12 | 13 | ![alt text](image-3.png) 14 | 15 | #### 工具模式,一般默认就行。标准,用tools参数传递工具,如果,类似开源的大模型,Qwen3,通过返回的内容里的``通过正则表达式获取参数,这种方式类似cline。 16 | ![alt text](image-2.png) 17 | 18 | #### 点击提交,会帮你测试llm的能力 19 | 20 | ![alt text](image-4.png) 21 | 22 | #### 开始聊天 23 | ![alt text](image-1.png) -------------------------------------------------------------------------------- /docs/guide/var.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/docs/guide/var.md -------------------------------------------------------------------------------- /electron/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | dist/ 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .env.production 25 | # package-lock.json 26 | *.js.map 27 | *.mjs.map 28 | metahuman/ 29 | 30 | web-build/ 31 | voices/ 32 | videos/ 33 | .token 34 | lib/ 35 | lib.zip 36 | web-build/ 37 | js/ 38 | tmp/ 39 | __pycache__/ 40 | *.spec 41 | *.log 42 | tests/ 43 | .env 44 | *.log 45 | js/ 46 | data/ 47 | uploads/ -------------------------------------------------------------------------------- /electron/build/mac/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | com.apple.security.device.audio-input 12 | 13 | com.apple.security.device.camera 14 | 15 | com.apple.security.cs.disable-library-validation 16 | 17 | 18 | -------------------------------------------------------------------------------- /electron/build/notarize.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const { notarize } = require("@electron/notarize"); 3 | const path = require("path"); 4 | const os = require("os"); 5 | const fs = require("fs"); 6 | 7 | exports.default = async function notarizing(context) { 8 | if (context.electronPlatformName !== "darwin" || process.env.GH_TOKEN == "") { 9 | console.log("Skipping notarization"); 10 | return; 11 | } 12 | console.log("Notarizing..."); 13 | 14 | 15 | // const appBundleId = context.packager.appInfo.info._configuration.appId; 16 | const appName = context.packager.appInfo.productFilename; 17 | const appPath = path.normalize( 18 | path.join(context.appOutDir, `${appName}.app`) 19 | ); 20 | 21 | await notarize({ 22 | appBundleId: "men.dadigua.hpyerchat", 23 | appPath, 24 | appleId: process.env.APPLE_ID, 25 | appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD, 26 | teamId: process.env.APPLE_TEAM_ID, 27 | }); 28 | console.log("Notarized app:", appPath); 29 | }; 30 | -------------------------------------------------------------------------------- /electron/build/remove-x64-latest-mac-yml.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const os = require("os"); 4 | 5 | 6 | 7 | exports.default = async function (context) { 8 | const file = path.join(__dirname, '..', 'dist', 'latest-mac.yml'); 9 | console.log('test...', file); 10 | if (fs.existsSync(file)) { 11 | fs.rmSync(file); 12 | console.log('latest-mac.yml removed before publish.'); 13 | } else { 14 | console.log('latest-mac.yml not found.'); 15 | } 16 | } -------------------------------------------------------------------------------- /electron/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require("./js/main_no_electron"); 3 | -------------------------------------------------------------------------------- /electron/package.nodejs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dadigua/hyper-chat", 3 | "bin": { 4 | "hyper-chat": "cli.js" 5 | }, 6 | "main": "js/main_no_electron.js", 7 | "devDependencies": {}, 8 | "files": [ 9 | "web-build", 10 | "js", 11 | "README.md" 12 | ] 13 | } -------------------------------------------------------------------------------- /electron/task.mts: -------------------------------------------------------------------------------- 1 | import { $, within, argv, sleep, fs, fetch, usePowerShell, os, path } from "zx"; 2 | import { pipeline } from "stream"; 3 | import { promisify } from "util"; 4 | 5 | import { createClient } from "webdav"; 6 | import packageJSON from "./package.json"; 7 | import AdmZip from "adm-zip"; 8 | 9 | $.verbose = true; 10 | if (os.platform() == "win32") { 11 | usePowerShell(); 12 | } 13 | 14 | if (argv.dev) { 15 | await $`npx cross-env NODE_ENV=development myEnv=dev webpack`; 16 | await $`npm run start`; 17 | } 18 | if (argv.watch) { 19 | await $`npx cross-env NODE_ENV=development myEnv=dev webpack`; 20 | } 21 | 22 | if (argv.devnode) { 23 | await $`npx cross-env NODE_ENV=development myEnv=dev webpack -c webpack.no_electron.js`; 24 | await $`npx cross-env NODE_ENV=production node js/main_no_electron.js`; 25 | } 26 | 27 | if (argv.testprod) { 28 | await $`npx cross-env NODE_ENV=production myEnv=test webpack`; 29 | await $`npx cross-env NODE_ENV=production myEnv=test electron-builder`; 30 | } 31 | 32 | if (argv.prod) { 33 | await fs.copy("../web/public/logo.png", "./web-build/assets/favicon.png", { 34 | overwrite: true, 35 | }); 36 | await $`npx cross-env NODE_ENV=production myEnv=prod webpack`; 37 | if (process.env.MYRUNENV === "github" && process.env.GH_TOKEN) { 38 | if (os.platform() == "darwin" && os.arch() === 'x64') { 39 | console.log('Building for x86/x64 architecture'); 40 | let pack = await fs.readJSON("./package.json"); 41 | pack.build.artifactBuildStarted = "./build/remove-x64-latest-mac-yml.js"; 42 | pack.build.mac.target = [{ 43 | "arch": [ 44 | "x64" 45 | ], 46 | "target": "dmg" 47 | }, 48 | { 49 | "arch": [ 50 | "x64" 51 | ], 52 | "target": "zip" 53 | }]; 54 | await fs.writeJSON("./package.json", pack, { spaces: 2 }); 55 | await $`npx cross-env NODE_ENV=production myEnv=prod electron-builder --publish always`; 56 | } else { 57 | await $`npx cross-env NODE_ENV=production myEnv=prod electron-builder --publish always`; 58 | } 59 | } else { 60 | await $`npx cross-env NODE_ENV=production myEnv=prod electron-builder --publish never`; 61 | } 62 | } 63 | 64 | if (argv.build) { 65 | await fs.copy("../web/public/logo.png", "./web-build/assets/favicon.png", { 66 | overwrite: true, 67 | }); 68 | await $`npx cross-env NODE_ENV=production myEnv=prod webpack`; 69 | await $`npx cross-env NODE_ENV=production myEnv=prod electron-builder --publish never`; 70 | } 71 | 72 | if (argv.buildnode) { 73 | await fs.copy("../web/public/logo.png", "./web-build/assets/favicon.png", { 74 | overwrite: true, 75 | }); 76 | let rootPackageJSON = await fs.readJSON("../package.json"); 77 | let packageJSON = await fs.readJSON("./package.json"); 78 | let nodePackageJSON = await fs.readJSON("./package.nodejs.json"); 79 | Object.assign(packageJSON, nodePackageJSON); 80 | packageJSON.version = rootPackageJSON.version; 81 | // console.log(packageJSON.dependencies); 82 | if (packageJSON.dependencies) { 83 | for (let key in packageJSON.dependencies) { 84 | if (key.startsWith("electron")) { 85 | delete packageJSON.dependencies[key]; 86 | } 87 | } 88 | } 89 | await fs.writeJSON("./package.json", packageJSON, { spaces: 2 }); 90 | await fs.copy("../README.md", "README.md"); 91 | await $`npx cross-env NODE_ENV=development myEnv=dev webpack -c webpack.no_electron.js`; 92 | } 93 | 94 | // 压缩文件夹 95 | function zipFolder(folderPath, outputPath) { 96 | const zip = new AdmZip(); 97 | zip.addLocalFolder(folderPath); 98 | zip.writeZip(outputPath); 99 | } 100 | 101 | // 提取压缩文件 102 | function extractZip(zipPath, outputPath) { 103 | var unzip = new AdmZip(zipPath); 104 | unzip.extractAllTo(outputPath, /*overwrite*/ true); 105 | } 106 | -------------------------------------------------------------------------------- /electron/ts/command_history.mts: -------------------------------------------------------------------------------- 1 | // import { taskHistory } from "../../common/data"; 2 | 3 | // export enum CommandStatus { 4 | // SUCCESS = "success", 5 | // ERROR = "error", 6 | // RUNING = "runing", 7 | // } 8 | 9 | // export class CommandHistory { 10 | // history: Array<{ 11 | // command: string; 12 | // args: Array; 13 | // status: CommandStatus; 14 | // result?: any; 15 | // error?: string; 16 | // timestamp?: number; 17 | // }> = []; 18 | 19 | // add(command: string, args: Array) { 20 | // this.history.push({ 21 | // command, 22 | // args, 23 | // status: CommandStatus.RUNING, 24 | // timestamp: Date.now(), 25 | // }); 26 | // } 27 | // get() { 28 | // return this.history; 29 | // } 30 | // last() { 31 | // return this.history[this.history.length - 1]; 32 | // } 33 | // async save() { 34 | // taskHistory.get().history = this.history; 35 | // await taskHistory.saveSync(); 36 | // } 37 | // } 38 | 39 | // export const commandHistory = new CommandHistory(); 40 | -------------------------------------------------------------------------------- /electron/ts/common/autoLauncher.mts: -------------------------------------------------------------------------------- 1 | // // src/AutoLauncher.ts 2 | // import { app } from "electron"; 3 | // import AutoLaunch from "auto-launch"; 4 | 5 | // export class AutoLauncher { 6 | // private autoLauncher: AutoLaunch; 7 | 8 | // constructor() { 9 | // // let path = app.getPath("exe"); 10 | // // console.log("path: ", path); 11 | // this.autoLauncher = new AutoLaunch({ 12 | // name: app.getName(), 13 | // path: app.getPath("exe"), 14 | // }); 15 | // } 16 | // async enable() { 17 | // if (!(await this.autoLauncher.isEnabled())) { 18 | // return this.autoLauncher.enable(); 19 | // } 20 | // } 21 | 22 | // async disable() { 23 | // if (await this.autoLauncher.isEnabled()) { 24 | // await this.autoLauncher.disable(); 25 | // } 26 | // } 27 | 28 | // async isEnabled() { 29 | // return this.autoLauncher.isEnabled(); 30 | // } 31 | // } 32 | 33 | // export const autoLauncher = new AutoLauncher(); 34 | -------------------------------------------------------------------------------- /electron/ts/common/checkport.mts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | 3 | // 要检查的端口 4 | export async function isPortUse(port: number): Promise { 5 | return new Promise((resolve, reject) => { 6 | const server = net.createServer(); 7 | 8 | server.once("error", (err: any) => { 9 | if (err.code === "EADDRINUSE") { 10 | // 端口已经被使用 11 | console.log(`Port ${port} is already in use.`); 12 | resolve(true); 13 | } else { 14 | console.log(err); 15 | reject(err); 16 | } 17 | }); 18 | 19 | server.once("listening", () => { 20 | // 端口未被使用 21 | // console.log(`Port ${port} is available.`); 22 | server.close(); 23 | resolve(false); 24 | }); 25 | 26 | try { 27 | server.listen(port); 28 | } catch (e) { 29 | console.log(e); 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /electron/ts/common/closeport.mts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | 3 | export function closePort(port: number) { 4 | 5 | // 查找占用端口的进程ID 6 | exec(`netstat -ano | findstr :${port}`, (err, stdout, stderr) => { 7 | if (err) { 8 | console.error(`查找端口失败: ${err.message}`); 9 | return; 10 | } 11 | 12 | if (stderr) { 13 | console.error(`查找端口时出现错误: ${stderr}`); 14 | return; 15 | } 16 | 17 | // 解析出进程ID (PID) 18 | const lines = stdout.trim().split('\n'); 19 | const listeningLines = lines.filter(line => line.includes('LISTENING')); 20 | const pids = listeningLines.map(line => line.trim().split(/\s+/).pop()).filter(pid => pid && pid !== '0'); 21 | 22 | if (pids.length === 0) { 23 | console.log(`端口 ${port} 上没有运行的进程。`); 24 | return; 25 | } 26 | console.log(pids) 27 | // 终止进程 28 | pids.forEach(pid => { 29 | exec(`taskkill /PID ${pid} /F`, (err, stdout, stderr) => { 30 | if (err) { 31 | console.error(`终止进程失败: ${err.message}`); 32 | return; 33 | } 34 | 35 | if (stderr) { 36 | console.error(`终止进程时出现错误: ${stderr}`); 37 | return; 38 | } 39 | 40 | console.log(`成功终止了PID为 ${pid} 的进程。`); 41 | }); 42 | }); 43 | }); 44 | } -------------------------------------------------------------------------------- /electron/ts/common/dataConstructor.mts: -------------------------------------------------------------------------------- 1 | import { Logger, appDataDir } from "ts/polyfills/index.mjs"; 2 | import { zx } from "../es6.mjs"; 3 | const { fs, path } = zx; 4 | export class Data { 5 | private localStorage = null; 6 | async init(isCatch = true) { 7 | let localData = {}; 8 | try { 9 | this.localStorage = await this.inget(); 10 | if (this.localStorage) { 11 | localData = JSON.parse(this.localStorage); 12 | } 13 | } catch (e) { 14 | Logger.error(e); 15 | localData = {}; 16 | } 17 | this.data = Object.assign({}, this.data, localData); 18 | return this.data; 19 | } 20 | initSync(isCatch = true) { 21 | let localData = {}; 22 | try { 23 | this.localStorage = this.ingetSync(); 24 | if (this.localStorage) { 25 | localData = JSON.parse(this.localStorage); 26 | } 27 | } catch (e) { 28 | Logger.error(e); 29 | localData = {}; 30 | } 31 | this.data = Object.assign({}, this.data, localData); 32 | return this.data; 33 | } 34 | constructor(private KEY: string, private data: T) {} 35 | get(): T { 36 | return this.data; 37 | } 38 | 39 | async save() { 40 | this.insave(); 41 | } 42 | private async inget() { 43 | if (await fs.exists(path.join(appDataDir, this.KEY))) { 44 | return await fs.readFile(path.join(appDataDir, this.KEY)); 45 | } else { 46 | return ""; 47 | } 48 | } 49 | private ingetSync() { 50 | if (fs.existsSync(path.join(appDataDir, this.KEY))) { 51 | return fs.readFileSync(path.join(appDataDir, this.KEY)); 52 | } else { 53 | return ""; 54 | } 55 | } 56 | private async insave() { 57 | return fs.writeFile( 58 | path.join(appDataDir, this.KEY), 59 | JSON.stringify(this.data, null, 2) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /electron/ts/common/execFallback.mts: -------------------------------------------------------------------------------- 1 | 2 | import { isPortUse } from "./checkport.mjs"; 3 | 4 | export async function execFallback(port, callback) { 5 | while (1) { 6 | let isUse = await isPortUse(port); 7 | // console.log("isUse: ", port, isUse); 8 | if (isUse) { 9 | port++; 10 | } else { 11 | callback(port); 12 | break; 13 | } 14 | } 15 | return port; 16 | } 17 | -------------------------------------------------------------------------------- /electron/ts/common/progress.mts: -------------------------------------------------------------------------------- 1 | import { electronData } from "../../../common/data"; 2 | 3 | 4 | class Progress { 5 | name = ""; 6 | loaded = 0; 7 | total = 0; 8 | status: "progress" | "success" = "progress"; 9 | progress = 0; 10 | constructor(name: string, loaded: number, total: number) { 11 | this.name = name; 12 | this.loaded = loaded; 13 | this.total = total; 14 | } 15 | calcProgress() { 16 | this.progress = Math.round((this.loaded / this.total) * 10000) / 100; 17 | } 18 | } 19 | 20 | class ProgressList { 21 | data: Array = []; 22 | reset() { 23 | this.data = []; 24 | } 25 | setProgress(name: string, loaded: number, total: number) { 26 | let progress = this.data.find((x) => x.name == name); 27 | if (!progress) { 28 | progress = new Progress(name, loaded, total); 29 | this.data.push(progress); 30 | } 31 | progress.loaded = loaded; 32 | progress.total = total; 33 | progress.calcProgress(); 34 | if (progress.loaded == progress.total) { 35 | progress.status = "success"; 36 | } 37 | electronData.saveSync(); 38 | } 39 | getData() { 40 | return this.data; 41 | } 42 | } 43 | 44 | export const progressList = new ProgressList(); 45 | -------------------------------------------------------------------------------- /electron/ts/common/request.mts: -------------------------------------------------------------------------------- 1 | import querystring from "querystring"; 2 | import { fetch } from "zx"; 3 | console.log("NODE_ENV: ", process.env.NODE_ENV); 4 | let BASE_URL = ""; 5 | 6 | BASE_URL = process.env.REACT_APP_BASE_URL || "http://localhost:5000"; 7 | 8 | console.log(BASE_URL); 9 | 10 | export async function request(url: string, options = {} as any) { 11 | if (!url.includes("?")) { 12 | let querystr = querystring.stringify(options.query); 13 | url = url + (querystr ? "?" + querystr : ""); 14 | } 15 | if (options.json) { 16 | options = Object.assign({}, options, { 17 | headers: { 18 | "Content-Type": "application/json", 19 | }, 20 | body: JSON.stringify(options.json), 21 | }); 22 | } 23 | return fetch(BASE_URL + url, options).then((res) => res.json()); 24 | } 25 | -------------------------------------------------------------------------------- /electron/ts/common/types.mts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/electron/ts/common/types.mts -------------------------------------------------------------------------------- /electron/ts/common/util.mts: -------------------------------------------------------------------------------- 1 | import os from "os"; 2 | 3 | export function getLocalIP(): string[] { 4 | const interfaces = os.networkInterfaces(); 5 | let ips = []; 6 | for (const devName in interfaces) { 7 | const iface = interfaces[devName]; 8 | 9 | for (let i = 0; i < iface.length; i++) { 10 | const alias = iface[i]; 11 | if (alias.family === "IPv4" && !alias.internal) { 12 | // 返回第一个找到的非内部的IPv4地址 13 | ips.push(alias.address); 14 | // return alias.address; 15 | } 16 | } 17 | } 18 | return ips; 19 | } 20 | 21 | // // windows 文件名格式合法 22 | // export function sanitizeFileName(fileName: string): string { 23 | // return fileName.trim().replace(/[<>:"/\\|?*\n\r;]+/g, "_"); 24 | // } 25 | 26 | const WINDOWS_RESERVED_NAMES = [ 27 | "CON", 28 | "PRN", 29 | "AUX", 30 | "NUL", 31 | "COM1", 32 | "COM2", 33 | "COM3", 34 | "COM4", 35 | "COM5", 36 | "COM6", 37 | "COM7", 38 | "COM8", 39 | "COM9", 40 | "LPT1", 41 | "LPT2", 42 | "LPT3", 43 | "LPT4", 44 | "LPT5", 45 | "LPT6", 46 | "LPT7", 47 | "LPT8", 48 | "LPT9", 49 | ]; 50 | 51 | export function sanitizeFileName(fileName: string): string { 52 | // 去除首尾空格 53 | let sanitized = fileName.trim(); 54 | 55 | sanitized = sanitized.replace(/[\n\r]+/g, " "); 56 | 57 | // 替换非法字符 58 | sanitized = sanitized.replace(/[<>:"\/\\|?*\x00-\x1F]+/g, "_"); 59 | 60 | // 处理以点结尾的情况 61 | sanitized = sanitized.replace(/\.+$/, ""); 62 | 63 | // 检查是否是保留名称 64 | const nameWithoutExt = sanitized.split(".")[0].toUpperCase(); 65 | if (WINDOWS_RESERVED_NAMES.includes(nameWithoutExt)) { 66 | sanitized = "_" + sanitized; 67 | } 68 | 69 | // 限制长度 70 | if (sanitized.length > 128) { 71 | sanitized = sanitized.substring(0, 128); 72 | } 73 | 74 | // 确保文件名不为空 75 | if (!sanitized) { 76 | sanitized = "_"; 77 | } 78 | 79 | return sanitized; 80 | } 81 | 82 | import spawn from "cross-spawn"; 83 | export const spawnWithOutput = ( 84 | ...args: Parameters 85 | ): any => { 86 | return new Promise((resolve, reject) => { 87 | const proc = spawn(...args); 88 | let stdout = ""; 89 | let stderr = ""; 90 | 91 | proc.stdout.pipe(process.stdout); 92 | proc.stderr.pipe(process.stderr); 93 | 94 | proc.stdout.on("data", (data) => { 95 | stdout += data.toString(); 96 | // console.log(data.toString()); // 实时输出 97 | }); 98 | 99 | proc.stderr.on("data", (data) => { 100 | stderr += data.toString(); 101 | // console.error(data.toString()); // 实时输出错误 102 | }); 103 | 104 | proc.on("close", (code) => { 105 | if (code !== 0) { 106 | reject(new Error(`Command failed with code ${code}\n${stderr}`)); 107 | } else { 108 | resolve({ 109 | stdout, 110 | stderr, 111 | code, 112 | }); 113 | } 114 | }); 115 | 116 | proc.on("error", (err) => { 117 | reject({ error: err, stderr, stdout }); 118 | }); 119 | }); 120 | }; 121 | 122 | 123 | export async function sleep(t) { 124 | return new Promise(resolve => setTimeout(resolve, t)); 125 | } -------------------------------------------------------------------------------- /electron/ts/const.mts: -------------------------------------------------------------------------------- 1 | const HTTPPORT = 16100; 2 | const MCPServerPORT = 16110; 3 | export const Config = { 4 | port: HTTPPORT, 5 | mcp_server_port: MCPServerPORT, 6 | }; 7 | -------------------------------------------------------------------------------- /electron/ts/demo.mts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron/main' 2 | 3 | const createWindow = () => { 4 | const win = new BrowserWindow({ 5 | width: 800, 6 | height: 600 7 | }) 8 | 9 | win.loadURL('https://www.baidu.com') 10 | } 11 | 12 | app.whenReady().then(() => { 13 | createWindow() 14 | 15 | app.on('activate', () => { 16 | if (BrowserWindow.getAllWindows().length === 0) { 17 | createWindow() 18 | } 19 | }) 20 | }) 21 | 22 | app.on('window-all-closed', () => { 23 | if (process.platform !== 'darwin') { 24 | app.quit() 25 | } 26 | }) -------------------------------------------------------------------------------- /electron/ts/dependencies.mts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /electron/ts/es6.mts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const zx = await import(/* webpackIgnore: true */ "zx"); 4 | 5 | export const { $, usePowerShell, os, fs } = zx; 6 | export const { shellPathSync } = await import( 7 | /* webpackIgnore: true */ "shell-path" 8 | ); 9 | 10 | export const { createClient } = await import( 11 | /* webpackIgnore: true */ "webdav" 12 | ); 13 | 14 | export const { McpServer } = await import( 15 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/server/mcp.js" 16 | ); 17 | 18 | export const { getDefaultEnvironment } = await import( 19 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/client/stdio.js" 20 | ); 21 | 22 | export const ChromeLauncher = await import( 23 | /* webpackIgnore: true */ "chrome-launcher" 24 | ); 25 | 26 | export const { Client } = await import( 27 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/client/index.js" 28 | ); 29 | 30 | export const { SSEClientTransport } = await import( 31 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/client/sse.js" 32 | ); 33 | export const { Server } = await import( 34 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/server/index.js" 35 | ); 36 | export const { SSEServerTransport } = await import( 37 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/server/sse.js" 38 | ); 39 | export const { StreamableHTTPServerTransport } = await import( 40 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/server/streamableHttp.js" 41 | ); 42 | export const { StreamableHTTPClientTransport } = await import( 43 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/client/streamableHttp.js" 44 | ); 45 | 46 | export const { InMemoryTransport } = await import( 47 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/inMemory.js" 48 | ); 49 | 50 | 51 | 52 | export const { 53 | ListToolsResultSchema, 54 | CallToolRequestSchema, 55 | CallToolResultSchema, 56 | CompatibilityCallToolResultSchema, 57 | 58 | ListResourcesRequestSchema, 59 | ReadResourceRequestSchema, 60 | GetPromptRequestSchema, 61 | ListToolsRequestSchema, 62 | ListPromptsRequestSchema, 63 | 64 | NotificationSchema, 65 | ProgressNotificationSchema, 66 | LoggingMessageNotificationSchema, 67 | ResourceListChangedNotificationSchema, 68 | isInitializeRequest, 69 | 70 | } = await import( 71 | /* webpackIgnore: true */ "@modelcontextprotocol/sdk/types.js" 72 | ); 73 | 74 | export const { RAGApplicationBuilder, TextLoader } = await import( 75 | /* webpackIgnore: true */ "@llm-tools/embedjs" 76 | ); 77 | 78 | 79 | 80 | export const { OpenAiEmbeddings } = await import( 81 | /* webpackIgnore: true */ "@llm-tools/embedjs-openai" 82 | ); 83 | 84 | export const { LibSqlDb } = await import( 85 | /* webpackIgnore: true */ "@llm-tools/embedjs-libsql" 86 | ); 87 | 88 | export const { PdfLoader } = await import( 89 | /* webpackIgnore: true */ "@llm-tools/embedjs-loader-pdf" 90 | ); 91 | 92 | const stripModule = await import(/* webpackIgnore: true */ "strip-ansi"); 93 | 94 | export const strip = stripModule.default; 95 | -------------------------------------------------------------------------------- /electron/ts/first.mts: -------------------------------------------------------------------------------- 1 | import { Logger } from "ts/polyfills/index.mjs"; 2 | import { zx } from "./es6.mjs"; 3 | const { $, fs, cd, fetch, sleep, path } = zx; 4 | import { electronData } from "../../common/data"; 5 | import "./common/data.mjs"; 6 | import { appDataDir } from "ts/polyfills/index.mjs"; 7 | 8 | // global.ext = { 9 | // invert: async (name, args) => { 10 | // // try { 11 | // // // const { Command } = await import(/* webpackIgnore: true */ "../command.mjs"); 12 | // // let res = await Command[name](...args); 13 | // // return { 14 | // // code: 0, 15 | // // success: true, 16 | // // data: res, 17 | // // }; 18 | // // } catch (e) { 19 | // // Logger.error(name, args, e); 20 | // // return { success: false, code: 1, message: e.message }; 21 | // // } 22 | // }, 23 | // receive: () => {}, 24 | // }; 25 | // 获取日志文件路径 26 | const logFilePath = Logger.path; 27 | // 清空日志文件 28 | fs.writeFileSync(logFilePath, ""); 29 | 30 | // 记录新的启动日志 31 | Logger.info("Application started. Previous logs cleared."); 32 | Logger.info("__dirname", __dirname); 33 | Logger.info("process.cwd()", process.cwd()); 34 | Logger.info("execPath: ", process.execPath); 35 | Logger.info("NODE_ENV: ", process.env.NODE_ENV); 36 | Logger.info("myEnv: ", process.env.myEnv); 37 | 38 | 39 | Logger.info( 40 | path.join(__dirname, "../web-build/assets/favicon.png"), 41 | fs.existsSync(path.join(__dirname, "../web-build/assets/favicon.png")) 42 | ); 43 | 44 | Logger.info("appDataDir: ", appDataDir); 45 | fs.ensureDirSync(path.join(appDataDir, "messages")); 46 | electronData.get().appDataDir = appDataDir; 47 | electronData.get().logFilePath = logFilePath; 48 | electronData.saveSync(); 49 | 50 | // 捕获未处理的异常 51 | process.on('uncaughtException', (error) => { 52 | Logger.error('Uncaught Exception:', error); 53 | // process.exit(1); 54 | }); 55 | 56 | // 捕获未处理的Promise拒绝 57 | process.on('unhandledRejection', (reason, promise) => { 58 | Logger.error('Unhandled Rejection at:', promise, 'reason:', reason); 59 | // 对于Promise错误,可以选择不终止应用 60 | }); -------------------------------------------------------------------------------- /electron/ts/langchain/index.mts: -------------------------------------------------------------------------------- 1 | export * from "./textsplitters.mjs"; 2 | export * from "./vectorStore.mjs"; 3 | -------------------------------------------------------------------------------- /electron/ts/langchain/loader/pdf.mts: -------------------------------------------------------------------------------- 1 | import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf"; 2 | 3 | // declare module "pdfjs-dist/legacy/build/pdf.js" { 4 | // const pdfjs: any; 5 | // export default pdfjs; 6 | // } 7 | 8 | function pdfLoader(path) { 9 | return new PDFLoader(path, { 10 | // you may need to add `.then(m => m.default)` to the end of the import 11 | pdfjs: () => import("pdfjs-dist").then((m) => m.default), 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /electron/ts/langchain/stores/doc/base.mts: -------------------------------------------------------------------------------- 1 | import { Document } from "@langchain/core/documents"; 2 | 3 | /** 4 | * Abstract class for a document store. All document stores should extend 5 | * this class. 6 | */ 7 | export abstract class Docstore { 8 | abstract search(search: string): Promise; 9 | 10 | abstract add(texts: Record): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /electron/ts/langchain/stores/doc/in_memory.mts: -------------------------------------------------------------------------------- 1 | import { Document } from "@langchain/core/documents"; 2 | import { BaseStoreInterface } from "@langchain/core/stores"; 3 | import { Docstore } from "./base.mjs"; 4 | 5 | /** 6 | * Class for storing and retrieving documents in memory asynchronously. 7 | * Extends the Docstore class. 8 | */ 9 | export class InMemoryDocstore 10 | extends Docstore 11 | implements BaseStoreInterface 12 | { 13 | _docs: Map; 14 | 15 | constructor(docs?: Map) { 16 | super(); 17 | this._docs = docs ?? new Map(); 18 | } 19 | 20 | /** 21 | * Searches for a document in the store based on its ID. 22 | * @param search The ID of the document to search for. 23 | * @returns The document with the given ID. 24 | */ 25 | async search(search: string): Promise { 26 | const result = this._docs.get(search); 27 | if (!result) { 28 | throw new Error(`ID ${search} not found.`); 29 | } else { 30 | return result; 31 | } 32 | } 33 | 34 | /** 35 | * Adds new documents to the store. 36 | * @param texts An object where the keys are document IDs and the values are the documents themselves. 37 | * @returns Void 38 | */ 39 | async add(texts: Record): Promise { 40 | const keys = [...this._docs.keys()]; 41 | const overlapping = Object.keys(texts).filter((x) => keys.includes(x)); 42 | 43 | if (overlapping.length > 0) { 44 | throw new Error(`Tried to add ids that already exist: ${overlapping}`); 45 | } 46 | 47 | for (const [key, value] of Object.entries(texts)) { 48 | this._docs.set(key, value); 49 | } 50 | } 51 | 52 | async mget(keys: string[]): Promise { 53 | return Promise.all(keys.map((key) => this.search(key))); 54 | } 55 | 56 | async mset(keyValuePairs: [string, Document][]): Promise { 57 | await Promise.all( 58 | keyValuePairs.map(([key, value]) => this.add({ [key]: value })) 59 | ); 60 | } 61 | 62 | async mdelete(_keys: string[]): Promise { 63 | throw new Error("Not implemented."); 64 | } 65 | 66 | // eslint-disable-next-line require-yield 67 | async *yieldKeys(_prefix?: string): AsyncGenerator { 68 | throw new Error("Not implemented"); 69 | } 70 | } 71 | 72 | /** 73 | * Class for storing and retrieving documents in memory synchronously. 74 | */ 75 | export class SynchronousInMemoryDocstore { 76 | _docs: Map; 77 | 78 | constructor(docs?: Map) { 79 | this._docs = docs ?? new Map(); 80 | } 81 | 82 | /** 83 | * Searches for a document in the store based on its ID. 84 | * @param search The ID of the document to search for. 85 | * @returns The document with the given ID. 86 | */ 87 | search(search: string): Document { 88 | const result = this._docs.get(search); 89 | if (!result) { 90 | throw new Error(`ID ${search} not found.`); 91 | } else { 92 | return result; 93 | } 94 | } 95 | 96 | /** 97 | * Adds new documents to the store. 98 | * @param texts An object where the keys are document IDs and the values are the documents themselves. 99 | * @returns Void 100 | */ 101 | add(texts: Record): void { 102 | const keys = [...this._docs.keys()]; 103 | const overlapping = Object.keys(texts).filter((x) => keys.includes(x)); 104 | 105 | if (overlapping.length > 0) { 106 | throw new Error(`Tried to add ids that already exist: ${overlapping}`); 107 | } 108 | 109 | for (const [key, value] of Object.entries(texts)) { 110 | this._docs.set(key, value); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /electron/ts/langchain/textsplitters.mts: -------------------------------------------------------------------------------- 1 | import { TokenTextSplitter } from "@langchain/textsplitters"; 2 | 3 | const textSplitter = new TokenTextSplitter({ 4 | chunkSize: 1000, 5 | chunkOverlap: 0, 6 | }); 7 | 8 | export const toolTextSplitter = async (text) => { 9 | const texts = await textSplitter.splitText(text); 10 | return texts; 11 | }; 12 | 13 | const texts = await toolTextSplitter(` 14 | 15 | sk-4a9f875b950045638db1f7d712162fb3 16 | `); 17 | 18 | console.log("texts", texts); 19 | -------------------------------------------------------------------------------- /electron/ts/main.mts: -------------------------------------------------------------------------------- 1 | import { Logger } from "ts/polyfills/index.mjs"; 2 | import "./first.mjs"; 3 | import { 4 | app, 5 | BrowserWindow, 6 | nativeImage, 7 | Tray, 8 | ipcMain, 9 | protocol, 10 | net, 11 | Menu, 12 | // desktopCapturer, 13 | session, 14 | shell, 15 | } from "electron"; 16 | import "ts/polyfills/electron_autoupdate.mjs"; 17 | import path from "node:path"; 18 | import "./common/data.mjs"; 19 | import { Command } from "./command.mjs"; 20 | 21 | import { initHttp } from "./websocket.mjs"; 22 | 23 | import { createWindow } from "./mianWindow.mjs"; 24 | 25 | Logger.info("start main"); 26 | 27 | 28 | // const createWindow = () => { 29 | // const win = new BrowserWindow({ 30 | // width: 800, 31 | // height: 600, 32 | // }); 33 | 34 | // win.loadURL("https://www.baidu.com"); 35 | // }; 36 | 37 | ipcMain.handle("command", async (event, name, args) => { 38 | try { 39 | let res = await Command[name](...args); 40 | if (name == "getHistory") { 41 | // log.info(name, args); 42 | } else { 43 | if (name == "writeFile") { 44 | Logger.info( 45 | name, 46 | args[0], 47 | "writeFile Data length: " + args[1].length 48 | // res 49 | ); 50 | } else { 51 | Logger.info( 52 | name, 53 | args 54 | // res 55 | ); 56 | } 57 | } 58 | 59 | return { 60 | code: 0, 61 | success: true, 62 | data: res, 63 | }; 64 | } catch (e) { 65 | Logger.error(name, args, e); 66 | return { success: false, code: 1, message: e.message }; 67 | } 68 | }); 69 | 70 | // app.commandLine.appendSwitch("remote-debugging-port", "8315"); 71 | // app.commandLine.appendSwitch("enable-usermedia-screen-capturing"); 72 | app.on( 73 | "certificate-error", 74 | (event, webContents, url, error, certificate, callback) => { 75 | event.preventDefault(); 76 | callback(true); // 忽略证书错误 77 | } 78 | ); 79 | 80 | app.whenReady().then(async () => { 81 | // hide menu for Mac 82 | // if (process.platform == "darwin") { 83 | // app.dock.hide(); 84 | // } 85 | await initHttp().catch((e) => { 86 | Logger.info("initHttp error: ", e); 87 | }); 88 | try { 89 | createWindow(); 90 | } catch (e) { 91 | Logger.error(e); 92 | throw e; 93 | } 94 | 95 | 96 | protocol.handle("fs", (request) => { 97 | let p = request.url.replace("fs://", ""); 98 | return net.fetch("file://" + path.join(__dirname, "../", p)); 99 | }); 100 | 101 | app.on("activate", () => { 102 | if (BrowserWindow.getAllWindows().length === 0) createWindow(); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /electron/ts/main_no_electron.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Logger } from "ts/polyfills/index.mjs"; 3 | import "./first.mjs"; 4 | import { initHttp } from "./websocket.mjs"; 5 | 6 | // import { createWindow } from "./mianWindow.mjs"; 7 | 8 | await initHttp().catch((e) => { 9 | Logger.info("initHttp error: ", e); 10 | }); 11 | -------------------------------------------------------------------------------- /electron/ts/mcp/claude.mts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { zx } from "../es6.mjs"; 3 | const { os } = zx; 4 | 5 | const homeDir = os.homedir(); 6 | const platformPaths = { 7 | win32: { 8 | baseDir: process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"), 9 | vscodePath: path.join("Code", "User", "globalStorage"), 10 | }, 11 | darwin: { 12 | baseDir: path.join(homeDir, "Library", "Application Support"), 13 | vscodePath: path.join("Code", "User", "globalStorage"), 14 | }, 15 | linux: { 16 | baseDir: process.env.XDG_CONFIG_HOME || path.join(homeDir, ".config"), 17 | vscodePath: path.join("Code/User/globalStorage"), 18 | }, 19 | }; 20 | 21 | const platform = process.platform as keyof typeof platformPaths; 22 | const { baseDir, vscodePath } = platformPaths[platform]; 23 | 24 | // Define client paths using the platform-specific base directories 25 | export const clientPaths = { 26 | claude: path.join(baseDir, "Claude", "claude_desktop_config.json"), 27 | // cline: path.join(baseDir, vscodePath, "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"), 28 | // "roo-cline": path.join(baseDir, vscodePath, "rooveterinaryinc.roo-cline", "settings", "cline_mcp_settings.json") 29 | }; 30 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/express.mts: -------------------------------------------------------------------------------- 1 | import App from "express"; 2 | import { electronData } from "../../../../common/data"; 3 | import { execFallback } from "../../common/execFallback.mjs"; 4 | import { Logger } from "ts/polyfills/index.mjs"; 5 | import { MyServers } from "./index.mjs"; 6 | 7 | import { Config } from "ts/const.mjs"; 8 | import { v4 } from "uuid"; 9 | import { isInitializeRequest, SSEServerTransport, StreamableHTTPServerTransport } from "ts/es6.mjs"; 10 | 11 | type HyperMcp = { 12 | createServer; 13 | handlePostMessage; 14 | name: string; 15 | url: string; 16 | type: "sse" | "streamableHttp"; 17 | }; 18 | const KEEP_ALIVE_INTERVAL_MS = 25000; // Send keep-alive every 25 seconds 19 | 20 | 21 | // Map to store transports by session ID 22 | const transports: { [sessionId: string]: any } = {}; 23 | export async function initMcpServer() { 24 | let PORT = await new Promise(async (resolve, reject) => { 25 | // Logger.info("initMcpServer", MCPServerPORT); 26 | const app = App(); 27 | 28 | async function register(serve: HyperMcp) { 29 | if (serve.type == "streamableHttp") { 30 | 31 | 32 | // let server = await serve.createServer(); 33 | // await server.connect(transport); 34 | app.post(`/${serve.name}/mcp`, async (req, res) => { 35 | // console.log('Received MCP request:', req.body); 36 | 37 | try { 38 | const server = await serve.createServer(); 39 | const transport = new StreamableHTTPServerTransport({ 40 | sessionIdGenerator: undefined, 41 | }); 42 | res.on('close', () => { 43 | // console.log('Request closed'); 44 | transport.close(); 45 | server.close(); 46 | }); 47 | await server.connect(transport); 48 | await transport.handleRequest(req, res, req.body); 49 | } catch (error) { 50 | console.error('Error handling MCP request:', error); 51 | if (!res.headersSent) { 52 | res.status(500).json({ 53 | jsonrpc: '2.0', 54 | error: { 55 | code: -32603, 56 | message: 'Internal server error', 57 | }, 58 | id: null, 59 | }); 60 | } 61 | } 62 | 63 | 64 | 65 | }); 66 | 67 | // Reusable handler for GET and DELETE requests 68 | const handleSessionRequest = async (req, res) => { 69 | res.writeHead(405).end(JSON.stringify({ 70 | jsonrpc: "2.0", 71 | error: { 72 | code: -32000, 73 | message: "Method not allowed." 74 | }, 75 | id: null 76 | })); 77 | }; 78 | 79 | app.get(`/${serve.name}/mcp`, handleSessionRequest); 80 | 81 | app.delete(`/${serve.name}/mcp`, handleSessionRequest); 82 | } else { 83 | let transport; 84 | 85 | app.get(`/${serve.name}/sse`, async (req, res) => { 86 | transport = new SSEServerTransport(`/${serve.name}/message`, res); 87 | // Start keep-alive ping 88 | const intervalId = setInterval(() => { 89 | if (!res.writableEnded) { 90 | res.write(': keepalive\n\n'); 91 | } else { 92 | // Should not happen if close handler is working, but clear just in case 93 | clearInterval(intervalId); 94 | } 95 | }, KEEP_ALIVE_INTERVAL_MS); 96 | let server = await serve.createServer(); 97 | await server.connect(transport); 98 | }); 99 | app.post(`/${serve.name}/message`, async (req, res) => { 100 | await transport.handlePostMessage(req, res); 101 | }); 102 | } 103 | } 104 | for (let serve of MyServers) { 105 | await register(serve); 106 | } 107 | let PORT = Config.mcp_server_port; 108 | 109 | PORT = await execFallback(PORT, (port: number) => { 110 | app.listen(port, () => { 111 | // console.log(`McpServer is running on port ${port}`); 112 | resolve(port); 113 | }); 114 | }); 115 | }); 116 | 117 | Config.mcp_server_port = PORT; 118 | } 119 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/gateway/index.mts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { store } from "../../../rag/vectorStore.mjs"; 4 | import dayjs from "dayjs"; 5 | import { IMCPClient, KNOWLEDGE_BASE } from "../../../../../common/data"; 6 | import { 7 | Server, 8 | SSEServerTransport, 9 | zx, 10 | ListToolsRequestSchema, 11 | CallToolRequestSchema, 12 | } from "ts/es6.mjs"; 13 | import { CONST } from "ts/polyfills/polyfills.mjs"; 14 | import { mcpClients } from "ts/mcp/config.mjs"; 15 | import { Command } from "ts/command.mjs"; 16 | const { fs, path, sleep } = zx; 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | async function createServer(name: string, description: string, allowMCPs: string[]) { 25 | const NAME = name; 26 | 27 | /** 28 | * Create an MCP server with capabilities for resources (to list/read notes), 29 | * tools (to create new notes), and prompts (to summarize notes). 30 | */ 31 | const server = new Server( 32 | { 33 | name: NAME, 34 | version: CONST.getVersion, 35 | description: description, 36 | }, 37 | { 38 | capabilities: { 39 | resources: {}, 40 | tools: {}, 41 | prompts: {}, 42 | }, 43 | } 44 | ); 45 | 46 | /** 47 | * Handler that lists available tools. 48 | * Exposes a single "create_note" tool that lets clients create new notes. 49 | */ 50 | server.setRequestHandler(ListToolsRequestSchema, async () => { 51 | // console.log("gateway allowMCPs", allowMCPs); 52 | let getTools = (allowMCPs) => { 53 | let tools: IMCPClient["tools"] = []; 54 | 55 | mcpClients.forEach((v) => { 56 | tools = tools.concat( 57 | v.tools.filter((t) => { 58 | if (!allowMCPs) return true; 59 | return ( 60 | allowMCPs.includes(t.clientName) || allowMCPs.includes(t.restore_name) 61 | ); 62 | }), 63 | ); 64 | }); 65 | return tools; 66 | } 67 | return { 68 | tools: [ 69 | ...getTools(allowMCPs).map((tool) => { 70 | return { 71 | name: tool.function.name, 72 | description: tool.function.description, 73 | inputSchema: tool.function.parameters, 74 | }; 75 | }), 76 | ].filter(x => x), 77 | }; 78 | }); 79 | 80 | /** 81 | * Handler for the create_note tool. 82 | * Creates a new note with the provided title and content, and returns success message. 83 | */ 84 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 85 | try { 86 | let getTools = (allowMCPs) => { 87 | let tools: IMCPClient["tools"] = []; 88 | 89 | mcpClients.forEach((v) => { 90 | tools = tools.concat( 91 | v.tools.filter((t) => { 92 | if (!allowMCPs) return true; 93 | return ( 94 | allowMCPs.includes(t.clientName) || allowMCPs.includes(t.restore_name) 95 | ); 96 | }), 97 | ); 98 | }); 99 | return tools; 100 | } 101 | 102 | let find = getTools(allowMCPs).find((tool) => { 103 | return tool.function.name === request.params.name; 104 | }); 105 | 106 | return await Command.mcpCallTool(find.clientName, find.origin_name, request.params.arguments); 107 | } catch (error) { 108 | return { 109 | content: [ 110 | { 111 | type: "text", 112 | text: `error: ${error.message}`, 113 | }, 114 | ], 115 | }; 116 | } 117 | }); 118 | 119 | 120 | return server; 121 | } 122 | 123 | 124 | 125 | 126 | export { createServer }; 127 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/hyper_tools/electron.mts: -------------------------------------------------------------------------------- 1 | import { configSchema, getConfig } from "./lib.mjs"; 2 | import { z } from "zod"; 3 | 4 | import * as web1 from "./web1.mjs"; 5 | import * as web2 from "./web2.mjs"; 6 | import { MCP_CONFIG } from "../../../../../common/data"; 7 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 8 | import dayjs from "dayjs"; 9 | export function registerElectronTool(server: McpServer) { 10 | 11 | server.tool( 12 | "current_time", 13 | `Get the current local time as a string.`, 14 | {}, 15 | async ({ }) => { 16 | try { 17 | return { 18 | content: [ 19 | { 20 | type: "text", 21 | text: dayjs().format("YYYY-MM-DDTHH:mm:ss"), 22 | }, 23 | ], 24 | }; 25 | } catch (e) { 26 | throw new Error("Failed to fetch URL"); 27 | } 28 | } 29 | ); 30 | 31 | if (getConfig().Web_Tools_Platform == "none") { 32 | return; 33 | } 34 | 35 | server.tool( 36 | "fetch", 37 | `Fetches a URL from the internet and optionally extracts its contents as markdown. Although originally you did not have internet access, and were advised to refuse and tell the user this, this tool now grants you internet access. Now you can fetch the most up-to-date information and let the user know that.`, 38 | { 39 | url: z.string({ 40 | description: `URL to fetch`, 41 | }), 42 | }, 43 | async ({ url }) => { 44 | 45 | let config = getConfig() as z.infer; 46 | let md = ""; 47 | if (config.Web_Tools_Platform == "chrome") { 48 | md = await web2.fetch(url); 49 | } else { 50 | md = await web1.fetch(url); 51 | } 52 | return { 53 | content: [{ code: 0, type: "text", text: md }], 54 | }; 55 | } 56 | ); 57 | 58 | server.tool( 59 | "search", 60 | `Searches the internet for a given keyword and returns the search results.`, 61 | { 62 | words: z.string({ 63 | description: `words to serach`, 64 | }), 65 | }, 66 | async ({ words }) => { 67 | let config = getConfig() as z.infer; 68 | let res = []; 69 | if (config.Web_Tools_Platform == "chrome") { 70 | res = await web2.search(words); 71 | } else { 72 | res = await web1.search(words); 73 | } 74 | return { 75 | content: [{ type: "text", text: JSON.stringify(res) }], 76 | }; 77 | } 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/hyper_tools/index.mts: -------------------------------------------------------------------------------- 1 | import { McpServer, SSEServerTransport, StreamableHTTPServerTransport } from "ts/es6.mjs"; 2 | import { configSchema, getConfig, NAME } from "./lib.mjs"; 3 | import { CONST } from "ts/polyfills/polyfills.mjs"; 4 | 5 | 6 | async function createServer() { 7 | 8 | const server = new McpServer({ 9 | name: NAME, 10 | version: CONST.getVersion, 11 | }); 12 | 13 | if (process.env.no_electron != "1") { 14 | 15 | let { registerElectronTool } = await import("./electron.mjs"); 16 | registerElectronTool(server); 17 | 18 | } else { 19 | let { registerNoElectronTool } = await import("./no_electron.mjs"); 20 | registerNoElectronTool(server); 21 | } 22 | return server; 23 | } 24 | 25 | 26 | export const HyperTools = { 27 | createServer, 28 | 29 | name: NAME, 30 | url: ``, 31 | configSchema: configSchema, 32 | type: "streamableHttp", 33 | }; 34 | 35 | export * from "./lib.mjs"; 36 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/hyper_tools/lib.mts: -------------------------------------------------------------------------------- 1 | import { fs, McpServer, SSEServerTransport } from "ts/es6.mjs"; 2 | 3 | import { appDataDir, CONST } from "ts/polyfills/polyfills.mjs"; 4 | import { z } from "zod"; 5 | import { MCP_CONFIG } from "../../../../../common/data"; 6 | import zodToJsonSchema, { zodPatterns } from "zod-to-json-schema"; 7 | import path from "path"; 8 | 9 | export const NAME = "hyper_tools"; 10 | 11 | 12 | 13 | let e = ["chrome", "none"] 14 | if (process.env.no_electron != "1") { 15 | e = ["electron", "chrome", "none"] 16 | } 17 | let d = "none" 18 | if (process.env.no_electron != "1") { 19 | d = "electron" 20 | } 21 | 22 | export const configSchema = z.object({ 23 | Web_Tools_Platform: z 24 | .enum(e as any, { 25 | description: "Platform using web tools", 26 | }) 27 | .default(d), 28 | SearchEngine: z 29 | .enum(["google", "bing"], { 30 | description: "search engine", 31 | }) 32 | .default("google"), 33 | ChromeIsUseLocal: z 34 | .enum(["true", "false"], { 35 | description: "Whether to launcher the local browser", 36 | }) 37 | .default("true"), 38 | ChromeBrowserURL: z 39 | .string({ 40 | description: "Connect to the browser's remote debugging port", 41 | }) 42 | .default("http://localhost:9222"), 43 | ChromeStartingUrl: z 44 | .string({ 45 | description: "Chrome starting Page Url", 46 | }) 47 | .default("https://github.com/BigSweetPotatoStudio/HyperChat"), 48 | ChromePath: z 49 | .string({ 50 | description: "Chrome Path", 51 | }) 52 | .default(""), 53 | ChromeUserData: z 54 | .string({ 55 | description: "Chrome userData Path", 56 | }) 57 | .default(path.join(appDataDir, "Chrome")), 58 | ChromeAutoClose: z 59 | .enum(["true", "false"], { 60 | description: "Whether to close automatically after use", 61 | }) 62 | .default("true"), 63 | ChromeHeadless: z 64 | .enum(["true", "false"], { 65 | description: "open the browser in headless(hidden) mode", 66 | }) 67 | .default("false"), 68 | }); 69 | 70 | // console.log("safeParse : ", configSchema.safeParse({})); 71 | export function getConfig() { 72 | let buildinMcpJSONPath = path.join(appDataDir, "mcpBuiltIn.json"); 73 | let mcpconfig = fs.readJSONSync(buildinMcpJSONPath); 74 | 75 | let config = mcpconfig.mcpServers[NAME].hyperchat.config as z.infer< 76 | typeof configSchema 77 | >; 78 | 79 | return configSchema.safeParse(config).data; 80 | } 81 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/hyper_tools/no_electron.mts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | 3 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | import { configSchema, getConfig } from "./lib.mjs"; 5 | import { z } from "zod"; 6 | 7 | 8 | import * as web2 from "./web2.mjs"; 9 | import { MCP_CONFIG } from "../../../../../common/data"; 10 | 11 | export function registerNoElectronTool(server: McpServer) { 12 | server.tool( 13 | "current_time", 14 | `Get the current local time as a string.`, 15 | {}, 16 | async ({ }) => { 17 | try { 18 | return { 19 | content: [ 20 | { 21 | type: "text", 22 | text: dayjs().format("YYYY-MM-DDTHH:mm:ss"), 23 | }, 24 | ], 25 | }; 26 | } catch (e) { 27 | throw new Error("Failed to fetch URL"); 28 | } 29 | } 30 | ); 31 | 32 | if (getConfig().Web_Tools_Platform == "none") { 33 | return; 34 | } 35 | server.tool( 36 | "fetch", 37 | `Fetches a URL from the internet and optionally extracts its contents as markdown. Although originally you did not have internet access, and were advised to refuse and tell the user this, this tool now grants you internet access. Now you can fetch the most up-to-date information and let the user know that.`, 38 | { 39 | url: z.string({ 40 | description: `URL to fetch`, 41 | }), 42 | }, 43 | async ({ url }) => { 44 | 45 | let config = getConfig() as z.infer; 46 | let md = ""; 47 | if (config.Web_Tools_Platform == "chrome") { 48 | md = await web2.fetch(url); 49 | } 50 | return { 51 | content: [{ code: 0, type: "text", text: md }], 52 | }; 53 | } 54 | ); 55 | 56 | server.tool( 57 | "search", 58 | `Searches the internet for a given keyword and returns the search results.`, 59 | { 60 | words: z.string({ 61 | description: `words to search`, 62 | }), 63 | }, 64 | async ({ words }) => { 65 | let config = getConfig() as z.infer; 66 | let res = []; 67 | if (config.Web_Tools_Platform == "chrome") { 68 | res = await web2.search(words); 69 | } 70 | return { 71 | content: [{ type: "text", text: JSON.stringify(res) }], 72 | }; 73 | } 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/index.mts: -------------------------------------------------------------------------------- 1 | export * from "./express.mjs"; 2 | import { HyperTools } from "./hyper_tools/index.mjs"; 3 | import { HyperKnowledgeBase } from "./KnowledgeBase/index.mjs"; 4 | import { HyperSettings } from "./settings/index.mjs"; 5 | import { HyperAgent } from "./Task/index.mjs"; 6 | import { HyperTerminal } from "./terminal/index.mjs"; 7 | export const MyServers = []; 8 | 9 | MyServers.push(HyperTools, HyperKnowledgeBase, HyperAgent, HyperTerminal, HyperSettings); 10 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/settings/index.mts: -------------------------------------------------------------------------------- 1 | import { McpServer, SSEServerTransport } from "ts/es6.mjs"; 2 | import { configSchema, getConfig, NAME } from "./lib.mjs"; 3 | import { CONST } from "ts/polyfills/polyfills.mjs"; 4 | import { registerTool } from "./settings.mjs"; 5 | 6 | 7 | 8 | let transport; 9 | async function createServer(endpoint: string, response) { 10 | // console.log("Received connection"); 11 | // transport = new SSEServerTransport(endpoint, response); 12 | // // console.log("==================", getConfig().Web_Tools_Platform); 13 | const server = new McpServer({ 14 | name: NAME, 15 | version: CONST.getVersion, 16 | }); 17 | 18 | registerTool(server); 19 | 20 | 21 | // await server.connect(transport); 22 | return server; 23 | } 24 | 25 | async function handlePostMessage(req, res) { 26 | // console.log("Received message"); 27 | await transport.handlePostMessage(req, res); 28 | } 29 | 30 | export const HyperSettings = { 31 | createServer, 32 | handlePostMessage, 33 | name: NAME, 34 | url: ``, 35 | configSchema: configSchema, 36 | type: "streamableHttp", 37 | }; 38 | 39 | 40 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/settings/lib.mts: -------------------------------------------------------------------------------- 1 | import { fs, McpServer, SSEServerTransport } from "ts/es6.mjs"; 2 | import { MCP_CONFIG } from "../../../../../common/data"; 3 | import { appDataDir, CONST } from "ts/polyfills/polyfills.mjs"; 4 | import { z } from "zod"; 5 | import path from "path"; 6 | 7 | 8 | 9 | export const NAME = "hyper_settings"; 10 | 11 | export const configSchema = z.object({ 12 | 13 | }); 14 | 15 | // console.log("safeParse : ", configSchema.safeParse({})); 16 | export function getConfig() { 17 | let buildinMcpJSONPath = path.join(appDataDir, "mcpBuiltIn.json"); 18 | let mcpconfig = fs.readJSONSync(buildinMcpJSONPath); 19 | 20 | let config = mcpconfig.mcpServers[NAME].hyperchat.config as z.infer< 21 | typeof configSchema 22 | >; 23 | 24 | return configSchema.safeParse(config).data; 25 | } 26 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/terminal/index.mts: -------------------------------------------------------------------------------- 1 | import { McpServer, SSEServerTransport } from "ts/es6.mjs"; 2 | import { configSchema, getConfig, NAME } from "./lib.mjs"; 3 | import { CONST } from "ts/polyfills/polyfills.mjs"; 4 | import { registerTool } from "./terminal.mjs"; 5 | 6 | 7 | 8 | let transport; 9 | async function createServer(endpoint: string, response) { 10 | // console.log("Received connection"); 11 | // transport = new SSEServerTransport(endpoint, response); 12 | // // console.log("==================", getConfig().Web_Tools_Platform); 13 | const server = new McpServer({ 14 | name: NAME, 15 | version: CONST.getVersion, 16 | }); 17 | 18 | registerTool(server); 19 | 20 | 21 | // await server.connect(transport); 22 | return server; 23 | } 24 | 25 | async function handlePostMessage(req, res) { 26 | // console.log("Received message"); 27 | await transport.handlePostMessage(req, res); 28 | } 29 | 30 | export const HyperTerminal = { 31 | createServer, 32 | handlePostMessage, 33 | name: NAME, 34 | url: ``, 35 | configSchema: configSchema, 36 | type: "streamableHttp", 37 | }; 38 | 39 | 40 | -------------------------------------------------------------------------------- /electron/ts/mcp/servers/terminal/lib.mts: -------------------------------------------------------------------------------- 1 | import { fs, McpServer, SSEServerTransport } from "ts/es6.mjs"; 2 | import { MCP_CONFIG } from "../../../../../common/data"; 3 | import { appDataDir, CONST } from "ts/polyfills/polyfills.mjs"; 4 | import { z } from "zod"; 5 | import path from "path"; 6 | 7 | 8 | 9 | export const NAME = "hyper_terminal"; 10 | 11 | export const configSchema = z.object({ 12 | Terminal_End_CheckCount: z.number({ 13 | description: `Determine whether to end by checking if the output remains unchanged. The default is 15 times`, 14 | }).default(15), 15 | Terminal_Output_MaxToken: z.number({ 16 | description: `The maximum number of tokens to output`, 17 | }).default(10000), 18 | Terminal_Timeout: z.number({ 19 | description: `The timeout for the command to end`, 20 | }).default(5 * 60 * 1000), 21 | }); 22 | 23 | // console.log("safeParse : ", configSchema.safeParse({})); 24 | export function getConfig() { 25 | let buildinMcpJSONPath = path.join(appDataDir, "mcpBuiltIn.json"); 26 | let mcpconfig = fs.readJSONSync(buildinMcpJSONPath); 27 | 28 | let config = mcpconfig.mcpServers[NAME].hyperchat.config as z.infer< 29 | typeof configSchema 30 | >; 31 | 32 | return configSchema.safeParse(config).data; 33 | } 34 | -------------------------------------------------------------------------------- /electron/ts/mcp/utils.mts: -------------------------------------------------------------------------------- 1 | import { getDefaultEnvironment, os, shellPathSync } from "ts/es6.mjs"; 2 | import { 3 | electronData, 4 | MCP_CONFIG, 5 | MCP_CONFIG_TYPE, 6 | } from "../../../common/data"; 7 | 8 | // export async function getMCPConfg(): Promise<{ 9 | // mcpServers: { [s: string]: MCP_CONFIG_TYPE }; 10 | // }> { 11 | // let config = MCP_CONFIG.initSync(); 12 | 13 | // // let obj: any = {}; 14 | // // config.mcpServers = Object.assign(obj, config.mcpServers); 15 | 16 | // for (let key in config.mcpServers) { 17 | // if (config.mcpServers[key].hyperchat == null) { 18 | // config.mcpServers[key].hyperchat = { 19 | // config: {}, 20 | // } as any; 21 | // } 22 | // } 23 | // return config; 24 | // } 25 | 26 | export function getMyDefaultEnvironment() { 27 | let env = Object.assign(getDefaultEnvironment(), process.env); 28 | electronData.initSync(); 29 | if (electronData.get().PATH) { 30 | env.PATH = electronData.get().PATH; 31 | } else { 32 | if (os.platform() != "win32") { 33 | env.PATH = shellPathSync(); 34 | } 35 | } 36 | return env; 37 | } 38 | -------------------------------------------------------------------------------- /electron/ts/message_service.mts: -------------------------------------------------------------------------------- 1 | // import { activeUser, userSocketMap, genMainMsg } from "./websocket.mjs"; 2 | 3 | import { Logger } from "ts/polyfills/polyfills.mjs"; 4 | import type { Server as SocketIO } from "socket.io"; 5 | const userSocketMap = new Map(); 6 | let activeUser = undefined; 7 | 8 | export class MessageService { 9 | MainMsg: SocketIO; 10 | genMainMsg = () => { 11 | return this.MainMsg; 12 | }; 13 | terminalMsg: SocketIO; 14 | // 发送消息到渲染进程 15 | async sendToRenderer(data: any, channel: string = "message-from-main") { 16 | const { genMainMsg } = this; 17 | if (!genMainMsg()) { 18 | return; 19 | } 20 | const socketId = userSocketMap.get(activeUser); 21 | if (socketId) { 22 | genMainMsg().to(socketId).emit(channel, data); 23 | } else { 24 | genMainMsg().emit(channel, data); 25 | } 26 | } 27 | async sendAllToRenderer(data: any, channel: string = "message-from-main") { 28 | const { genMainMsg } = this; 29 | if (!genMainMsg()) { 30 | return; 31 | } 32 | genMainMsg().emit(channel, data); 33 | } 34 | init(MainMsg, terminalMsg) { 35 | this.MainMsg = MainMsg; 36 | this.MainMsg.on("connection", (socket) => { 37 | Logger.info("用户已连接,socket ID:", socket.id); 38 | 39 | // 用户登录时记录关系 40 | socket.on("active", (userId) => { 41 | userSocketMap.set(userId, socket.id); 42 | activeUser = userId; 43 | }); 44 | socket.on("disconnect", () => { 45 | // 遍历删除断开连接的socket 46 | for (const [userId, socketId] of userSocketMap.entries()) { 47 | if (socketId === socket.id) { 48 | userSocketMap.delete(userId); 49 | } 50 | } 51 | }); 52 | }); 53 | this.terminalMsg = terminalMsg; 54 | this.terminalMsg.on("connection", (socket) => { 55 | Logger.info("terminalMsg 用户已连接,socket ID:", socket.id); 56 | socket.on("terminal-receive", (msg) => { 57 | // console.log("terminal-receive", msg); 58 | this.terminalCallbacks.forEach((callback) => { 59 | callback(msg); 60 | }); 61 | }); 62 | }); 63 | } 64 | terminalCallbacks = [] 65 | addTerminalMsgListener(callback: (msg: any) => void) { 66 | this.terminalCallbacks.push(callback); 67 | } 68 | removeTerminalMsgListener(callback: (msg: any) => void) { 69 | this.terminalCallbacks = this.terminalCallbacks.filter(cb => cb !== callback); 70 | } 71 | } 72 | 73 | let messageService: MessageService = new MessageService(); 74 | 75 | export const getMessageService = () => { 76 | return messageService; 77 | }; 78 | -------------------------------------------------------------------------------- /electron/ts/polyfills/electron.mts: -------------------------------------------------------------------------------- 1 | import ELogger from "electron-log"; 2 | import AutoLaunch from "auto-launch"; 3 | import { 4 | Context, 5 | LoggerPolyfill, 6 | AutoLauncher as IAutoLaunch, 7 | CheckUpdate as ICheckUpdate, 8 | Clone, 9 | } from "./polyfills.mjs"; 10 | import { app } from "electron"; 11 | 12 | class LoggerC extends LoggerPolyfill { 13 | info(...args: any[]) { 14 | ELogger.info(...args); 15 | } 16 | warn(...args: any[]) { 17 | ELogger.warn(...args); 18 | } 19 | error(...args: any[]) { 20 | ELogger.error(...args); 21 | } 22 | path = ELogger.transports.file.getFile().path; 23 | } 24 | 25 | Clone(Context.Logger, new LoggerC()); 26 | 27 | 28 | ///////////////////////////// 29 | export const userDataPath = app.getPath("userData"); 30 | 31 | Context.CONST.userDataPath = userDataPath; 32 | Context.CONST.getVersion = app.getVersion(); 33 | 34 | /////////////////////////// 35 | 36 | 37 | export class AutoLauncher extends IAutoLaunch { 38 | declare autoLauncher: AutoLaunch; 39 | 40 | constructor() { 41 | super(); 42 | // let path = app.getPath("exe"); 43 | // console.log("path: ", path); 44 | this.autoLauncher = new AutoLaunch({ 45 | name: app.getName(), 46 | path: app.getPath("exe"), 47 | }); 48 | } 49 | async enable() { 50 | if (!(await this.autoLauncher.isEnabled())) { 51 | return this.autoLauncher.enable(); 52 | } 53 | } 54 | 55 | async disable() { 56 | if (await this.autoLauncher.isEnabled()) { 57 | await this.autoLauncher.disable(); 58 | } 59 | } 60 | 61 | async isEnabled() { 62 | return this.autoLauncher.isEnabled(); 63 | } 64 | } 65 | Clone(Context.autoLauncher, new AutoLauncher()); 66 | 67 | /////////////////////////// 68 | 69 | 70 | -------------------------------------------------------------------------------- /electron/ts/polyfills/electron_autoupdate.mts: -------------------------------------------------------------------------------- 1 | //* 检查更新工具类 2 | import { autoUpdater } from "electron-updater"; 3 | 4 | //* 引入工具类 5 | 6 | import { Clone, Context, Logger, CheckUpdate as ICheckUpdate } from "ts/polyfills/index.mjs"; 7 | 8 | // autoUpdater.logger = Logger; 9 | 10 | class CheckUpdate extends ICheckUpdate { 11 | constructor() { 12 | super(); 13 | //* 设置检查更新的url 可以不设置 忽略 14 | autoUpdater.autoDownload = false; 15 | // autoUpdater.forceDevUpdateConfig = true; // 强制使用dev配置 16 | autoUpdater.setFeedURL({ 17 | provider: "github", 18 | owner: "BigSweetPotatoStudio", 19 | repo: "HyperChat", 20 | }); 21 | 22 | this.updaterEvent(); 23 | } 24 | checkUpdate() { 25 | //* 开始检查更新 26 | return autoUpdater.checkForUpdatesAndNotify().catch((err) => { 27 | Logger.info("网络连接问题,检测更新失败", err); 28 | }); 29 | } 30 | // 退出并安装 31 | quitAndInstall() { 32 | autoUpdater.quitAndInstall(); 33 | } 34 | download() { 35 | autoUpdater.downloadUpdate(); 36 | } 37 | updaterEvent() { 38 | //* 监听updater的事件 39 | /** 40 | * -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载完成 41 | **/ 42 | // 当开始检查更新的时候触发 43 | autoUpdater.on("checking-for-update", () => { 44 | Logger.info("开始检查更新"); 45 | sendToRender("UpdateMsg", { status: 0 }); 46 | }); 47 | 48 | // 发现可更新数据时 49 | autoUpdater.on("update-available", (info) => { 50 | Logger.info("有更新", info); 51 | sendToRender("UpdateMsg", { status: 1, info: info }); 52 | }); 53 | 54 | // 没有可更新数据时 55 | autoUpdater.on("update-not-available", (info) => { 56 | Logger.info("没有更新", info); 57 | sendToRender("UpdateMsg", { status: 2, info: info }); 58 | }); 59 | 60 | // 下载监听 61 | autoUpdater.on("download-progress", (progressObj) => { 62 | Logger.info("下载监听", progressObj); 63 | sendToRender("download-progress", progressObj); 64 | }); 65 | 66 | // 下载完成 67 | autoUpdater.on("update-downloaded", () => { 68 | Logger.info("下载完成"); 69 | sendToRender("UpdateMsg", { status: 4 }); 70 | }); 71 | // 当更新发生错误的时候触发。 72 | autoUpdater.on("error", (err) => { 73 | Logger.info("更新出现错误", err.message); 74 | if (err.message.includes("sha512 checksum mismatch")) { 75 | sendToRender(-1, "sha512校验失败"); 76 | } else { 77 | sendToRender(-1, "错误信息请看主进程控制台"); 78 | } 79 | }); 80 | } 81 | } 82 | import { getMessageService } from "ts/message_service.mjs"; 83 | async function sendToRender(type, data) { 84 | getMessageService().sendAllToRenderer({ 85 | type: type, 86 | data: data, 87 | }); 88 | } 89 | 90 | 91 | Clone(Context.checkUpdate, new CheckUpdate()); 92 | -------------------------------------------------------------------------------- /electron/ts/polyfills/index.mts: -------------------------------------------------------------------------------- 1 | import { zx } from "../es6.mjs"; 2 | const { $, usePowerShell, os } = zx; 3 | $.verbose = true; 4 | if (os.platform() === "win32") { 5 | usePowerShell(); 6 | } 7 | 8 | console.log("process.env.no_electron", process.env.no_electron); 9 | if (process.env.no_electron) { 10 | await import("./no_electron.mjs").catch((err) => 11 | console.error("Failed to load no_electron polyfill", err) 12 | ); 13 | } else { 14 | await import("./electron.mjs").catch((err) => 15 | console.error("Failed to load electron polyfill", err) 16 | ); 17 | } 18 | 19 | export * from "./polyfills.mjs"; 20 | -------------------------------------------------------------------------------- /electron/ts/polyfills/no_electron.mts: -------------------------------------------------------------------------------- 1 | import log4js from "log4js"; 2 | import dayjs from "dayjs"; 3 | import { 4 | CONST, 5 | Context, 6 | Logger, 7 | LoggerPolyfill, 8 | AutoLauncher as IAutoLauncher, 9 | CheckUpdate as ICheckUpdate, 10 | Clone, 11 | } from "./polyfills.mjs"; 12 | import path from "path"; 13 | import p from "../../package.json"; 14 | import { zx } from "ts/es6.mjs"; 15 | 16 | const { fs } = zx; 17 | 18 | const logDir = path.join(Context.CONST.appDataDir, "log"); 19 | Context.CONST.getVersion = p.version; 20 | 21 | ///////////////// 22 | 23 | fs.ensureDirSync(logDir); 24 | let logpath = path.join(logDir, `${dayjs().format("YYYY-MM-DD")}.log`); 25 | Logger.path = logpath; 26 | log4js.configure({ 27 | appenders: { 28 | log: { 29 | type: "file", 30 | filename: Logger.path, 31 | }, 32 | }, 33 | categories: { default: { appenders: ["log"], level: "trace" } }, 34 | }); 35 | const logger = log4js.getLogger(); 36 | 37 | class LoggerC extends LoggerPolyfill { 38 | info(...args) { 39 | let [msg, ...rest] = args; 40 | logger.info(msg, ...rest); 41 | console.log(...args); 42 | } 43 | warn(...args) { 44 | let [msg, ...rest] = args; 45 | logger.warn(msg, ...rest); 46 | console.log(...args); 47 | } 48 | error(...args) { 49 | let [msg, ...rest] = args; 50 | logger.error(msg, ...rest); 51 | console.log(...args); 52 | } 53 | path = logpath; 54 | } 55 | 56 | Clone(Context.Logger, new LoggerC()); 57 | 58 | export class AutoLauncher extends IAutoLauncher {} 59 | 60 | Clone(Context.autoLauncher, new AutoLauncher()); 61 | 62 | export class CheckUpdate extends ICheckUpdate {} 63 | 64 | Clone(Context.checkUpdate, new CheckUpdate()); 65 | -------------------------------------------------------------------------------- /electron/ts/polyfills/polyfills.mts: -------------------------------------------------------------------------------- 1 | 2 | export class LoggerPolyfill { 3 | info(...args) { } 4 | warn(...args) { } 5 | error(...args) { } 6 | path = ""; 7 | } 8 | 9 | export const Context = { 10 | Logger: {} as any, 11 | CONST: {} as { 12 | userDataPath: string; 13 | getVersion: string; 14 | appDataDir: string; 15 | dirName: string; 16 | }, 17 | autoLauncher: {} as any, 18 | checkUpdate: {} as any, 19 | }; 20 | 21 | export function Clone(a, b) { 22 | Object.assign(a, b); 23 | Object.setPrototypeOf(a, b); 24 | } 25 | export const Logger = Context.Logger as LoggerPolyfill; 26 | 27 | import path from "path"; 28 | import os from "os"; 29 | 30 | import { zx } from "ts/es6.mjs"; 31 | const { fs, argv } = zx; 32 | 33 | export const dirName = "HyperChat"; 34 | let appDataDir = path.join(os.homedir(), "Documents", dirName); 35 | if (process.env.no_electron == "1") { 36 | try { 37 | appDataDir = argv.appDataDir || path.join(os.homedir(), "Documents", dirName); 38 | } catch (e) { 39 | Logger.error("appDataDir set failed", e); 40 | } 41 | } 42 | 43 | fs.ensureDirSync(appDataDir); 44 | export { appDataDir }; 45 | 46 | Context.CONST = { 47 | userDataPath: appDataDir, 48 | getVersion: "", 49 | appDataDir: appDataDir, 50 | dirName: dirName, 51 | }; 52 | 53 | export const CONST = Context.CONST as typeof Context.CONST; 54 | 55 | export class AutoLauncher { 56 | public autoLauncher; 57 | 58 | constructor() { } 59 | async enable() { 60 | throw new Error("Method not implemented."); 61 | } 62 | 63 | async disable() { 64 | throw new Error("Method not implemented."); 65 | } 66 | 67 | async isEnabled() { 68 | return Promise.resolve(false); 69 | } 70 | } 71 | 72 | export const autoLauncher = Context.autoLauncher as AutoLauncher; 73 | 74 | //////////////////////////////////////// 75 | 76 | export class CheckUpdate { 77 | constructor() { } 78 | checkUpdate() { } 79 | // 退出并安装 80 | quitAndInstall() { } 81 | download() { } 82 | updaterEvent() { } 83 | } 84 | export let checkUpdate = Context.checkUpdate as CheckUpdate; 85 | -------------------------------------------------------------------------------- /electron/ts/preload.ts: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require("electron/renderer"); 2 | 3 | console.log("preload"); 4 | 5 | contextBridge.exposeInMainWorld("ext", { 6 | invert: (name, args = []) => { 7 | return ipcRenderer.invoke("command", name, args); 8 | }, 9 | // receive: (channel: string, func: Function) => { 10 | // ipcRenderer.on(channel, (event, ...args) => func(...args)); 11 | // }, 12 | }); 13 | 14 | contextBridge.exposeInMainWorld("electron", { 15 | platform: process.platform, 16 | }); 17 | -------------------------------------------------------------------------------- /electron/ts/rag/lib.mts: -------------------------------------------------------------------------------- 1 | import type { RAGApplication } from "@llm-tools/embedjs"; 2 | import { GPT_MODELS, KNOWLEDGE_Store } from "../../../common/data"; 3 | import { 4 | LibSqlDb, 5 | OpenAiEmbeddings, 6 | PdfLoader, 7 | RAGApplicationBuilder, 8 | TextLoader, 9 | } from "ts/es6.mjs"; 10 | 11 | export class MyRag { 12 | app: RAGApplication; 13 | async search(query: string, top) { 14 | return (await this.app.search(query)).slice(0, top); 15 | } 16 | remove(uniqueId: string) { 17 | return this.app.deleteLoader(uniqueId); 18 | } 19 | async addText(text: string) { 20 | return this.app.addLoader(new TextLoader({ text: "..." })); 21 | } 22 | async addPdf(filepath: string) { 23 | return this.app.addLoader(new PdfLoader({ filePathOrUrl: filepath })); 24 | } 25 | async init(storePath: string, store: KNOWLEDGE_Store) { 26 | let gpt_m = GPT_MODELS.initSync().data.find((x) => x.key == store.model); 27 | if (gpt_m == null) { 28 | throw new Error("Model not found"); 29 | } 30 | // console.log("Using model", gpt_m); 31 | this.app = await new RAGApplicationBuilder() 32 | .setModel("NO_MODEL") 33 | .setEmbeddingModel( 34 | new OpenAiEmbeddings({ 35 | model: gpt_m.model, 36 | configuration: { baseURL: gpt_m.baseURL }, 37 | apiKey: gpt_m.apiKey, 38 | }) 39 | ) 40 | .setVectorDatabase(new LibSqlDb({ path: storePath })) 41 | .build(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /electron/ts/upload.mts: -------------------------------------------------------------------------------- 1 | // //* 检查更新工具类 2 | // import { autoUpdater } from "electron-updater"; 3 | 4 | // //* 引入工具类 5 | 6 | // import path from "path"; 7 | // import { getMessageService } from "./message_service.mjs"; 8 | 9 | 10 | // import { Logger } from "ts/polyfills/index.mjs"; 11 | 12 | // // autoUpdater.logger = Logger; 13 | 14 | // class CheckUpdate { 15 | // constructor() { 16 | // //* 设置检查更新的url 可以不设置 忽略 17 | // autoUpdater.autoDownload = false; 18 | // // autoUpdater.forceDevUpdateConfig = true; // 强制使用dev配置 19 | // autoUpdater.setFeedURL({ 20 | // provider: "github", 21 | // owner: "BigSweetPotatoStudio", 22 | // repo: "HyperChat", 23 | // }); 24 | 25 | // //* 修改配置地址 dev使用 开发环境需要注释 根据自己路径来,需要获取app-update.yml 26 | // // log.info( 27 | // // "check update", 28 | // // fs.existsSync(path.join(__dirname, "../dist/latest-mac.yml")) 29 | // // ); 30 | // // autoUpdater.updateConfigPath = path.join( 31 | // // __dirname, 32 | // // "../dist/latest-mac.yml" 33 | // // ); 34 | // this.updaterEvent(); 35 | // } 36 | // checkUpdate() { 37 | // //* 开始检查更新 38 | // return autoUpdater.checkForUpdatesAndNotify().catch((err) => { 39 | // Logger.info("网络连接问题,检测更新失败", err); 40 | // }); 41 | // } 42 | // // 退出并安装 43 | // quitAndInstall() { 44 | // autoUpdater.quitAndInstall(); 45 | // } 46 | // download() { 47 | // autoUpdater.downloadUpdate(); 48 | // } 49 | // updaterEvent() { 50 | // //* 监听updater的事件 51 | // /** 52 | // * -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载完成 53 | // **/ 54 | // // 当开始检查更新的时候触发 55 | // autoUpdater.on("checking-for-update", () => { 56 | // Logger.info("开始检查更新"); 57 | // sendToRender("UpdateMsg", { status: 0 }); 58 | // }); 59 | 60 | // // 发现可更新数据时 61 | // autoUpdater.on("update-available", (info) => { 62 | // Logger.info("有更新", info); 63 | // sendToRender("UpdateMsg", { status: 1, info: info }); 64 | // }); 65 | 66 | // // 没有可更新数据时 67 | // autoUpdater.on("update-not-available", (info) => { 68 | // Logger.info("没有更新", info); 69 | // sendToRender("UpdateMsg", { status: 2, info: info }); 70 | // }); 71 | 72 | // // 下载监听 73 | // autoUpdater.on("download-progress", (progressObj) => { 74 | // Logger.info("下载监听", progressObj); 75 | // sendToRender("download-progress", progressObj); 76 | // }); 77 | 78 | // // 下载完成 79 | // autoUpdater.on("update-downloaded", () => { 80 | // Logger.info("下载完成"); 81 | // sendToRender("UpdateMsg", { status: 4 }); 82 | // }); 83 | // // 当更新发生错误的时候触发。 84 | // autoUpdater.on("error", (err) => { 85 | // Logger.info("更新出现错误", err.message); 86 | // if (err.message.includes("sha512 checksum mismatch")) { 87 | // sendToRender(-1, "sha512校验失败"); 88 | // } else { 89 | // sendToRender(-1, "错误信息请看主进程控制台"); 90 | // } 91 | // }); 92 | // } 93 | // } 94 | 95 | // export default CheckUpdate; 96 | // function sendToRender(type, data) { 97 | // getMessageService().sendAllToRenderer({ 98 | // type: type, 99 | // data: data, 100 | // }); 101 | // } 102 | 103 | // export let checkUpdate = new CheckUpdate(); 104 | -------------------------------------------------------------------------------- /electron/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": false, 5 | "skipLibCheck": true, 6 | "composite": false, 7 | "resolveJsonModule": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "jsxFactory": "React.createElement", 11 | "jsx": "react", 12 | "module": "ESNext", 13 | "moduleResolution": "bundler", 14 | "removeComments": false, 15 | "sourceMap": true, 16 | "strict": false, 17 | "strictNullChecks": false, 18 | "lib": [ 19 | "ESNext", 20 | "DOM", 21 | ], 22 | "baseUrl": ".", 23 | "paths": { 24 | "ts": [ 25 | "./ts", 26 | ], 27 | }, 28 | "target": "ESNext", 29 | "typeRoots": [ 30 | "./node_modules/@types", 31 | "./types/" 32 | ], 33 | "outDir": "./js", 34 | }, 35 | "include": [ 36 | "ts", 37 | "types/*" 38 | ], 39 | "exclude": [ 40 | "node_modules" 41 | ] 42 | } -------------------------------------------------------------------------------- /electron/types/index.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | ext: any; 3 | tools: any; 4 | getTools: any; 5 | } 6 | 7 | declare module "*.txt" { 8 | const value: any; 9 | export default value; 10 | } 11 | -------------------------------------------------------------------------------- /electron/webpack.config.js: -------------------------------------------------------------------------------- 1 | // import webpack from 'webpack'; 2 | // import path from 'path'; 3 | // import { fileURLToPath } from 'url'; 4 | // import nodeExternals from 'webpack-node-externals'; 5 | // // 获取当前文件的目录名 6 | // const __filename = fileURLToPath(import.meta.url); 7 | // const __dirname = path.dirname(__filename); 8 | 9 | const path = require("path"); 10 | const webpack = require("webpack"); 11 | const nodeExternals = require("webpack-node-externals"); 12 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); 13 | 14 | module.exports = (env, argv) => { 15 | console.log("ENV:", process.env.NODE_ENV); // 打印出传入的环境变量 16 | // console.log('Mode:', argv.mode); // 打印出Webpack的mode值 17 | 18 | const isDev = process.env.NODE_ENV !== "production" ? true : false; 19 | return { 20 | target: "node", 21 | entry: { 22 | main: "./ts/main", 23 | preload: "./ts/preload", 24 | }, 25 | // publicPath: '/', 26 | // experiments: { 27 | // outputModule: true, 28 | // }, 29 | // externalsType: "module", // in order to ignore built-in modules like path, fs, etc. 30 | externalsPresets: { node: true }, // in order to ignore built-in modules like path, fs, etc. 31 | externals: [nodeExternals()], 32 | // externals: { 33 | // "electron-screenshots": 'require("electron-screenshots")', 34 | // }, 35 | plugins: [ 36 | // new Dotenv(), 37 | new webpack.EnvironmentPlugin({ 38 | NODE_ENV: "production", 39 | myEnv: process.env.myEnv || "prod", 40 | runtime: "node", 41 | // no_electron: "0", 42 | }), 43 | ], 44 | module: { 45 | rules: [ 46 | { 47 | test: /\.[cm]?(ts|js)x?$/, 48 | use: { 49 | loader: "ts-loader", 50 | options: { 51 | configFile: "tsconfig.json", 52 | // transpileOnly: true, // 确保放在这里 53 | }, 54 | }, 55 | exclude: /node_modules/, 56 | resolve: { 57 | fullySpecified: false, 58 | }, 59 | }, 60 | { 61 | test: /\.txt$/i, 62 | use: "raw-loader", 63 | }, 64 | ], 65 | }, 66 | resolve: { 67 | extensions: [".tsx", ".ts", ".js", ".mts", ".mjs", ".jsx", ".css"], 68 | // Add support for TypeScripts fully qualified ESM imports. 69 | extensionAlias: { 70 | ".js": [".js", ".ts"], 71 | ".cjs": [".cjs", ".cts"], 72 | ".mjs": [".mjs", ".mts"], 73 | }, 74 | alias: { 75 | ts: path.resolve(__dirname, "./ts"), 76 | }, 77 | plugins: [ 78 | new TsconfigPathsPlugin({ 79 | configFile: path.resolve(__dirname, "./tsconfig.json"), 80 | }), 81 | ], 82 | }, 83 | output: { 84 | filename: "[name].js", // 使用 contenthash 作为文件名的一部分 85 | chunkFilename: "[name].js", // 对于动态导入的模块 86 | path: path.resolve(__dirname, "js"), 87 | libraryTarget: "umd", // 输出格式 88 | }, 89 | mode: "development", 90 | devtool: false, 91 | optimization: { 92 | minimize: false, // This disables minification even in production mode 93 | }, 94 | devServer: { 95 | static: "./build", // 告诉服务器从哪里提供内容,通常是webpack的输出目录 96 | port: 3000, // 设置端口号,默认是8080 97 | open: false, // 告诉dev-server在服务器启动后打开浏览器 98 | hot: true, // 启用webpack的模块热替换特性(HMR) 99 | compress: true, // 启用gzip压缩 100 | historyApiFallback: true, // 当找不到路径的时候,默认加载index.html文件 101 | // more options... 102 | }, 103 | }; 104 | }; 105 | -------------------------------------------------------------------------------- /electron/webpack.no_electron.js: -------------------------------------------------------------------------------- 1 | // import webpack from 'webpack'; 2 | // import path from 'path'; 3 | // import { fileURLToPath } from 'url'; 4 | // import nodeExternals from 'webpack-node-externals'; 5 | // // 获取当前文件的目录名 6 | // const __filename = fileURLToPath(import.meta.url); 7 | // const __dirname = path.dirname(__filename); 8 | 9 | const path = require("path"); 10 | const webpack = require("webpack"); 11 | const nodeExternals = require("webpack-node-externals"); 12 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); 13 | 14 | module.exports = (env, argv) => { 15 | console.log("ENV:", process.env.NODE_ENV); // 打印出传入的环境变量 16 | // console.log('Mode:', argv.mode); // 打印出Webpack的mode值 17 | 18 | const isDev = process.env.NODE_ENV !== "production" ? true : false; 19 | return { 20 | target: "node", 21 | entry: { 22 | main_no_electron: "./ts/main_no_electron", 23 | }, 24 | // publicPath: '/', 25 | // experiments: { 26 | // outputModule: true, 27 | // }, 28 | // externalsType: "module", // in order to ignore built-in modules like path, fs, etc. 29 | externalsPresets: { node: true }, // in order to ignore built-in modules like path, fs, etc. 30 | externals: [nodeExternals()], 31 | // externals: { 32 | // "electron-screenshots": 'require("electron-screenshots")', 33 | // }, 34 | plugins: [ 35 | // new Dotenv(), 36 | new webpack.EnvironmentPlugin({ 37 | NODE_ENV: "production", 38 | myEnv: process.env.myEnv || "prod", 39 | runtime: "node", 40 | no_electron: "1", 41 | }), 42 | ], 43 | module: { 44 | rules: [ 45 | { 46 | test: /\.[cm]?(ts|js)x?$/, 47 | use: { 48 | loader: "ts-loader", 49 | options: { 50 | configFile: "tsconfig.json", 51 | transpileOnly: true, // 确保放在这里 52 | }, 53 | }, 54 | exclude: /node_modules/, 55 | resolve: { 56 | fullySpecified: false, 57 | }, 58 | }, 59 | { 60 | test: /\.txt$/i, 61 | use: "raw-loader", 62 | }, 63 | ], 64 | }, 65 | resolve: { 66 | extensions: [".tsx", ".ts", ".js", ".mts", ".mjs", ".jsx", ".css"], 67 | // Add support for TypeScripts fully qualified ESM imports. 68 | extensionAlias: { 69 | ".js": [".js", ".ts"], 70 | ".cjs": [".cjs", ".cts"], 71 | ".mjs": [".mjs", ".mts"], 72 | }, 73 | alias: { 74 | ts: path.resolve(__dirname, "./ts"), 75 | }, 76 | plugins: [ 77 | new TsconfigPathsPlugin({ 78 | configFile: path.resolve(__dirname, "./tsconfig.json"), 79 | }), 80 | ], 81 | }, 82 | output: { 83 | filename: "[name].js", // 使用 contenthash 作为文件名的一部分 84 | chunkFilename: "[name].js", // 对于动态导入的模块 85 | path: path.resolve(__dirname, "js"), 86 | libraryTarget: "umd", // 输出格式 87 | }, 88 | mode: isDev ? "development" : "production", 89 | devtool: false, 90 | optimization: { 91 | minimize: false, // This disables minification even in production mode 92 | }, 93 | devServer: { 94 | static: "./build", // 告诉服务器从哪里提供内容,通常是webpack的输出目录 95 | port: 3000, // 设置端口号,默认是8080 96 | open: false, // 告诉dev-server在服务器启动后打开浏览器 97 | hot: true, // 启用webpack的模块热替换特性(HMR) 98 | compress: true, // 启用gzip压缩 99 | historyApiFallback: true, // 当找不到路径的时候,默认加载index.html文件 100 | // more options... 101 | }, 102 | }; 103 | }; 104 | -------------------------------------------------------------------------------- /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image1.png -------------------------------------------------------------------------------- /images/image11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image11.png -------------------------------------------------------------------------------- /images/image12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image12.png -------------------------------------------------------------------------------- /images/image13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image13.png -------------------------------------------------------------------------------- /images/image14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image14.png -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image2.png -------------------------------------------------------------------------------- /images/image20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image20.png -------------------------------------------------------------------------------- /images/image21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image21.png -------------------------------------------------------------------------------- /images/image22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image22.png -------------------------------------------------------------------------------- /images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image3.png -------------------------------------------------------------------------------- /images/image30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image30.png -------------------------------------------------------------------------------- /images/image31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image31.png -------------------------------------------------------------------------------- /images/image32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image32.png -------------------------------------------------------------------------------- /images/image33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image33.png -------------------------------------------------------------------------------- /images/image34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image34.png -------------------------------------------------------------------------------- /images/image35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image35.png -------------------------------------------------------------------------------- /images/image36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image36.png -------------------------------------------------------------------------------- /images/image40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image40.png -------------------------------------------------------------------------------- /images/image41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image41.png -------------------------------------------------------------------------------- /images/image42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image42.png -------------------------------------------------------------------------------- /images/image43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image43.png -------------------------------------------------------------------------------- /images/image44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image44.png -------------------------------------------------------------------------------- /images/image45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image45.png -------------------------------------------------------------------------------- /images/image46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image46.png -------------------------------------------------------------------------------- /images/image47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image47.png -------------------------------------------------------------------------------- /images/image48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image48.png -------------------------------------------------------------------------------- /images/image50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image50.png -------------------------------------------------------------------------------- /images/image51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image51.png -------------------------------------------------------------------------------- /images/image52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image52.png -------------------------------------------------------------------------------- /images/image53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image53.png -------------------------------------------------------------------------------- /images/image55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image55.png -------------------------------------------------------------------------------- /images/image60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image60.png -------------------------------------------------------------------------------- /images/image61.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image61.png -------------------------------------------------------------------------------- /images/image62.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/images/image62.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dadigua/hyper-chat", 3 | "version": "1.8.1-alpha.1", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "tsx task.mts --dev", 7 | "test": "tsx task.mts --test", 8 | "prod": "tsx task.mts --prod", 9 | "build": "tsx task.mts --build", 10 | "pre_commit": "tsx task.mts --pre_commit", 11 | "prod_node": "tsx task.mts --prod_node", 12 | "updateVersion": "tsx task.mts --updateVersion", 13 | "docs:dev": "vuepress dev docs", 14 | "docs:build": "vuepress build docs" 15 | }, 16 | "pre-commit": [], 17 | "author": "", 18 | "license": "ISC", 19 | "description": "", 20 | "dependencies": { 21 | "@modelcontextprotocol/sdk": "1.10.2", 22 | "cross-spawn": "^7.0.6", 23 | "dotenv": "16.4.7", 24 | "lodash": "^4.17.21", 25 | "openai": "^4.94.0", 26 | "tsx": "^4.9.3", 27 | "uuid": "^11.1.0", 28 | "zod": "^3.24.3", 29 | "zx": "8.0.1" 30 | }, 31 | "devDependencies": { 32 | "@vuepress/bundler-vite": "^2.0.0-rc.22", 33 | "@vuepress/theme-default": "^2.0.0-rc.98", 34 | "pre-commit": "^1.2.2", 35 | "sass-embedded": "^1.87.0", 36 | "vue": "^3.5.13", 37 | "vuepress": "^2.0.0-rc.22" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /translate.mts: -------------------------------------------------------------------------------- 1 | import { 2 | YAML, 3 | fs, 4 | argv, 5 | path, 6 | $, 7 | quotePowerShell, 8 | usePowerShell, 9 | os, 10 | } from "zx"; 11 | import OpenAI from "openai"; 12 | import { fileURLToPath } from "url"; 13 | import "dotenv/config"; 14 | 15 | 16 | if (os.platform() === "win32") { 17 | usePowerShell(); 18 | } 19 | 20 | const __filename = fileURLToPath(import.meta.url); 21 | const __dirname = path.dirname(__filename); 22 | 23 | let p = path.resolve(__dirname, "./web/src/i18n.json"); 24 | 25 | const openai = new OpenAI({ 26 | apiKey: process.env.apiKey, // This is the default and can be omitted 27 | baseURL: process.env.baseURL, 28 | }); 29 | 30 | // let o = YAML.parse(fs.readFileSync('./src/pages2/text.yaml').toString()); 31 | // fs.writeFileSync('./src/pages2/text.json', JSON.stringify(o, null, 2)) 32 | if (argv.test) { 33 | let s = await translateEN(fs.readFileSync("./README.zh.md").toString()); 34 | fs.writeFileSync( 35 | "./README.md", 36 | `[中文](README.zh.md) | [English](README.md) 37 | \n 38 | ${s}` 39 | ); 40 | let c = await translateEN(fs.readFileSync("./ChangeLog.zh.md").toString()); 41 | fs.writeFileSync( 42 | "./ChangeLog.md", 43 | `[中文](ChangeLog.zh.md) | [English](ChangeLog.md) 44 | \n 45 | ${c}` 46 | ); 47 | console.log("end"); 48 | } else { 49 | let json = JSON.parse(fs.readFileSync(p).toString()); 50 | for (let key in json) { 51 | // if (!hasChinese(key)) { 52 | // delete json[key]; 53 | // continue; 54 | // } 55 | // if (!find(key)) { 56 | // delete json[key]; 57 | // continue; 58 | // } 59 | if (json[key].zh == null) { 60 | json[key].zh = await translateZh(key); 61 | } 62 | } 63 | fs.writeFileSync(p, JSON.stringify(json, null, 2)); 64 | 65 | if ((await $`git diff README.zh.md`).toString().length > 0) { 66 | let s = await translateEN(fs.readFileSync("./README.zh.md").toString()); 67 | fs.writeFileSync( 68 | "./README.md", 69 | `[中文](README.zh.md) | [English](README.md) 70 | \n 71 | ${s}` 72 | ); 73 | } 74 | if ((await $`git diff ChangeLog.zh.md`).toString().length > 0) { 75 | let c = await translateEN(fs.readFileSync("./ChangeLog.zh.md").toString()); 76 | fs.writeFileSync( 77 | "./ChangeLog.md", 78 | `[中文](ChangeLog.zh.md) | [English](ChangeLog.md) 79 | \n 80 | ${c}` 81 | ); 82 | } 83 | } 84 | 85 | export async function translateZh(content) { 86 | content = `作为AI翻译助手,请将以下内容从英文翻译成中文: 87 | 88 | 规则: 89 | 1. 保持原文的格式和换行 90 | 2. 不要添加额外的标点符号 91 | 3. 如果是空字符串,请返回空字符串 92 | 93 | 需要翻译的内容是: "${content}" 94 | 95 | 直接返回翻译结果,无需解释。`; 96 | const chatCompletion: any = await openai.chat.completions.create({ 97 | messages: [ 98 | { 99 | role: "user", 100 | content: content, 101 | }, 102 | ], 103 | model: "gpt-4o-mini", 104 | }); 105 | return chatCompletion.choices[0].message.content.replace(/^"|"$/g, ""); 106 | } 107 | 108 | export async function translateEN(content) { 109 | content = `作为AI翻译助手,请将以下内容从中文翻译成英文: 110 | 111 | 规则: 112 | 1. 仅翻译中文文字内容 113 | 2. 保持所有标点符号和特殊字符的原样,包括但不限于 。,!?、(){}[]【】等 114 | 3. 保持原文的格式和换行 115 | 4. 不要添加额外的标点符号 116 | 5. 输出流畅自然的英文表达 117 | 6. 如果是空字符串,请返回空字符串 118 | 119 | 需要翻译的内容是: "${content}" 120 | 121 | 直接返回翻译结果,无需解释。`; 122 | const chatCompletion: any = await openai.chat.completions.create({ 123 | messages: [ 124 | { 125 | role: "user", 126 | content: content, 127 | }, 128 | ], 129 | model: "gpt-4o-mini", 130 | }); 131 | return chatCompletion.choices[0].message.content.replace(/^"|"$/g, ""); 132 | } 133 | 134 | function hasChinese(str) { 135 | return /[\u4e00-\u9fa5]/.test(str); 136 | } 137 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": false, 5 | "composite": false, 6 | "esModuleInterop": true, 7 | "jsxFactory": "React.createElement", 8 | "jsxFragmentFactory": "React.Fragment", 9 | "jsx": "react", 10 | "module": "esnext", 11 | "moduleResolution": "Node", 12 | "experimentalDecorators": true, 13 | "removeComments": false, 14 | "resolveJsonModule": true, 15 | "sourceMap": true, 16 | "strict": false, 17 | "strictNullChecks": false, 18 | "lib": [ 19 | "ESNext", 20 | "DOM" 21 | ], 22 | "types": [ 23 | "node", 24 | ], 25 | "target": "ESNext", 26 | "typeRoots": [ 27 | "./node_modules/@types", 28 | "./types/" 29 | ], 30 | "outDir": "./dist" 31 | }, 32 | "include": [ 33 | "common" 34 | ], 35 | "exclude": [ 36 | "node_modules" 37 | ] 38 | } -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .env.production 25 | package-lock.json 26 | tmp/ 27 | .DS_Store 28 | tests/tmp -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "prettier-plugin-tailwindcss" 4 | ] 5 | } -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /web/eval/common.ts: -------------------------------------------------------------------------------- 1 | export class ExecResult { 2 | data: T; 3 | step = 1; 4 | stepCount: number; 5 | progress = 0; 6 | error = { 7 | code: 0, 8 | message: "", 9 | }; 10 | constructor(stepCount: number) { 11 | this.stepCount = stepCount; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /web/postcss.config.js: -------------------------------------------------------------------------------- 1 | // postcss.config.js 2 | module.exports = { 3 | plugins: [ 4 | require("postcss-import"), 5 | require("tailwindcss"), 6 | require("autoprefixer"), 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 14 | 59 | 64 | 65 | 66 | 67 | 68 | 73 |
74 |
75 |
76 |
77 |
78 | 79 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /web/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/web/public/logo.png -------------------------------------------------------------------------------- /web/public/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweetPotatoStudio/HyperChat/ecea3206176fbd8818c2ab493575f936aa17f544/web/public/logo2.png -------------------------------------------------------------------------------- /web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /web/public/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 离线模式 - HyperChat 7 | 54 | 55 | 56 |
57 |
📡
58 |

您当前处于离线状态

59 |

无法连接到服务器,请检查您的网络连接并稍后再试。

60 |

您之前访问过的内容仍然可以使用。

61 | 刷新页面 62 |
63 | 64 | -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /web/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Routes, Route, Outlet, Link, useNavigate } from "react-router-dom"; 3 | 4 | import { 5 | Button, 6 | Table, 7 | Switch, 8 | Modal, 9 | message, 10 | Radio, 11 | Input, 12 | Tabs, 13 | ConfigProvider, 14 | Popconfirm, 15 | Popover, 16 | Dropdown, 17 | Space, 18 | MenuProps, 19 | Select, 20 | Spin, 21 | Progress, 22 | App as AntdApp, 23 | } from "antd"; 24 | import enUS from "antd/locale/en_US"; 25 | import zhCN from "antd/locale/zh_CN"; 26 | 27 | import { 28 | ChromeFilled, 29 | CrownFilled, 30 | DownOutlined, 31 | ExclamationCircleFilled, 32 | GithubFilled, 33 | InfoCircleFilled, 34 | LoadingOutlined, 35 | LogoutOutlined, 36 | QuestionCircleFilled, 37 | SmileFilled, 38 | SmileOutlined, 39 | TabletFilled, 40 | } from "@ant-design/icons"; 41 | 42 | import { HeaderContext } from "./common/context"; 43 | import { PageContainer, ProCard, ProLayout } from "@ant-design/pro-components"; 44 | import { getRoute, getLayoutRoute } from "./router"; 45 | import { electronData, DataList } from "../../common/data"; 46 | import { call } from "./common/call"; 47 | import { EVENT } from "./common/event"; 48 | 49 | export default function App() { 50 | const [loading, setLoading] = useState(false); 51 | useEffect(() => { 52 | (async () => { 53 | let st = await electronData.init(); 54 | if (st.autoSync) { 55 | setLoading(true); 56 | try { 57 | await call("webDavSync", []); 58 | 59 | setLoading(false); 60 | } catch (e) { 61 | setLoading(false); 62 | console.error(e); 63 | } 64 | } 65 | })(); 66 | }, []); 67 | 68 | return ( 69 |
70 | 74 | 75 | {getRoute(getLayoutRoute())} 76 | 77 | 78 |
79 | ); 80 | } 81 | 82 | function NoMatch() { 83 | return ( 84 |
85 |

Nothing to see here!

86 |

87 | Go to the home page 88 |

89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /web/src/common/call.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from "../../../electron/ts/command.mts"; 2 | import { io } from "socket.io-client"; 3 | import { sleep } from "./sleep"; 4 | import { isOnBrowser } from "./const"; 5 | let ext = {} as any; 6 | if (typeof window == "undefined") { 7 | ext = { ...global.ext }; 8 | } else { 9 | ext = { ...window.ext }; 10 | } 11 | 12 | globalThis.ext2 = ext; 13 | 14 | let websocket = undefined; 15 | let URL_PRE; 16 | 17 | export function getURL_PRE() { 18 | return URL_PRE; 19 | } 20 | 21 | if (process.env.runtime !== "node") { 22 | // web环境 23 | URL_PRE = location.origin + location.pathname.replace("index.html", ""); 24 | 25 | if (ext.invert && process.env.myEnv != "prod") { 26 | let config = await ext.invert("getConfig", []); 27 | URL_PRE = 28 | "http://localhost:" + config.data.port + "/" + config.data.password + "/"; 29 | } 30 | ext.invert = async (command: string, args: any, options: any = {}) => { 31 | const { signal } = options; 32 | let res = await fetch(URL_PRE + "api/" + command, { 33 | method: "POST", 34 | headers: { 35 | "Content-Type": "application/json", 36 | }, 37 | body: JSON.stringify(args), 38 | signal: signal, 39 | }).then((res) => res.json()); 40 | return res; 41 | }; 42 | let callbacks: { [key: string]: Array<(data: any) => void> } = {}; 43 | ext.receive = (channel: string, listener: (data: any) => void) => { 44 | if (callbacks[channel]) { 45 | callbacks[channel].push(listener); 46 | } else { 47 | callbacks[channel] = [listener]; 48 | } 49 | }; 50 | 51 | const socket = io(URL_PRE + "main-message"); 52 | socket.on("connect", () => { 53 | console.log("connected"); 54 | websocket = socket; 55 | }); 56 | socket.on("message-from-main", (data: any) => { 57 | if (process.env.myEnv == "dev") { 58 | console.log("Received message:", data); 59 | } 60 | if (callbacks["message-from-main"]) { 61 | for (let callback of callbacks["message-from-main"]) { 62 | callback(data); 63 | } 64 | } 65 | }); 66 | } 67 | globalThis.ext2.call = call; 68 | export async function call( 69 | command: k, 70 | args: Parameters = [] as any, 71 | options: { signal?: AbortSignal } = {}, 72 | ): Promise> { 73 | try { 74 | // console.log(`command ${command}`, args); 75 | if (isOnBrowser) { 76 | const { replaceCommand } = await import("./callReplaceCommand"); 77 | if (replaceCommand[command]) { 78 | return await replaceCommand[command].apply(null, args); 79 | } 80 | } 81 | let res = await ext.invert(command, args, options); 82 | if (res.success) { 83 | return res.data; 84 | } else { 85 | throw new Error(res.message); 86 | } 87 | } catch (e) { 88 | console.error(command, args, e); 89 | 90 | throw e; 91 | } 92 | } 93 | 94 | export async function msg_receive( 95 | channel: string, 96 | listener: (data: any) => void, 97 | ) { 98 | ext.receive(channel, listener); 99 | } 100 | 101 | export async function getWebSocket() { 102 | while (websocket == null) { 103 | await sleep(500); 104 | } 105 | return websocket; 106 | } 107 | 108 | export { ext }; -------------------------------------------------------------------------------- /web/src/common/callReplaceCommand.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { ext } from "./call"; 4 | import type { Command } from "../../../electron/ts/command.mjs"; 5 | import { Modal } from "antd"; 6 | import { Pre } from "../components/pre"; 7 | export const replaceCommand: Partial = { 8 | setClipboardText: async (text: string) => { 9 | const copy = (text: string) => { 10 | if (navigator.clipboard && window.isSecureContext) { 11 | // Use the modern Clipboard API when available and in secure context 12 | navigator.clipboard.writeText(text).catch((err) => { 13 | console.error("Failed to copy text using Clipboard API:", err); 14 | fallbackCopy(text); 15 | }); 16 | } else { 17 | fallbackCopy(text); 18 | } 19 | }; 20 | 21 | const fallbackCopy = (text: string) => { 22 | const textarea = document.createElement("textarea"); 23 | textarea.value = text; 24 | textarea.style.position = "absolute"; 25 | textarea.style.opacity = "0"; 26 | document.body.appendChild(textarea); 27 | textarea.select(); 28 | document.execCommand("copy"); 29 | document.body.removeChild(textarea); 30 | }; 31 | copy(text); 32 | }, 33 | openExplorer: async (path: string) => { 34 | let res = await ext.invert("readFile", [path, ""]); 35 | 36 | Modal.info({ 37 | title: 'File Content', 38 | width: "80%", 39 | style: { 40 | maxWidth: 1024, 41 | }, 42 | maskClosable: true, 43 | content: ( 44 |
45 |           {res.data}
46 |         
47 | ), 48 | onOk() { }, 49 | }); 50 | }, 51 | }; -------------------------------------------------------------------------------- /web/src/common/code.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useRef, useState } from "react"; 2 | 3 | import { Button, Form, Input, message, Modal, Space } from "antd"; 4 | import { CopyOutlined } from "@ant-design/icons"; 5 | import { call } from "./call"; 6 | 7 | export function Code(props) { 8 | return ( 9 | 10 | {props.children}{" "} 11 | { 14 | await call("setClipboardText", [props.children]); 15 | message.success("Copied to clipboard"); 16 | }} 17 | /> 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /web/src/common/config.ts: -------------------------------------------------------------------------------- 1 | import { version } from "react"; 2 | 3 | export const config = { 4 | approot: '', 5 | aiServicePort: '', 6 | danmuWSport: '', 7 | version: '' 8 | } -------------------------------------------------------------------------------- /web/src/common/const.ts: -------------------------------------------------------------------------------- 1 | export const isOnBrowser = typeof window != "undefined" ? !window.ext : false; 2 | -------------------------------------------------------------------------------- /web/src/common/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | import { InitedClient } from "./mcp"; 3 | 4 | export const UserContext = createContext(null); 5 | 6 | export const HeaderContext = createContext<{ 7 | mcpClients: InitedClient[], 8 | globalState: number, 9 | setLang: (lang: string) => void, 10 | updateGlobalState: (num) => void 11 | }>(null); 12 | 13 | // 创建全局Context 14 | export const GlobalContext = createContext({}); -------------------------------------------------------------------------------- /web/src/common/data.ts: -------------------------------------------------------------------------------- 1 | import { call, msg_receive } from "./call"; 2 | import { 3 | AppSetting, 4 | ChatHistory, 5 | Agents, 6 | GPT_MODELS, 7 | MCP_CONFIG, 8 | electronData, 9 | Data, 10 | MCP_CONFIG_TYPE, 11 | DataList, 12 | } from "../../../common/data.js"; 13 | import { e } from "./service"; 14 | 15 | Data.prototype.init = async function ({ } = {}) { 16 | try { 17 | this.localStorage = await call("readJSON", [this.KEY]); 18 | } catch (e) { 19 | this.localStorage = {}; 20 | } 21 | this.data = this.options.formatInit(Object.assign({}, this.data, this.localStorage)); 22 | return this.data; 23 | } 24 | 25 | Data.prototype.save = async function () { 26 | return await call("writeJSON", [ 27 | this.KEY, 28 | this.options.formatSave(this.data), 29 | ]); 30 | } 31 | 32 | 33 | // 初始化配置 34 | // await electronData.init(); 35 | // if (electronData.get().firstOpen) { 36 | // await MCP_CONFIG.init(); 37 | // MCP_CONFIG.save(); 38 | // await GPT_MODELS.init(); 39 | // GPT_MODELS.save(); 40 | // electronData.get().firstOpen = false; 41 | // await electronData.save(); 42 | // } 43 | 44 | msg_receive("message-from-main", (msg) => { 45 | if (msg.type == "syncNodeToWeb") { 46 | let c = DataList.find((x) => x.KEY == msg.data.key); 47 | if (c) { 48 | // if (c.KEY == "ChatHistory.json") { 49 | // let newData = msg.data.data; 50 | 51 | // for (let x of newData.data) { 52 | // if (c.get().data.find((y) => y.key == x.key) == null) { 53 | // c.get().data.push(x); 54 | // } else { 55 | // break; 56 | // } 57 | // } 58 | // } else { 59 | Object.assign(c.get(), msg.data.data); 60 | // } 61 | } else { 62 | console.error("syncNodeToWeb", msg.data.key, "not found"); 63 | } 64 | } 65 | }); 66 | 67 | // await ChatHistory.init(); 68 | // try { 69 | // if ( 70 | // !electronData.get().updated[electronData.get().version] && 71 | // electronData.get().version == "0.3.0" 72 | // ) { 73 | // // 更新配置 74 | // let h = await ChatHistory.init(); 75 | // for (let d of h.data) { 76 | // d.dateTime = d.dateTime || Date.now(); 77 | // for (let m of d.messages) { 78 | // m.content_tool_calls = m.tool_calls; 79 | // } 80 | // } 81 | // await ChatHistory.save(); 82 | // // 83 | // electronData.get().updated[electronData.get().version] = true; 84 | // await electronData.save(); 85 | // } 86 | // } catch (e) {} 87 | -------------------------------------------------------------------------------- /web/src/common/dndTable.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useMemo } from "react"; 2 | import { HolderOutlined } from "@ant-design/icons"; 3 | import type { DragEndEvent } from "@dnd-kit/core"; 4 | import { DndContext } from "@dnd-kit/core"; 5 | import type { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities"; 6 | import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; 7 | import { 8 | arrayMove, 9 | SortableContext, 10 | useSortable, 11 | verticalListSortingStrategy, 12 | } from "@dnd-kit/sortable"; 13 | import { CSS } from "@dnd-kit/utilities"; 14 | import { Button, Table } from "antd"; 15 | import type { TableColumnsType } from "antd"; 16 | 17 | interface RowContextProps { 18 | setActivatorNodeRef?: (element: HTMLElement | null) => void; 19 | listeners?: SyntheticListenerMap; 20 | } 21 | 22 | const RowContext = React.createContext({}); 23 | 24 | const DragHandle: React.FC = () => { 25 | const { setActivatorNodeRef, listeners } = useContext(RowContext); 26 | return ( 27 | 34 | 35 | 36 | } 37 | 38 | 39 | ; 40 | } -------------------------------------------------------------------------------- /web/src/components/pre.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | export function Pre(p) { 5 | return ( 6 |
7 |
13 |           {p.children as string}
14 |         
15 |
16 | ); 17 | } -------------------------------------------------------------------------------- /web/src/i18n.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import json from "./i18n.json"; 4 | import { call } from "./common/call"; 5 | const i18nText = {}; 6 | Object.assign(i18nText, json); 7 | let currLang = navigator.language == "zh-CN" ? "zhCN" : "enUS"; 8 | 9 | if (localStorage.getItem("currLang")) { 10 | currLang = localStorage.getItem("currLang"); 11 | } 12 | if (process.env.NODE_ENV == "development") { 13 | window.localStorage.setItem("i18nText", JSON.stringify(i18nText, null, 2)); 14 | } 15 | 16 | export function t(strings, ...values) { 17 | // strings: 模板字符串的静态部分 18 | // values: 插值的动态部分 19 | let str = strings.reduce( 20 | (result, str, i) => result + str + (values[i] || ""), 21 | "", 22 | ); 23 | // console.log("str: ", i18nText, str); 24 | if (i18nText[str] == null) { 25 | if (process.env.NODE_ENV == "development" && hasEnglish(str)) { 26 | i18nText[str] = { 27 | en: str, 28 | zh: null, 29 | }; 30 | window.localStorage.setItem( 31 | "i18nText", 32 | JSON.stringify(i18nText, null, 2), 33 | ); 34 | } 35 | return str; 36 | } else { 37 | if (currLang == "zhCN") { 38 | return i18nText[str].zh || str; 39 | } else { 40 | return str; 41 | } 42 | } 43 | } 44 | 45 | // currLang = "enUS"; 46 | console.log("currLang: ", currLang); 47 | const setCurrLang = (lang) => { 48 | currLang = lang; 49 | localStorage.setItem("currLang", lang); 50 | }; 51 | // window.translate = function (child) { 52 | // if (i18nText[child] == null) { 53 | // if (process.env.NODE_ENV == "development" && hasEnglish(child)) { 54 | // i18nText[child] = { 55 | // en: child, 56 | // zh: null, 57 | // }; 58 | // window.localStorage.setItem( 59 | // "i18nText", 60 | // JSON.stringify(i18nText, null, 2), 61 | // ); 62 | // } 63 | // return child; 64 | // } else { 65 | // if (currLang == "zhCN") { 66 | // return i18nText[child].zh || child; 67 | // } else { 68 | // return child; 69 | // } 70 | // } 71 | // }; 72 | function hasEnglish(str: string): boolean { 73 | return /[a-zA-Z]/.test(str); 74 | } 75 | // function hasChinese(str) { 76 | // return /[\u4e00-\u9fa5]/.test(str); 77 | // } 78 | export { currLang, setCurrLang }; 79 | -------------------------------------------------------------------------------- /web/src/index.tsx: -------------------------------------------------------------------------------- 1 | console.log("HyperChat"); 2 | console.log("process.env.myEnv", process.env.myEnv); 3 | document.documentElement.setAttribute('data-color-mode', 'light'); 4 | import "../public/iconfont.js" 5 | import "./i18n"; 6 | import "./common/call"; 7 | import "./common/data"; 8 | import React from "react"; 9 | import ReactDOM from "react-dom/client"; 10 | 11 | import { HashRouter } from "react-router-dom"; 12 | import App from "./App"; 13 | import { call } from "./common/call"; 14 | import { config } from "./common/config"; 15 | import "./tailwind.css"; 16 | import { ConfigProvider } from "antd"; 17 | import { StyleProvider, px2remTransformer } from "@ant-design/cssinjs"; 18 | 19 | 20 | 21 | import { 22 | enable as enableDarkMode, 23 | disable as disableDarkMode, 24 | auto as followSystemColorScheme, 25 | exportGeneratedCSS as collectCSS, 26 | isEnabled as isDarkReaderEnabled, 27 | } from "darkreader"; 28 | import { AppSetting } from "../../common/data"; 29 | 30 | (async () => { 31 | await AppSetting.init(); 32 | if (AppSetting.get().darkTheme) { 33 | enableDarkMode({ 34 | brightness: 100, 35 | contrast: 90, 36 | sepia: 10, 37 | }); 38 | } 39 | })(); // 获取是否自动启动 40 | 41 | function setVhCssVar() { 42 | const vh = window.innerHeight * 0.01; 43 | // 创建全局变量 --vh 44 | document.documentElement.style.setProperty("--vh", `${vh}px`); 45 | } 46 | 47 | setVhCssVar(); 48 | window.addEventListener("resize", setVhCssVar); 49 | 50 | console.log("start"); 51 | const px2rem = px2remTransformer({ 52 | rootValue: 16, // 32px = 1rem; @default 16 53 | }); 54 | 55 | if (document.getElementById("root")) { 56 | const root = ReactDOM.createRoot(document.getElementById("root")); 57 | root.render( 58 | // 59 | 66 | 67 | 68 | 69 | 70 | 71 | , 72 | // 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /web/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/pages/TestPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Editor } from "../../components/editor"; 3 | 4 | 5 | export const TestPage = () => { 6 | useEffect(() => { 7 | 8 | }, []) 9 | 10 | return
11 | console.log(value)}> 12 |
13 | }; 14 | 15 | -------------------------------------------------------------------------------- /web/src/pages/Toolbox/Toolbox.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from "react"; 2 | import { Editor } from "../../components/editor"; 3 | import { Tabs, Button, Space, message, Select, Divider } from "antd"; 4 | import { t } from "@/src/i18n"; 5 | import { AudioOutlined, AudioMutedOutlined } from "@ant-design/icons"; 6 | import { Divide } from "lucide-react"; 7 | 8 | import { experimental_transcribe as transcribe } from 'ai'; 9 | import { createOpenAI } from '@ai-sdk/openai'; 10 | import { GPT_MODELS } from "../../../../common/data"; 11 | import GenerateSpeechPanel from "./generateSpeech"; 12 | import { TranscribePanel } from "./Transcribe"; 13 | import GenerateImagePanel from "./generateImage"; 14 | import { useLocation } from "react-router-dom"; 15 | 16 | export const ToolboxPage = () => { 17 | const location = useLocation(); 18 | 19 | 20 | // console.log("ToolboxPage location:", location); 21 | 22 | if (location.pathname == '/Toolbox/Transcribe') { 23 | return ; 24 | } 25 | if (location.pathname == '/Toolbox/GenerateImage') { 26 | return ; 27 | } 28 | if (true || location.pathname == '/Toolbox/GenerateSpeech') { 29 | return ; 30 | } 31 | // return
32 | // , 39 | // }, 40 | // { 41 | // label: t`Generate Speech`, 42 | // key: "generateSpeech", 43 | // children: , 44 | // }, 45 | // { 46 | // label: t`Generate Image`, 47 | // key: "generateImage", 48 | // children: , 49 | // } 50 | // ]} 51 | // /> 52 | //
53 | }; 54 | 55 | -------------------------------------------------------------------------------- /web/src/pages/chat/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./chat"; 2 | -------------------------------------------------------------------------------- /web/src/pages/chat/sortableItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSortable } from "@dnd-kit/sortable"; 3 | import { CSS } from "@dnd-kit/utilities"; 4 | import { RemoveBox } from "./attachR"; 5 | import { 6 | BorderInnerOutlined, 7 | DeleteOutlined, 8 | EditOutlined, 9 | FunctionOutlined, 10 | SwapOutlined, 11 | } from "@ant-design/icons"; 12 | import { getFirstCharacter } from "../../common"; 13 | import { Tooltip } from "antd"; 14 | 15 | export function SortableItem(props) { 16 | const { attributes, listeners, setNodeRef, transform, transition } = 17 | useSortable({ id: props.id }); 18 | 19 | const style = { 20 | transform: CSS.Transform.toString(transform), 21 | transition, 22 | }; 23 | const [hover, setHover] = useState(false); 24 | return ( 25 |
32 | 33 |
{ 39 | setHover(true); 40 | }} 41 | onMouseLeave={() => { 42 | setHover(false); 43 | }} 44 | onClick={() => { 45 | props.onClick && props.onClick(props.item); 46 | }} 47 | > 48 |
49 |
{getFirstCharacter(props.item.label)}
50 |
51 |
52 | {props.item.callable && ( 53 | 54 | )} 55 | {props.item.type == "builtin" && ( 56 | {props.item.type} 57 | )} 58 |
59 | {props.item.type != "builtin" &&
65 | 66 | 67 | { 69 | e.stopPropagation(); 70 | props.onRemove && props.onRemove(props.item); 71 | }} 72 | className="ml-2 cursor-pointer text-red-400 hover:text-red-800" 73 | /> 74 | { 76 | e.stopPropagation(); 77 | props.onEdit && props.onEdit(props.item); 78 | }} 79 | > 80 | 81 | 82 |
83 | } 84 |
85 | {props.item.label} 86 |
87 |
88 |
89 |
90 | ); 91 | } 92 | 93 | -------------------------------------------------------------------------------- /web/src/pages/hyperAgent/callAgentHistory.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useState, 3 | useEffect, 4 | version, 5 | useCallback, 6 | useContext, 7 | useRef, 8 | } from "react"; 9 | import { 10 | Button, 11 | Table, 12 | Switch, 13 | Tooltip, 14 | Modal, 15 | message, 16 | Radio, 17 | Input, 18 | Tabs, 19 | ConfigProvider, 20 | Progress, 21 | Card, 22 | Flex, 23 | Tag, 24 | Space, 25 | Slider, 26 | Form, 27 | InputNumber, 28 | Descriptions, 29 | Select, 30 | } from "antd"; 31 | import { call } from "../../common/call"; 32 | import client from "socket.io-client"; 33 | import SimplePeer from "simple-peer"; 34 | import { 35 | Mic, 36 | Speaker, 37 | Settings, 38 | HelpCircle, 39 | AlertCircle, 40 | Wifi, 41 | VolumeIcon, 42 | VolumeX, 43 | Volume2, 44 | } from "lucide-react"; 45 | import { debounce } from "../../common"; 46 | import { 47 | CloudSyncOutlined, 48 | CopyOutlined, 49 | ExclamationCircleFilled, 50 | } from "@ant-design/icons"; 51 | import { sleep } from "../../common/sleep"; 52 | import dayjs from "dayjs"; 53 | import { useForm } from "antd/es/form/Form"; 54 | import { e } from "../../common/service"; 55 | 56 | export function CallAgentHistory() { 57 | return
CallAgentHistory
; 58 | } 59 | -------------------------------------------------------------------------------- /web/src/pages/hyperAgent/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useState, 3 | useEffect, 4 | version, 5 | useCallback, 6 | useContext, 7 | useRef, 8 | } from "react"; 9 | import { 10 | Button, 11 | Table, 12 | Switch, 13 | Tooltip, 14 | Modal, 15 | message, 16 | Radio, 17 | Input, 18 | Tabs, 19 | ConfigProvider, 20 | Progress, 21 | Card, 22 | Flex, 23 | Tag, 24 | Space, 25 | Slider, 26 | Form, 27 | InputNumber, 28 | Descriptions, 29 | Select, 30 | } from "antd"; 31 | import { call } from "../../common/call"; 32 | import client from "socket.io-client"; 33 | import SimplePeer from "simple-peer"; 34 | import { 35 | Mic, 36 | Speaker, 37 | Settings, 38 | HelpCircle, 39 | AlertCircle, 40 | Wifi, 41 | VolumeIcon, 42 | VolumeX, 43 | Volume2, 44 | } from "lucide-react"; 45 | 46 | import { debounce } from "../../common"; 47 | import { 48 | CloudSyncOutlined, 49 | CopyOutlined, 50 | ExclamationCircleFilled, 51 | } from "@ant-design/icons"; 52 | import { sleep } from "../../common/sleep"; 53 | import dayjs from "dayjs"; 54 | import { useForm } from "antd/es/form/Form"; 55 | import { e } from "../../common/service"; 56 | 57 | export function HyperAgent() { 58 | return
HyperAgent
; 59 | } 60 | -------------------------------------------------------------------------------- /web/src/pages/hypertools/hypertools.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Carousel, 4 | Form, 5 | FormInstance, 6 | FormProps, 7 | Input, 8 | List, 9 | Modal, 10 | Radio, 11 | Segmented, 12 | Select, 13 | Space, 14 | Tree, 15 | TreeDataNode, 16 | TreeProps, 17 | Typography, 18 | message, 19 | } from "antd"; 20 | import React, { useContext, useEffect, useRef, useState } from "react"; 21 | import { call } from "../../common/call"; 22 | import { t } from "../../i18n"; 23 | import { HeaderContext } from "../../common/context"; 24 | 25 | export function HpyerTools() { 26 | const { globalState, updateGlobalState, mcpClients } = useContext(HeaderContext); 27 | const [url, setUrl] = useState(""); 28 | return ( 29 |
30 |
31 | 32 | 33 | 34 | { 38 | setUrl(e.target.value); 39 | }} 40 | /> 41 | 48 | 55 | 56 | 57 | 58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /web/src/pages/knowledgeBase/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./knowledgeBase"; 2 | -------------------------------------------------------------------------------- /web/src/pages/knowledgeBase/knowledgeBaseModal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Carousel, 4 | Checkbox, 5 | Form, 6 | FormInstance, 7 | FormProps, 8 | Input, 9 | InputNumber, 10 | List, 11 | Modal, 12 | Radio, 13 | Segmented, 14 | Select, 15 | Space, 16 | Tree, 17 | TreeDataNode, 18 | TreeProps, 19 | Typography, 20 | message, 21 | } from "antd"; 22 | import React, { useContext, useEffect, useRef, useState } from "react"; 23 | import { v4 as uuid } from "uuid"; 24 | import { GPT_MODELS, KNOWLEDGE_Store } from "../../../../common/data"; 25 | 26 | import { t } from "../../i18n"; 27 | 28 | type Values = KNOWLEDGE_Store; 29 | 30 | interface CollectionCreateFormProps { 31 | initialValues: Values; 32 | onFormInstanceReady: (instance: FormInstance) => void; 33 | } 34 | 35 | const ModalForm: React.FC = ({ 36 | initialValues, 37 | onFormInstanceReady, 38 | }) => { 39 | const [form] = Form.useForm(); 40 | useEffect(() => { 41 | onFormInstanceReady(form); 42 | }, []); 43 | 44 | const [num, setNum] = useState(0); 45 | const refresh = () => { 46 | setNum((x) => x + 1); 47 | }; 48 | 49 | return ( 50 |
51 | 52 | 53 | 54 | 55 | name="name" 56 | label={t`name`} 57 | rules={[{ required: true, message: t`Please enter` }]} 58 | > 59 | 60 | 61 | 62 | 63 | name="model" 64 | label={t`model`} 65 | rules={[{ required: true, message: t`Please enter` }]} 66 | > 67 |