├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── README.md ├── apps ├── cli │ ├── README.md │ ├── migpt.defaults.json │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── server │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── babel.config.js ├── docker └── Dockerfile ├── jest.config.js ├── nx.json ├── package-lock.json ├── package.json ├── packages ├── controller │ ├── README.md │ ├── package.json │ ├── src │ │ ├── child_process.ts │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── gui │ ├── .browserslistrc │ ├── README.md │ ├── babel.config.js │ ├── build │ │ └── buildWebpackConfig.js │ ├── index.js │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── App.tsx │ │ ├── apis.ts │ │ ├── app.css │ │ ├── css.ts │ │ ├── index.html │ │ └── index.tsx │ ├── tailwind.config.js │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── webpack.config.js ├── options-with-preview │ ├── README.md │ ├── index.css │ ├── package.json │ ├── src │ │ ├── OptionsWithPreview.tsx │ │ └── index.tsx │ ├── tsconfig.build.json │ └── tsconfig.json ├── options │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Ai.tsx │ │ ├── Characters.tsx │ │ ├── Dev.tsx │ │ ├── Options.tsx │ │ ├── Speaker.tsx │ │ ├── Tts.tsx │ │ ├── _utils.test.ts │ │ ├── _utils.ts │ │ ├── components │ │ │ ├── ChooseAI.tsx │ │ │ ├── ChooseSpeaker.tsx │ │ │ ├── LotCommand.tsx │ │ │ ├── MultiInput.tsx │ │ │ ├── NumberText.tsx │ │ │ └── TTSVolcano.tsx │ │ ├── defaults.ts │ │ ├── index.tsx │ │ ├── type.ts │ │ └── utils.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── server │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── tsconfig.base.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | coverage 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | 15 | # misc 16 | /.sass-cache 17 | npm-debug.log 18 | .env 19 | 20 | # System Files 21 | .DS_Store 22 | Thumbs.db 23 | 24 | # 自定义 25 | /.nx 26 | /apps/cli/testrobot 27 | /apps/cli/migptgui.json 28 | /packages/gui/webpack-report.html 29 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | electron_mirror=https://npmmirror.com/mirrors/electron/ 2 | puppeteer_download_host=https://npmmirror.com/mirrors 3 | fund=false 4 | audit=false 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.17.0 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "plugins": ["prettier-plugin-tailwindcss"] 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 给自己的另一个项目打个广告:划词翻译,一站式划词 / 截图 / 网页全文 / 音视频 AI 翻译扩展,[点击查看简介](https://hcfy.ai/docs/guides/summary) 2 | 3 | ------------------- 4 | 5 | # MiGPT GUI 6 | 7 | 为 [MiGPT](https://github.com/idootop/mi-gpt/) 提供图形化操作界面。 8 | 9 | 特点: 10 | 11 | - 使用图形化界面的方式编辑配置并控制(比如启动 / 停止 / 重置)MiGPT 12 | - 内置 [MiGPT TTS](https://github.com/idootop/mi-gpt-tts),所以无需你自己额外部署 13 | - 提供在公网运行所需的安全功能: 14 | - 提供登录认证功能,你可以设置账号密码,确保只有你自己能够控制你的 MiGPT 15 | - 内置的 MiGPT TTS 始终启用了[秘密路径](https://github.com/idootop/mi-gpt-tts/blob/main/docs/mi-gpt.md#2-%E9%85%8D%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F),且会自动下发给 MiGPT,对你是无感知的,你也不需要知道这个概念 16 | - 可以修改运行的端口号,避免被精准扫描 17 | 18 | 具体使用方式见 [https://migptgui.com](https://migptgui.com/)。 19 | -------------------------------------------------------------------------------- /apps/cli/README.md: -------------------------------------------------------------------------------- 1 | 说明见:https://migptgui.com/docs/intro/cli 2 | -------------------------------------------------------------------------------- /apps/cli/migpt.defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "bot": { 4 | "name": "傻妞", 5 | "profile": "性别:女\n性格:乖巧可爱\n爱好:喜欢搞怪,爱吃醋。" 6 | }, 7 | "master": { 8 | "name": "陆小千", 9 | "profile": "性别:男\n性格:善良正直\n其他:总是舍己为人,是傻妞的主人。" 10 | }, 11 | "speaker": { 12 | "switchSpeakerKeywords": ["把声音换成"], 13 | "callAIKeywords": ["请", "你", "傻妞"], 14 | "wakeUpKeywords": ["打开", "进入", "召唤"], 15 | "exitKeywords": ["关闭", "退出", "再见"], 16 | "onEnterAI": ["你好,我是傻妞,很高兴认识你"], 17 | "onExitAI": ["傻妞已退出"], 18 | "onAIAsking": ["让我先想想", "请稍等"], 19 | "onAIReplied": ["我说完了", "还有其他问题吗"], 20 | "onAIError": ["啊哦,出错了,请稍后再试吧!"] 21 | } 22 | }, 23 | "env": { 24 | "OPENAI_API_KEY": "", 25 | "OPENAI_MODEL": "", 26 | "OPENAI_BASE_URL": "" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "migpt-cli", 3 | "version": "5.0.0", 4 | "type": "module", 5 | "bin": { 6 | "migpt": "dist/index.js" 7 | }, 8 | "files": [ 9 | "dist", 10 | "migpt.defaults.json" 11 | ], 12 | "scripts": { 13 | "clean": "rm -rf dist", 14 | "build": "tsc -p tsconfig.build.json", 15 | "dev": "tsc", 16 | "dev:watch": "tsc -w", 17 | "start": "node dist/index.js run testrobot" 18 | }, 19 | "dependencies": { 20 | "@migptgui/controller": "^1.0.0", 21 | "@migptgui/server": "^1.0.0", 22 | "@migptgui/gui": "^1.0.0", 23 | "commander": "^12.1.0", 24 | "fs-extra": "^11.2.0" 25 | }, 26 | "devDependencies": { 27 | "@hcfy/js-to-mjs": "^1.1.0", 28 | "@types/fs-extra": "^11.0.4", 29 | "@types/node": "^22.7.3", 30 | "typescript": "^5.6.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/cli/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as path from 'node:path' 4 | import fse from 'fs-extra' 5 | import { program } from 'commander' 6 | import { run } from '@migptgui/controller' 7 | 8 | program 9 | .command('create') 10 | .description('创建一个机器人。') 11 | .argument('', '用于保存机器人数据的文件夹的名称。') 12 | .action((name) => { 13 | const defaults = path.join(__dirname, '../migpt.defaults.json') 14 | const robotDir = path.resolve(name) 15 | const newConfig = path.join(robotDir, './migptgui.json') 16 | fse.copySync(defaults, newConfig) 17 | console.log('创建机器人成功,机器人位置:') 18 | console.log(robotDir) 19 | console.log('机器人配置文件的位置:') 20 | console.log(newConfig) 21 | console.log( 22 | '注意:此配置文件仅仅是一个范本,你需要自行编辑其中的配置项之后才能成功运行 MiGPT!', 23 | ) 24 | }) 25 | 26 | program 27 | .command('run') 28 | .description('启动机器人。') 29 | .argument('', '保存有机器人数据的文件夹的名字。') 30 | .action((name) => { 31 | const json = fse.readJSONSync(path.join(name, './migptgui.json')) 32 | run(json, name) 33 | }) 34 | 35 | program.parse() 36 | -------------------------------------------------------------------------------- /apps/cli/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "src/**/*.test.tsx", 5 | "src/**/*.test.ts", 6 | "src/**/*.test.before.ts" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /apps/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/server/README.md: -------------------------------------------------------------------------------- 1 | 说明见:https://migptgui.com/docs/intro/gui 2 | -------------------------------------------------------------------------------- /apps/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "migpt-server", 3 | "version": "1.12.0", 4 | "type": "module", 5 | "scripts": { 6 | "clean": "rm -rf dist", 7 | "build": "tsc -p tsconfig.build.json", 8 | "dev": "tsc", 9 | "dev:watch": "tsc -w", 10 | "start": "node dist/index.js --open --user a --pwd b" 11 | }, 12 | "bin": { 13 | "migpt-server": "dist/index.js" 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "dependencies": { 19 | "@migptgui/gui": "^1.11.0", 20 | "@migptgui/server": "^1.11.0", 21 | "commander": "^12.1.0" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^22.7.3", 25 | "typescript": "^5.6.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/server/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { runServer } from '@migptgui/server' 4 | import path from 'node:path' 5 | import { program } from 'commander' 6 | import { fileURLToPath } from 'node:url' 7 | 8 | program 9 | .option('-p, --port ', '后台服务的端口。') 10 | .option('--user ', '用户名。') 11 | .option('--pwd ', '密码。') 12 | .option('--open', '是否自动打开配置界面。') 13 | 14 | program.parse() 15 | 16 | const options = program.opts() 17 | 18 | runServer({ 19 | port: options.port, 20 | open: options.open, 21 | users: 22 | options.user && options.pwd ? { [options.user]: options.pwd } : undefined, 23 | staticPath: fileURLToPath( 24 | path.dirname(import.meta.resolve('@migptgui/gui')) + '/dist/', 25 | ), 26 | }) 27 | -------------------------------------------------------------------------------- /apps/server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "src/**/*.test.tsx", 5 | "src/**/*.test.ts", 6 | "src/**/*.test.before.ts" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /apps/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "lib": ["ESNext"] 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 这个配置是给 jest 用的,其它项目应该自己编写 babel config,例如 packages/gui/babel.config.js 3 | */ 4 | 5 | module.exports = { 6 | presets: [ 7 | ['@babel/preset-env', { targets: { node: 'current' } }], 8 | [ 9 | '@babel/preset-react', 10 | { 11 | runtime: 'automatic', 12 | }, 13 | ], 14 | '@babel/preset-typescript', 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20.15.0-alpine3.20 AS env-amd64 2 | FROM node:20.15.0-alpine3.20 AS env-arm64 3 | 4 | ###### 尝试构建 armv7l 时失败,但还是把过程放在这里 ###### 5 | # arm32v7/node:20.15.0-alpine3.20 获取不到 metadata 报错 6 | # arm32v7/node:20.15.0 虽然在 docker hub 的描述里是有这个 tag 的,但是实际 build 镜像时会报错 docker.io/arm32v7/node:20.15.0: not found 7 | # arm32v7/node:20.14.0-alpine3.20 会卡在 npm install 阶段,没有任何日志输出,但也不报错。测试过,带有 alpine 的镜像都有这个问题。 8 | # arm32v7/node:20.14.0 只有这个才是正常 9 | #FROM arm32v7/node:20.14.0 10 | 11 | # prisma 官方不提供 armv7 架构的二进制文件,所以这里需要阻止它在找不到二进制文件时报错 12 | #ENV PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1 13 | #ENV PRISMA_GENERATE_SKIP_AUTOINSTALL=1 14 | #ENV PRISMA_SKIP_POSTINSTALL_GENERATE=1 15 | #ENV PRISMA_GENERATE_NO_ENGINE=1 16 | 17 | # 设置 armv7l 架构的 prisma 二进制文件路径 18 | #ENV PRISMA_QUERY_ENGINE_BINARY=/app/armv7l/query-engine 19 | #ENV PRISMA_QUERY_ENGINE_LIBRARY=/app/armv7l/libquery_engine.so.node 20 | #ENV PRISMA_SCHEMA_ENGINE_BINARY=/app/armv7l/schema-engine 21 | 22 | # 把自己编译的 armv7l 架构的 prisma 二进制文件拷贝到 /app 目录下 23 | # 注意:其中的 query-engine 和 schema-engine 必须提前赋予可执行权限,即 `chmod +x query-engine` 24 | #WORKDIR /app 25 | #COPY . . 26 | ###### 尝试构建 armv7l 的过程 ###### 27 | 28 | FROM env-$TARGETARCH 29 | RUN npm install -g migpt-server@1.12.0 30 | EXPOSE 36592 31 | VOLUME /root/.migptgui 32 | ENTRYPOINT ["migpt-server"] 33 | 34 | ###### 运行阶段结束 ###### -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | const config = { 3 | rootDir: './', 4 | // 覆盖率相关设置 5 | collectCoverage: true, 6 | coverageDirectory: 'coverage', 7 | // 设为 false 才能正常显示 console.log,见 https://stackoverflow.com/a/53684761 8 | verbose: false, 9 | // 完全重置所有 mock 函数,详细说明见 https://github.com/lmk123/blog/issues/114 10 | clearMocks: true, 11 | transformIgnorePatterns: [ 12 | // 为了确保 jest 将这些 esm 模块传给 transformer,需要将这些模块“反”排除 13 | 'node_modules/(?!(node-fetch|lodash-es|date-fns|data-uri-to-buffer|fetch-blob|formdata-polyfill)/)', 14 | ], 15 | 16 | // 设置 monorepo 17 | projects: [ 18 | { 19 | displayName: 'packages/options', 20 | testMatch: ['/packages/options/src/**/*.test.{ts,tsx}'], 21 | }, 22 | ], 23 | } 24 | 25 | if (process.env.CI) { 26 | config.cacheDirectory = '.jest-cache' 27 | } 28 | 29 | // 虽然 jest 的文档上说 “Jest will copy the root-level configuration options to each individual child configuration during the test run”, 30 | // 但实际使用中发现,下方的配置项需要明确添加后才能生效 31 | const keys = ['clearMocks', 'cacheDirectory', 'transformIgnorePatterns'] 32 | config.projects.forEach((p) => { 33 | keys.forEach((key) => { 34 | if (p[key] == null && config[key] != null) { 35 | p[key] = config[key] 36 | } 37 | }) 38 | }) 39 | 40 | module.exports = config 41 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "nx/presets/npm.json", 3 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 4 | "affected": { 5 | "defaultBase": "main" 6 | }, 7 | "tasksRunnerOptions": { 8 | "default": { 9 | "runner": "nx/tasks-runners/default", 10 | "options": { 11 | "cacheableOperations": ["build", "lint", "test", "test:ci", "e2e"] 12 | } 13 | } 14 | }, 15 | "targetDefaults": { 16 | "dev": { 17 | "dependsOn": ["^build"], 18 | "outputs": ["{projectRoot}/dist"] 19 | }, 20 | "build": { 21 | "dependsOn": ["^build"], 22 | "outputs": ["{projectRoot}/dist"] 23 | }, 24 | "test": { 25 | "dependsOn": ["^build"], 26 | "outputs": ["{projectRoot}/coverage"] 27 | }, 28 | "test:ci": { 29 | "dependsOn": ["^build"], 30 | "outputs": ["{projectRoot}/coverage"] 31 | } 32 | }, 33 | "workspaceLayout": { 34 | "appsDir": "apps", 35 | "libsDir": "packages" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mgc/root", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "test": "jest && nx run-many --target=test --exclude @mgc/root", 6 | "clean-all": "npx nx run-many --target=clean", 7 | "build-all": "npx nx run-many --target=build", 8 | "u": "npx -y npm-check-updates -ws --root -u -x eslint,open", 9 | "pub-options": "npx nx run @migptgui/options:build --verbose && npm publish --access public -w packages/options", 10 | "pub-options-preview": "npx nx run @migptgui/options-with-preview:build --verbose && npm publish --access public -w packages/options-with-preview", 11 | "pub-gui": "npx nx run @migptgui/gui:build --verbose && npm publish --access public -w packages/gui", 12 | "pub-controller": "npx nx run @migptgui/controller:build --verbose && npm publish --access public -w packages/controller", 13 | "pub-server-api": "npx nx run @migptgui/server:build --verbose && npm publish --access public -w packages/server", 14 | "pub-server": "npx nx run migpt-server:build --verbose && npm publish --access public -w apps/server", 15 | "build:docker-server": "docker build -t lmk123/migpt-server --platform=linux/arm64,linux/amd64 ./docker", 16 | "build:docker-server-version": "docker tag lmk123/migpt-server lmk123/migpt-server:$(node -p \"require('./apps/server/package.json').version\")", 17 | "pub-cli": "npx nx run migpt-cli:build --verbose && npm publish --access public -w apps/cli" 18 | }, 19 | "private": true, 20 | "devDependencies": { 21 | "@babel/core": "^7.25.2", 22 | "@babel/preset-env": "^7.25.4", 23 | "@babel/preset-typescript": "^7.24.7", 24 | "@nrwl/cli": "^15.9.3", 25 | "@typescript-eslint/eslint-plugin": "^8.7.0", 26 | "@typescript-eslint/parser": "^8.7.0", 27 | "eslint": "^8.57.0", 28 | "jest": "^29.7.0", 29 | "nx": "^19.8.2", 30 | "prettier": "^3.3.3", 31 | "prettier-plugin-tailwindcss": "^0.6.8" 32 | }, 33 | "workspaces": [ 34 | "packages/*", 35 | "apps/*" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /packages/controller/README.md: -------------------------------------------------------------------------------- 1 | 说明见 https://migptgui.com/docs/intro/controller 2 | -------------------------------------------------------------------------------- /packages/controller/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@migptgui/controller", 3 | "version": "1.12.0", 4 | "type": "module", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "clean": "rm -rf dist", 9 | "build": "tsc -p tsconfig.build.json -d", 10 | "dev": "tsc -d", 11 | "dev:watch": "tsc -d -w" 12 | }, 13 | "files": [ 14 | "dist" 15 | ], 16 | "dependencies": { 17 | "mi-gpt": "^4.2.0" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^22.7.3", 21 | "typescript": "^5.6.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/controller/src/child_process.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 用于运行 MiGPT 的子进程。 3 | * 使用子进程是因为一旦 import 'mi-gpt' 模块,它就会从环境变量中获取一些配置并且之后都不再改变, 4 | * 所以为了在 MiGPT 的代码执行前就配置好环境变量,需要在子进程中运行 MiGPT。 5 | */ 6 | 7 | import { MiGPT, type MiGPTConfig } from 'mi-gpt' 8 | 9 | let migpt: MiGPT | undefined 10 | 11 | process.on('message', (msg: any) => { 12 | if (msg.type === 'destroy') { 13 | if (migpt) { 14 | migpt.stop().then(() => { 15 | process.exit(0) 16 | }) 17 | } else { 18 | process.exit(0) 19 | } 20 | } else if (msg.type === 'run') { 21 | const config = msg.config as MiGPTConfig 22 | // console.log('配置:\n', config) 23 | const migpt = MiGPT.create(config) 24 | migpt.start() 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /packages/controller/src/index.ts: -------------------------------------------------------------------------------- 1 | import childProcess, { type ChildProcess } from 'node:child_process' 2 | import path from 'node:path' 3 | import { type MiGPTConfig } from 'mi-gpt' 4 | import { fileURLToPath } from 'node:url' 5 | 6 | export interface RunConfig { 7 | /** 8 | * 运行 MiGPT 所需的环境变量 9 | * @see https://github.com/idootop/mi-gpt/blob/main/docs/settings.md#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F 10 | */ 11 | env: { 12 | OPENAI_API_KEY: string 13 | OPENAI_MODEL: string 14 | OPENAI_BASE_URL?: string 15 | AZURE_OPENAI_API_KEY?: string 16 | AUDIO_SILENT?: string 17 | AUDIO_BEEP?: string 18 | AUDIO_ACTIVE?: string 19 | AUDIO_ERROR?: string 20 | TTS_BASE_URL?: string 21 | } 22 | /** 23 | * MiGPT 的配置 24 | * @see https://github.com/idootop/mi-gpt/blob/main/docs/settings.md#migptjs 25 | */ 26 | config: MiGPTConfig 27 | } 28 | 29 | let child: ChildProcess | undefined 30 | 31 | function killChild() { 32 | return new Promise((resolve) => { 33 | if (child) { 34 | child.once('exit', () => { 35 | resolve() 36 | }) 37 | child.send({ type: 'destroy' }) 38 | child = undefined 39 | } else { 40 | resolve() 41 | } 42 | }) 43 | } 44 | 45 | const __filename = fileURLToPath(import.meta.url) 46 | const __dirname = path.dirname(__filename) 47 | 48 | const childFilePath = path.join(__dirname, './child_process.js') 49 | 50 | export async function run(config: RunConfig, cwd?: string) { 51 | await killChild() 52 | child = childProcess.fork(childFilePath, { 53 | env: config.env, 54 | cwd, 55 | }) 56 | child.send({ type: 'run', config: config.config }) 57 | } 58 | 59 | export async function stop() { 60 | await killChild() 61 | } 62 | 63 | export function getStatus() { 64 | return { 65 | running: !!child, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/controller/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "src/**/*.test.tsx", 5 | "src/**/*.test.ts", 6 | "src/**/*.test.before.ts" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/controller/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "lib": ["ESNext"] 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/gui/.browserslistrc: -------------------------------------------------------------------------------- 1 | Chrome >= 66, Edge >= 79, Firefox >= 57 2 | -------------------------------------------------------------------------------- /packages/gui/README.md: -------------------------------------------------------------------------------- 1 | 说明见 https://migptgui.com/docs/intro/gui-server 2 | -------------------------------------------------------------------------------- /packages/gui/babel.config.js: -------------------------------------------------------------------------------- 1 | // console.log('process.env.NODE_ENV', process.env.NODE_ENV) 2 | 3 | // 对 node_modules 下的文件使用 babel-loader 参考了以下链接: 4 | // @see https://stackoverflow.com/a/52415747 5 | 6 | export default { 7 | sourceType: 'unambiguous', 8 | ignore: [/\/core-js/], 9 | presets: [ 10 | [ 11 | '@babel/preset-env', 12 | { 13 | modules: false, 14 | bugfixes: true, 15 | useBuiltIns: 'usage', 16 | corejs: '3', 17 | // 如果没有显示 debug 信息,那么需要将 webpack.config.js 中 babel-loader 的 cacheDirectory 改成 false 18 | debug: true, 19 | }, 20 | ], 21 | [ 22 | '@babel/preset-react', 23 | { 24 | runtime: 'automatic', 25 | }, 26 | ], 27 | '@babel/preset-typescript', 28 | ], 29 | } 30 | -------------------------------------------------------------------------------- /packages/gui/build/buildWebpackConfig.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import webpack from 'webpack' 3 | import HtmlWebpackPlugin from 'html-webpack-plugin' 4 | // import CopyPlugin from 'copy-webpack-plugin' 5 | import MiniCssExtractPlugin from 'mini-css-extract-plugin' 6 | import TerserJSPlugin from 'terser-webpack-plugin' 7 | import OptimizeCSSAssetsPlugin from 'css-minimizer-webpack-plugin' 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' 9 | 10 | import { fileURLToPath } from 'node:url' 11 | 12 | const __filename = fileURLToPath(import.meta.url) 13 | const __dirname = path.dirname(__filename) 14 | 15 | /** 16 | * 根据参数产出不同的 webpack 配置 17 | * @param mode {"production"|"development"} 18 | */ 19 | export default function buildWebpackConfig(mode) { 20 | const IS_PROD = mode === 'production' 21 | 22 | const outputPath = path.join(__dirname, '../dist/') 23 | 24 | const base = { 25 | mode, 26 | output: { 27 | publicPath: '', 28 | path: outputPath, 29 | clean: true, 30 | }, 31 | entry: './src/index.tsx', 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.(woff2|ico|png|eot|svg|ttf|woff)$/i, 36 | loader: 'file-loader', 37 | options: { 38 | name: '[name].[ext]', 39 | }, 40 | }, 41 | { 42 | test: /\.s?css$/i, 43 | oneOf: [ 44 | { 45 | use: [ 46 | // 官方推荐在 dev 模式时使用 style-loader,在 build 时才用 extract, 47 | // 但是内容脚本的样式不能注入到宿主页面的 dom 里,所以即使在 dev 模式下也抽离出来 48 | MiniCssExtractPlugin.loader, 49 | { 50 | loader: 'css-loader', 51 | options: { 52 | importLoaders: 1, 53 | esModule: false, 54 | }, 55 | }, 56 | { 57 | loader: 'postcss-loader', 58 | }, 59 | 'sass-loader', 60 | ], 61 | }, 62 | ], 63 | }, 64 | { 65 | test: /\.(tsx?|jsx?|mjs|cjs|js)$/, 66 | exclude: { 67 | and: [/node_modules/], 68 | }, 69 | use: { 70 | loader: 'babel-loader', 71 | options: { 72 | cacheDirectory: true, 73 | }, 74 | }, 75 | }, 76 | ], 77 | }, 78 | plugins: [ 79 | // new webpack.ProvidePlugin({ 80 | // process: 'process/browser', 81 | // }), 82 | new MiniCssExtractPlugin(), 83 | new HtmlWebpackPlugin({ 84 | filename: 'index.html', 85 | template: './src/index.html', 86 | }), 87 | // new CopyPlugin({ 88 | // patterns: [{ from: 'public' }], 89 | // }), 90 | ], 91 | resolve: { 92 | modules: ['node_modules'], 93 | extensions: ['.tsx', '.ts', '.js'], 94 | }, 95 | devServer: { 96 | static: { 97 | directory: path.join(__dirname, '../public'), 98 | }, 99 | hot: true, 100 | open: true, 101 | }, 102 | } 103 | 104 | const definePluginObj = {} 105 | if (mode === 'development') { 106 | base.devtool = 'inline-source-map' 107 | } else if (IS_PROD) { 108 | base.devtool = false 109 | base.optimization = { 110 | minimizer: [new TerserJSPlugin(), new OptimizeCSSAssetsPlugin()], 111 | } 112 | } 113 | base.plugins.push(new webpack.DefinePlugin(definePluginObj)) 114 | 115 | if (IS_PROD) { 116 | base.plugins.push( 117 | new BundleAnalyzerPlugin({ 118 | analyzerMode: 'static', 119 | // 不要将 report.html 放在 dist 里,因为 dist 里的文件会被复制过去作为 cli 包的一部分发布 120 | reportFilename: `../webpack-report.html`, 121 | openAnalyzer: false, 122 | }), 123 | ) 124 | } 125 | 126 | return base 127 | } 128 | -------------------------------------------------------------------------------- /packages/gui/index.js: -------------------------------------------------------------------------------- 1 | throw new Error( 2 | 'Do not import the GUI package directly. Use the "dist" instead.', 3 | ) 4 | -------------------------------------------------------------------------------- /packages/gui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@migptgui/gui", 3 | "version": "1.12.0", 4 | "type": "module", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rm -rf dist", 8 | "checktype": "tsc --noEmit", 9 | "dev": "webpack --env mode=development", 10 | "dev:watch": "webpack --watch --env mode=development", 11 | "dev:serve": "webpack serve --env mode=development", 12 | "build": "npm run checktype && webpack --env mode=production" 13 | }, 14 | "files": [ 15 | "dist", 16 | "index.js" 17 | ], 18 | "devDependencies": { 19 | "@blueprintjs/core": "^5.13.0", 20 | "@blueprintjs/icons": "^5.13.0", 21 | "@migptgui/options": "*", 22 | "core-js": "^3.38.1", 23 | "normalize.css": "^8.0.1", 24 | "react": "^18.3.1", 25 | "react-dom": "^18.3.1", 26 | "@babel/core": "^7.25.2", 27 | "@babel/preset-env": "^7.25.4", 28 | "@babel/preset-react": "^7.24.7", 29 | "@babel/preset-typescript": "^7.24.7", 30 | "@types/react": "^18.3.9", 31 | "@types/react-dom": "^18.3.0", 32 | "autoprefixer": "^10.4.20", 33 | "babel-loader": "^9.2.1", 34 | "copy-webpack-plugin": "^12.0.2", 35 | "css-loader": "^7.1.2", 36 | "css-minimizer-webpack-plugin": "^7.0.0", 37 | "file-loader": "^6.2.0", 38 | "html-webpack-plugin": "^5.6.0", 39 | "mini-css-extract-plugin": "^2.9.1", 40 | "postcss": "^8.4.47", 41 | "postcss-loader": "^8.1.1", 42 | "sass": "^1.79.3", 43 | "sass-loader": "^16.0.2", 44 | "tailwindcss": "^3.4.13", 45 | "terser-webpack-plugin": "^5.3.10", 46 | "typescript": "^5.6.2", 47 | "webpack": "^5.95.0", 48 | "webpack-bundle-analyzer": "^4.10.2", 49 | "webpack-cli": "^5.1.4", 50 | "webpack-dev-server": "^5.1.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/gui/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/gui/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Options, 3 | defaults, 4 | exportJSON, 5 | importJSON, 6 | strip, 7 | normalize, 8 | type GuiConfig, 9 | } from '@migptgui/options' 10 | import { 11 | Alignment, 12 | AnchorButton, 13 | Button, 14 | ButtonGroup, 15 | FocusStyleManager, 16 | Navbar, 17 | } from '@blueprintjs/core' 18 | import { useEffect, useState } from 'react' 19 | import * as apis from './apis.ts' 20 | import _debounce from 'lodash/debounce.js' 21 | import _pick from 'lodash/pick.js' 22 | 23 | const saveConfigDebounced = _debounce(apis.saveConfig, 1000) 24 | 25 | FocusStyleManager.onlyShowFocusOnTabs() 26 | 27 | export function App() { 28 | const [config, setConfig] = useState(defaults) 29 | 30 | const [formEle, setFormEle] = useState() 31 | 32 | // 读取配置 33 | useEffect(() => { 34 | apis.getConfig().then((config) => { 35 | if (config) { 36 | setConfig(config) 37 | } 38 | }) 39 | }, []) 40 | 41 | // 保存配置 42 | useEffect(() => { 43 | if (config !== defaults) { 44 | saveConfigDebounced(config) 45 | } 46 | }, [config]) 47 | 48 | return ( 49 | <> 50 | 51 | 52 | MiGPT 控制面板 53 | 54 |
55 | 56 | 65 | 80 | 102 | 103 | 104 | 114 | 122 | 128 | 配置项详解 129 | 130 | 131 |
132 |
133 |
134 |
135 |
136 | {/*todo: 未来开发多机器人功能时作为机器人列表*/} 137 |
138 |
139 |
{ 142 | event.preventDefault() 143 | apis.run(config).then( 144 | () => { 145 | alert('启动成功!你可以切换回终端内查看运行日志。') 146 | }, 147 | (err) => { 148 | alert('启动失败!' + err) 149 | }, 150 | ) 151 | }} 152 | > 153 | { 156 | // console.log('config and stripped config', config, strip(config)) 157 | setConfig(config) 158 | }} 159 | onPublicURLTest={(url) => { 160 | apis.testPublicURL(url).then( 161 | (res) => { 162 | if (res.success) { 163 | alert('测试成功。') 164 | } else { 165 | alert('测试失败。') 166 | } 167 | }, 168 | (err) => { 169 | alert('测试失败。' + err) 170 | }, 171 | ) 172 | }} 173 | onTtsTest={() => { 174 | if (!config.tts || !config.gui?.ttsProvider) { 175 | alert('请先完成 TTS 引擎配置。') 176 | return 177 | } 178 | 179 | const testTtsConfig = _pick(config.tts, [ 180 | config.gui.ttsProvider, 181 | 'defaultSpeaker', 182 | ]) 183 | 184 | apis.testTts(testTtsConfig).then( 185 | () => { 186 | // alert('测试成功。') 187 | }, 188 | (err) => { 189 | alert('测试失败。' + err) 190 | }, 191 | ) 192 | }} 193 | /> 194 | 195 |
196 |
197 | 198 | ) 199 | } 200 | -------------------------------------------------------------------------------- /packages/gui/src/apis.ts: -------------------------------------------------------------------------------- 1 | import { GuiConfig } from '@migptgui/options' 2 | import type { TTSConfig } from 'mi-gpt-tts' 3 | 4 | export async function getStatus() { 5 | const response = await fetch('/api/status') 6 | if (response.ok) { 7 | return response.json() 8 | } 9 | throw new Error('Failed to get status:HTTP ' + response.statusText) 10 | } 11 | 12 | export async function testPublicURL( 13 | url: string, 14 | ): Promise<{ success: boolean }> { 15 | const response = await fetch('/api/test', { 16 | method: 'POST', 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | body: JSON.stringify({ url }), 21 | }) 22 | if (response.ok) { 23 | return response.json() 24 | } 25 | throw new Error('Failed to test public URL:HTTP ' + response.statusText) 26 | } 27 | 28 | export async function testTts(ttsConfig: TTSConfig) { 29 | const audioUrl = 30 | '/api/test/audio?ttsConfig=' + encodeURIComponent(JSON.stringify(ttsConfig)) 31 | const audio = new Audio(audioUrl) 32 | return new Promise((resolve, reject) => { 33 | audio.addEventListener('error', () => { 34 | reject() 35 | }) 36 | audio.addEventListener('canplay', () => { 37 | resolve() 38 | audio.play() 39 | }) 40 | }) 41 | } 42 | 43 | export async function run(config: unknown) { 44 | const response = await fetch('/api/default/start', { 45 | method: 'POST', 46 | headers: { 47 | 'Content-Type': 'application/json', 48 | }, 49 | body: JSON.stringify(config), 50 | }) 51 | if (response.ok) { 52 | return response.json() 53 | } 54 | throw new Error('Failed to start:HTTP ' + response.statusText) 55 | } 56 | 57 | export async function stop() { 58 | const response = await fetch('/api/default/stop', { 59 | method: 'POST', 60 | }) 61 | if (response.ok) { 62 | return response.json() 63 | } 64 | throw new Error('Failed to stop:HTTP ' + response.statusText) 65 | } 66 | 67 | export async function getConfig() { 68 | const response = await fetch('/api/default') 69 | if (response.ok) { 70 | const res = await response.json() 71 | return res.config as GuiConfig | undefined 72 | } 73 | throw new Error('Failed to get config:HTTP ' + response.statusText) 74 | } 75 | 76 | export async function saveConfig(config: GuiConfig) { 77 | const response = await fetch('/api/default', { 78 | method: 'PUT', 79 | headers: { 80 | 'Content-Type': 'application/json', 81 | }, 82 | body: JSON.stringify(config), 83 | }) 84 | if (response.ok) { 85 | return response.json() 86 | } 87 | throw new Error('Failed to save config:HTTP ' + response.statusText) 88 | } 89 | 90 | export async function reset() { 91 | const response = await fetch('/api/default', { 92 | method: 'DELETE', 93 | }) 94 | if (response.ok) { 95 | return response.json() 96 | } 97 | throw new Error('Failed to stop:HTTP ' + response.statusText) 98 | } 99 | -------------------------------------------------------------------------------- /packages/gui/src/app.css: -------------------------------------------------------------------------------- 1 | /*@tailwind base;*/ 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | #app { 10 | padding-top: 50px; 11 | } 12 | 13 | .bp5-label { 14 | width: 160px; 15 | } 16 | 17 | .bp5-input-group:not(.tw-min-w-0), 18 | textarea { 19 | min-width: 600px; 20 | } 21 | -------------------------------------------------------------------------------- /packages/gui/src/css.ts: -------------------------------------------------------------------------------- 1 | import 'normalize.css' 2 | import '@blueprintjs/core/lib/css/blueprint.css' 3 | import '@blueprintjs/icons/lib/css/blueprint-icons.css' 4 | import './app.css' 5 | -------------------------------------------------------------------------------- /packages/gui/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MiGPT 控制面板 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/gui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import './css' 3 | import { App } from './App.tsx' 4 | 5 | const container = document.getElementById('app') 6 | if (!container) { 7 | throw new Error('DOM 里没有 #app 元素。') 8 | } 9 | const root = createRoot(container) 10 | root.render() 11 | -------------------------------------------------------------------------------- /packages/gui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | export default { 5 | prefix: 'tw-', 6 | content: [ 7 | './src/**/*.{ts,tsx,html,js}', 8 | path.join( 9 | path.dirname(require.resolve('@migptgui/options')), 10 | '**/*.{ts,tsx,html,js}', 11 | ), 12 | ], 13 | important: true, 14 | theme: { 15 | extend: {}, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /packages/gui/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx" 5 | }, 6 | "exclude": [ 7 | "src/**/*.test.tsx", 8 | "src/**/*.test.ts", 9 | "src/**/*.test.before.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/gui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "lib": ["ESNext", "DOM"] 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/gui/webpack.config.js: -------------------------------------------------------------------------------- 1 | import buildWebpackConfig from './build/buildWebpackConfig.js' 2 | 3 | export default (env) => { 4 | return buildWebpackConfig(env.mode) 5 | } 6 | -------------------------------------------------------------------------------- /packages/options-with-preview/README.md: -------------------------------------------------------------------------------- 1 | 说明见 https://migptgui.com/docs/intro/web 2 | -------------------------------------------------------------------------------- /packages/options-with-preview/index.css: -------------------------------------------------------------------------------- 1 | .gui-web-form { 2 | margin-right: 520px; 3 | } 4 | 5 | .gui-web-preview { 6 | position: fixed; 7 | z-index: 1; 8 | top: 113px; 9 | right: 20px; 10 | width: 500px; 11 | bottom: 167px; 12 | 13 | display: flex; 14 | flex-direction: column; 15 | } 16 | 17 | .gui-web-preview-head { 18 | margin-bottom: 20px; 19 | 20 | flex-grow: 0; 21 | flex-shrink: 0; 22 | 23 | display: flex; 24 | justify-content: space-between; 25 | align-items: center; 26 | } 27 | 28 | .gui-web-preview-head .bp5-button { 29 | margin-left: 5px; 30 | } 31 | 32 | .gui-web-preview-head textarea { 33 | flex-grow: 1; 34 | overflow: auto; 35 | } 36 | 37 | .gui-web-form .bp5-label { 38 | width: 160px; 39 | } 40 | 41 | .gui-web-main .bp5-input-group:not(.tw-min-w-0), 42 | .gui-web-main textarea { 43 | min-width: 500px; 44 | } 45 | -------------------------------------------------------------------------------- /packages/options-with-preview/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@migptgui/options-with-preview", 3 | "version": "1.12.0", 4 | "type": "module", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "style": "index.css", 8 | "scripts": { 9 | "clean": "rm -rf dist", 10 | "build": "tsc -p tsconfig.build.json -d", 11 | "dev": "tsc -d", 12 | "dev:watch": "tsc -d -w" 13 | }, 14 | "files": [ 15 | "dist", 16 | "index.css" 17 | ], 18 | "dependencies": { 19 | "@blueprintjs/core": "^5.13.0", 20 | "@migptgui/options": "^1.11.0", 21 | "react": "^18.3.1" 22 | }, 23 | "devDependencies": { 24 | "@types/lodash": "^4.17.9", 25 | "mi-gpt": "^4.2.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/options-with-preview/src/OptionsWithPreview.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { 3 | Options, 4 | exportFile, 5 | defaults, 6 | type GuiConfig, 7 | strip, 8 | } from '@migptgui/options' 9 | import { Tabs, Tab, Button, TextArea } from '@blueprintjs/core' 10 | 11 | function toWholeConfig(config: GuiConfig) { 12 | return JSON.stringify(config, null, 2) 13 | } 14 | 15 | function envToTxt(config: GuiConfig) { 16 | return Object.entries( 17 | Object.assign( 18 | { 19 | OPENAI_API_KEY: '', 20 | OPENAI_MODEL: '', 21 | OPENAI_BASE_URL: '', 22 | }, 23 | config.env, 24 | ), 25 | ) 26 | .map(([key, value]) => `${key}=${value}`) 27 | .join('\n') 28 | } 29 | 30 | function jsonTojs(config: GuiConfig) { 31 | return `export default ${JSON.stringify(config.config, null, 2)}` 32 | } 33 | 34 | export function OptionsWithPreview() { 35 | const [config, setConfig] = useState(defaults) 36 | const [tabId, setTabId] = useState('whole') 37 | const [fileName, setFileName] = useState('migptgui.json') 38 | const [textAreaValue, setTextareaValue] = useState('') 39 | 40 | useEffect(() => { 41 | if (tabId === 'whole') { 42 | setFileName('migptgui.json') 43 | setTextareaValue(toWholeConfig(strip(config))) 44 | } else if (tabId == 'config') { 45 | setFileName('.migpt.js') 46 | setTextareaValue(jsonTojs(strip(config))) 47 | } else { 48 | setFileName('.env') 49 | setTextareaValue(envToTxt(strip(config))) 50 | } 51 | }, [tabId, config]) 52 | 53 | const [formEle, setFormEle] = useState() 54 | 55 | return ( 56 |
57 |
{ 61 | e.preventDefault() 62 | }} 63 | > 64 | setConfig(newConfig)} 67 | /> 68 | 69 |
70 |
71 | setTabId(newTabId as string)} 76 | > 77 | 78 | 79 | 80 | 81 |
82 | 93 | 109 |
110 |
111 | 112 |