├── .cursor └── rules │ ├── design-system.mdc │ └── shortcuts.mdc ├── .cursorrules ├── .env.example ├── .gitignore ├── README.md ├── README.zh-CN.md ├── app ├── api │ ├── generate │ │ ├── route.ts │ │ └── stop │ │ │ └── route.ts │ └── regions │ │ └── route.ts ├── components │ ├── AddRegionModal.tsx │ ├── LogsPanel.tsx │ ├── Navbar.tsx │ └── SettingsPanel.tsx ├── config │ └── providers.ts ├── globals.css ├── layout.tsx └── page.tsx ├── bun.lock ├── config └── config.ts ├── generators └── generate-groq.ts ├── index.ts ├── next-env.d.ts ├── next.config.mjs ├── package.json ├── postcss.config.cjs ├── prompts ├── base.ts └── qianfan.ts ├── providers ├── base │ └── service.ts ├── groq │ ├── client.ts │ └── service.ts ├── openai │ ├── client.ts │ └── service.ts └── qianfan │ ├── client.ts │ └── service.ts ├── tailwind.config.cjs ├── tsconfig.json ├── types ├── provider.ts ├── types.ts └── worker.ts ├── utils ├── logger.ts ├── prompt.ts ├── similarity.ts ├── storage.ts └── stream.ts └── workers ├── answer-worker.ts ├── question-worker.ts └── worker-pool.ts /.cursor/rules/design-system.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Design System Specification 3 | globs: 4 | alwaysApply: true 5 | --- 6 | # Design System Specification 7 | 8 | This project utilizes Tailwind CSS and HeroUI. 9 | 10 | ## Color System 11 | - **Primary Color**: Use `blue-600` as the main color 12 | - **Neutral Colors**: Use `slate` series 13 | - **Semantic Colors**: Use `red` / `green` / `yellow` series 14 | - **Opacity**: Use suffixes like `/90` or `/95` 15 | 16 | ## Spacing System 17 | Using Tailwind's default spacing system: 18 | - **Small Spacing**: `px-4 py-2.5` (for buttons etc.) 19 | - **Medium Spacing**: `px-6 py-3` (for card content etc.) 20 | - **Large Spacing**: `px-6 py-5` (for sections etc.) 21 | - **Gap**: `gap-2` / `gap-3` / `gap-4` etc. 22 | 23 | ## Shadow System 24 | - **Light Shadow**: `shadow-sm` 25 | - **Regular Shadow**: `shadow-md` 26 | - **Heavy Shadow**: `shadow-lg` 27 | - **Special Effects**: `shadow-[0_8px_16px_0_rgba(0,0,0,0.1)]` 28 | 29 | ## Border Radius 30 | - **Small Radius**: `rounded-lg` (8px) 31 | - **Medium Radius**: `rounded-xl` (12px) 32 | - **Large Radius**: `rounded-2xl` (16px) 33 | - **Full Round**: `rounded-full` 34 | 35 | ## Animation Effects 36 | - **Transition**: `transition-all duration-300` 37 | - **Hover Scale**: `hover:scale-105` 38 | - **Click Effect**: `active:scale-98` 39 | - **Fade In**: `animate-in slide-in-from-bottom-5` 40 | - **Loading**: `animate-pulse` -------------------------------------------------------------------------------- /.cursor/rules/shortcuts.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | - When I mentioned “Ready to release”, I meant: 7 | - Retrieve the codebase, updating the [README.md](mdc:README.md), and then rewriting the [README.zh-CN.md](mdc:README.zh-CN.md) based on the updated README. 8 | - When I mentioned “Hi”,I meant: 9 | - Deeply retrieve the codebase, prompting me to optimize the specific section. 10 | - If there is a bug, please mark it specifically. 11 | - Do not start directly; let me choose the task. 12 | -------------------------------------------------------------------------------- /.cursorrules: -------------------------------------------------------------------------------- 1 | This project use bun as runtime. 2 | This project don't use `src` folder. -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GROQ_API_KEY=your_groq_api_key 2 | QIANFAN_ACCESS_KEY=your_qianfan_access_key 3 | QIANFAN_SECRET_KEY=your_qianfan_secret_key 4 | OPENAI_API_KEY=your_openai_api_key 5 | OPENAI_BASE_URL=your_openai_base_url -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *_q_results.json 2 | *_qa_results.json 3 | 4 | 5 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 6 | 7 | # Logs 8 | 9 | logs 10 | _.log 11 | npm-debug.log_ 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | # Caches 18 | 19 | .cache 20 | 21 | # Diagnostic reports (https://nodejs.org/api/report.html) 22 | 23 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 24 | 25 | # Runtime data 26 | 27 | pids 28 | _.pid 29 | _.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | 34 | lib-cov 35 | 36 | # Coverage directory used by tools like istanbul 37 | 38 | coverage 39 | *.lcov 40 | 41 | # nyc test coverage 42 | 43 | .nyc_output 44 | 45 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 46 | 47 | .grunt 48 | 49 | # Bower dependency directory (https://bower.io/) 50 | 51 | bower_components 52 | 53 | # node-waf configuration 54 | 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | 59 | build/Release 60 | 61 | # Dependency directories 62 | 63 | node_modules/ 64 | jspm_packages/ 65 | 66 | # Snowpack dependency directory (https://snowpack.dev/) 67 | 68 | web_modules/ 69 | 70 | # TypeScript cache 71 | 72 | *.tsbuildinfo 73 | 74 | # Optional npm cache directory 75 | 76 | .npm 77 | 78 | # Optional eslint cache 79 | 80 | .eslintcache 81 | 82 | # Optional stylelint cache 83 | 84 | .stylelintcache 85 | 86 | # Microbundle cache 87 | 88 | .rpt2_cache/ 89 | .rts2_cache_cjs/ 90 | .rts2_cache_es/ 91 | .rts2_cache_umd/ 92 | 93 | # Optional REPL history 94 | 95 | .node_repl_history 96 | 97 | # Output of 'npm pack' 98 | 99 | *.tgz 100 | 101 | # Yarn Integrity file 102 | 103 | .yarn-integrity 104 | 105 | # dotenv environment variable files 106 | 107 | .env 108 | .env.development.local 109 | .env.test.local 110 | .env.production.local 111 | .env.local 112 | 113 | # parcel-bundler cache (https://parceljs.org/) 114 | 115 | .parcel-cache 116 | 117 | # Next.js build output 118 | 119 | .next 120 | out 121 | 122 | # Nuxt.js build / generate output 123 | 124 | .nuxt 125 | dist 126 | 127 | # Gatsby files 128 | 129 | # Comment in the public line in if your project uses Gatsby and not Next.js 130 | 131 | # https://nextjs.org/blog/next-9-1#public-directory-support 132 | 133 | # public 134 | 135 | # vuepress build output 136 | 137 | .vuepress/dist 138 | 139 | # vuepress v2.x temp and cache directory 140 | 141 | .temp 142 | 143 | # Docusaurus cache and generated files 144 | 145 | .docusaurus 146 | 147 | # Serverless directories 148 | 149 | .serverless/ 150 | 151 | # FuseBox cache 152 | 153 | .fusebox/ 154 | 155 | # DynamoDB Local files 156 | 157 | .dynamodb/ 158 | 159 | # TernJS port file 160 | 161 | .tern-port 162 | 163 | # Stores VSCode versions used for testing VSCode extensions 164 | 165 | .vscode-test 166 | 167 | # yarn v2 168 | 169 | .yarn/cache 170 | .yarn/unplugged 171 | .yarn/build-state.yml 172 | .yarn/install-state.gz 173 | .pnp.* 174 | 175 | # IntelliJ based IDEs 176 | .idea 177 | 178 | # Finder (MacOS) folder config 179 | .DS_Store 180 | 181 | # Environment variables 182 | .env 183 | .env.local 184 | .env.*.local 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QA Generator ![](https://img.shields.io/badge/A%20FRAD%20PRODUCT-WIP-yellow) 2 | 3 | [![Twitter Follow](https://img.shields.io/twitter/follow/FradSer?style=social)](https://twitter.com/FradSer) 4 | 5 | English | [简体中文](README.zh-CN.md) 6 | 7 | A sophisticated TypeScript application that leverages multiple AI providers to generate high-quality questions and answers for various regions in China. Built with Next.js and Tailwind CSS for a modern web interface. 8 | 9 | ## Key Features 10 | 11 | - **Multiple AI Providers**: Seamless integration with QianFan and Groq 12 | - **Region-based Generation**: Support for multiple regions with customizable names and descriptions 13 | - **Diverse Content**: Generates unique questions about local history, culture, cuisine, attractions, and specialties 14 | - **Quality Assurance**: 15 | - Automatic duplicate question detection 16 | - Multiple retry attempts for answer generation 17 | - Progress auto-save after each answer 18 | - **Flexible Configuration**: Customizable question count and answer retry attempts 19 | - **Multi-threaded Processing**: Parallel processing with worker threads for improved performance 20 | - **Intelligent Output**: Structured JSON output with questions, answers, and reasoning content 21 | - **Modern Web Interface**: Built with Next.js and Tailwind CSS for a beautiful, responsive UI 22 | 23 | ## Prerequisites 24 | 25 | Before you begin, ensure you have: 26 | - [Bun](https://bun.sh) runtime installed 27 | - QianFan API credentials (for QianFan provider) 28 | - Groq API key (for Groq provider) 29 | - Node.js 18+ (for Next.js development) 30 | 31 | ## Getting Started 32 | 33 | 1. Clone the repository: 34 | ```bash 35 | git clone https://github.com/FradSer/qa-generator.git 36 | cd qa-generator 37 | ``` 38 | 39 | 2. Install dependencies: 40 | ```bash 41 | bun install 42 | ``` 43 | 44 | 3. Set up environment variables: 45 | ```bash 46 | cp .env.example .env 47 | ``` 48 | 49 | 4. Configure your API keys in `.env`: 50 | ```bash 51 | # Required for QianFan provider (default) 52 | QIANFAN_ACCESS_KEY=your_qianfan_access_key 53 | QIANFAN_SECRET_KEY=your_qianfan_secret_key 54 | 55 | # Required for Groq provider 56 | GROQ_API_KEY=your_groq_api_key 57 | ``` 58 | 59 | ## Usage Guide 60 | 61 | ### Command Structure 62 | 63 | ```bash 64 | bun run start [options] 65 | ``` 66 | 67 | ### Parameters 68 | 69 | Required options: 70 | - `--mode `: Operation mode 71 | - `questions`: Generate questions only 72 | - `answers`: Generate answers only 73 | - `all`: Generate both questions and answers 74 | - `--region `: Region name in pinyin (e.g., "chibi" for 赤壁) 75 | 76 | Optional parameters: 77 | - `--count `: Number of questions to generate (default: 100) 78 | 79 | Worker-related parameters: 80 | - `--workers `: Number of worker threads (default: CPU cores - 1) 81 | - `--batch `: Batch size for processing (default: 50) 82 | - `--delay `: Delay between batches in milliseconds (default: 1000) 83 | - `--attempts `: Maximum retry attempts per task (default: 3) 84 | 85 | ### Web Interface 86 | 87 | To start the Next.js development server: 88 | 89 | ```bash 90 | bun run dev 91 | ``` 92 | 93 | Visit `http://localhost:3000` to access the web interface. 94 | 95 | ### Worker System 96 | 97 | The application leverages a multi-threaded worker system for parallel processing: 98 | 99 | - **Architecture**: 100 | - Tasks are evenly distributed among worker threads 101 | - Each worker processes its assigned batch independently 102 | - Workers are automatically cleaned up after task completion 103 | - Error isolation prevents cascading failures 104 | 105 | - **Performance Optimization**: 106 | - Adjust thread count based on your CPU (`--workers`) 107 | - Fine-tune batch size for optimal throughput (`--batch`) 108 | - Control API rate limiting with delays (`--delay`) 109 | - Set retry attempts for failed tasks (`--attempts`) 110 | 111 | Example with optimized worker configuration: 112 | ```bash 113 | bun run start --mode all --region chibi --workers 20 --batch 25 --delay 2000 114 | ``` 115 | 116 | ### Example Commands 117 | 118 | 1. Generate questions for a specific region: 119 | ```bash 120 | bun run start --mode questions --region chibi --count 50 121 | ``` 122 | 123 | 2. Generate answers for existing questions: 124 | ```bash 125 | bun run start --mode answers --region chibi 126 | ``` 127 | 128 | 3. Generate both questions and answers: 129 | ```bash 130 | bun run start --mode all --region chibi --count 100 131 | ``` 132 | 133 | 4. Use Groq as AI provider: 134 | ```bash 135 | AI_PROVIDER=groq bun run start --mode all --region chibi 136 | ``` 137 | 138 | ### Adding New Regions 139 | 140 | Edit `config/config.ts` to add new regions: 141 | 142 | ```typescript 143 | export const regions: Region[] = [ 144 | { 145 | name: "赤壁", 146 | pinyin: "chibi", 147 | description: "Chibi City in Xianning, Hubei Province, site of the historic Battle of Red Cliffs" 148 | }, 149 | // Add new regions here 150 | { 151 | name: "New Region", 152 | pinyin: "newregion", 153 | description: "Description of the new region" 154 | } 155 | ]; 156 | ``` 157 | 158 | ### Output Format 159 | 160 | Each region generates two JSON files: 161 | 162 | 1. Questions file: `_q_results.json` 163 | ```json 164 | [ 165 | { 166 | "question": "Question text", 167 | "is_answered": false 168 | } 169 | ] 170 | ``` 171 | 172 | 2. Q&A file: `_qa_results.json` 173 | ```json 174 | [ 175 | { 176 | "question": "Question text", 177 | "content": "Answer content", 178 | "reasoning_content": "Reasoning process and references" 179 | } 180 | ] 181 | ``` 182 | 183 | ## Project Structure 184 | 185 | ``` 186 | . 187 | ├── app/ # Next.js application files 188 | ├── config/ # Configuration files 189 | ├── generators/ # Question and answer generators 190 | ├── providers/ # AI provider implementations 191 | │ ├── groq/ # Groq provider 192 | │ └── qianfan/ # QianFan provider 193 | ├── prompts/ # AI prompt templates 194 | ├── types/ # TypeScript type definitions 195 | ├── utils/ # Utility functions 196 | ├── workers/ # Worker thread implementations 197 | └── index.ts # Main entry point 198 | ``` 199 | 200 | ## Error Handling 201 | 202 | The application implements robust error handling: 203 | - Automatic retry for failed API requests 204 | - Progress saving after each successful answer 205 | - Duplicate question detection and filtering 206 | - Detailed error logging with stack traces 207 | - Graceful failure recovery with state preservation 208 | 209 | ## Contributing 210 | 211 | Issues and pull requests are welcome! Feel free to contribute to improve the project. 212 | 213 | ## License 214 | 215 | This project is licensed under the MIT License - see the LICENSE file for details. 216 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # QA 生成器 ![](https://img.shields.io/badge/A%20FRAD%20PRODUCT-WIP-yellow) 2 | 3 | [![Twitter Follow](https://img.shields.io/twitter/follow/FradSer?style=social)](https://twitter.com/FradSer) 4 | 5 | [English](README.md) | 简体中文 6 | 7 | 一款基于多种 AI 模型的智能问答生成工具,专注于为中国各地区生成高质量的本地特色问答内容。采用 Next.js 和 Tailwind CSS 构建现代化的 Web 界面。 8 | 9 | ## 主要特性 10 | 11 | - **多模型支持**:目前已集成百度千帆和 Groq 两大 AI 模型 12 | - **地区特色**:支持为不同地区定制化生成问答内容,可自定义地区名称和描述 13 | - **内容丰富**:涵盖地方历史、文化、美食、景点和特产等多个维度 14 | - **质量保障**: 15 | - 智能去重,避免重复问题 16 | - 答案生成失败自动重试 17 | - 实时保存生成进度 18 | - **灵活配置**:可自定义问题数量和重试次数 19 | - **多线程处理**:利用多线程并行处理,提升生成效率 20 | - **智能输出**:结构化的 JSON 输出,包含问题、答案和推理过程 21 | - **现代界面**:使用 Next.js 和 Tailwind CSS 构建美观、响应式的用户界面 22 | 23 | ## 运行环境 24 | 25 | 使用前请确保: 26 | - 已安装 [Bun](https://bun.sh) 运行环境 27 | - 有百度千帆 API 密钥(使用千帆模型时需要) 28 | - 有 Groq API 密钥(使用 Groq 模型时需要) 29 | - Node.js 18+ 版本(用于 Next.js 开发) 30 | 31 | ## 快速上手 32 | 33 | 1. 克隆项目: 34 | ```bash 35 | git clone https://github.com/FradSer/qa-generator.git 36 | cd qa-generator 37 | ``` 38 | 39 | 2. 安装依赖: 40 | ```bash 41 | bun install 42 | ``` 43 | 44 | 3. 配置环境变量: 45 | ```bash 46 | cp .env.example .env 47 | ``` 48 | 49 | 4. 在 `.env` 中填写 API 密钥: 50 | ```bash 51 | # Required for QianFan provider (default) 52 | QIANFAN_ACCESS_KEY=your_qianfan_access_key 53 | QIANFAN_SECRET_KEY=your_qianfan_secret_key 54 | 55 | # Required for Groq provider 56 | GROQ_API_KEY=your_groq_api_key 57 | ``` 58 | 59 | ## 使用说明 60 | 61 | ### 命令格式 62 | 63 | ```bash 64 | bun run start [参数] 65 | ``` 66 | 67 | ### 参数说明 68 | 69 | 必需参数: 70 | - `--mode <类型>`:运行模式 71 | - `questions`:仅生成问题 72 | - `answers`:仅生成答案 73 | - `all`:同时生成问题和答案 74 | - `--region <名称>`:地区拼音(如 "chibi" 代表赤壁) 75 | 76 | 可选参数: 77 | - `--count <数字>`:生成问题数量(默认:100) 78 | 79 | 工作线程相关参数: 80 | - `--workers <数字>`:工作线程数(默认:CPU核心数-1) 81 | - `--batch <数字>`:批处理大小(默认:50) 82 | - `--delay <数字>`:批次间延迟(毫秒)(默认:1000) 83 | - `--attempts <数字>`:每个任务的最大重试次数(默认:3) 84 | 85 | ### Web 界面 86 | 87 | 启动 Next.js 开发服务器: 88 | 89 | ```bash 90 | bun run dev 91 | ``` 92 | 93 | 访问 `http://localhost:3000` 即可使用 Web 界面。 94 | 95 | ### 工作线程系统 96 | 97 | 应用采用多线程工作系统进行并行处理: 98 | 99 | - **架构**: 100 | - 任务均匀分配给工作线程 101 | - 每个工作线程独立处理其分配的批次 102 | - 任务完成后自动清理工作线程 103 | - 错误隔离机制防止故障级联 104 | 105 | - **性能优化**: 106 | - 根据 CPU 调整线程数(`--workers`) 107 | - 微调批处理大小以获得最佳吞吐量(`--batch`) 108 | - 通过延迟控制 API 限流(`--delay`) 109 | - 设置失败任务重试次数(`--attempts`) 110 | 111 | 优化的工作线程配置示例: 112 | ```bash 113 | bun run start --mode all --region chibi --workers 20 --batch 25 --delay 2000 114 | ``` 115 | 116 | ### 使用示例 117 | 118 | 1. 为特定地区生成问题: 119 | ```bash 120 | bun run start --mode questions --region chibi --count 50 121 | ``` 122 | 123 | 2. 为已有问题生成答案: 124 | ```bash 125 | bun run start --mode answers --region chibi 126 | ``` 127 | 128 | 3. 同时生成问题和答案: 129 | ```bash 130 | bun run start --mode all --region chibi --count 100 131 | ``` 132 | 133 | 4. 使用 Groq 模型: 134 | ```bash 135 | AI_PROVIDER=groq bun run start --mode all --region chibi 136 | ``` 137 | 138 | ### 添加新地区 139 | 140 | 在 `config/config.ts` 中添加新地区配置: 141 | 142 | ```typescript 143 | export const regions: Region[] = [ 144 | { 145 | name: "赤壁", 146 | pinyin: "chibi", 147 | description: "湖北省咸宁市赤壁市,三国赤壁之战古战场所在地" 148 | }, 149 | // 在此添加新地区 150 | { 151 | name: "新地区", 152 | pinyin: "xindiqiu", 153 | description: "新地区的描述" 154 | } 155 | ]; 156 | ``` 157 | 158 | ### 输出格式 159 | 160 | 每个地区会在 `data` 目录下生成两个 JSON 文件: 161 | 162 | 1. 问题文件:`<地区>_q_results.json` 163 | ```json 164 | [ 165 | { 166 | "question": "问题内容", 167 | "is_answered": false 168 | } 169 | ] 170 | ``` 171 | 172 | 2. 问答文件:`<地区>_qa_results.json` 173 | ```json 174 | [ 175 | { 176 | "question": "问题内容", 177 | "content": "答案内容", 178 | "reasoning_content": "推理过程和参考依据" 179 | } 180 | ] 181 | ``` 182 | 183 | ## 项目结构 184 | 185 | ``` 186 | . 187 | ├── app/ # Next.js 应用文件 188 | ├── config/ # 配置文件 189 | ├── data/ # 生成的问答数据 190 | ├── generators/ # 问答生成器 191 | ├── providers/ # AI 模型接入 192 | │ ├── groq/ # Groq 模型 193 | │ └── qianfan/ # 千帆模型 194 | ├── prompts/ # AI 提示词模板 195 | ├── types/ # TypeScript 类型定义 196 | ├── utils/ # 工具函数 197 | ├── workers/ # 多线程处理 198 | └── index.ts # 主程序入口 199 | ``` 200 | 201 | ## 错误处理 202 | 203 | 本应用实现了强大的错误处理机制: 204 | - API 调用失败自动重试 205 | - 答案生成后自动保存进度 206 | - 智能检测并过滤重复问题 207 | - 详细的错误日志和堆栈追踪 208 | - 优雅的故障恢复和状态保存 209 | 210 | ## 参与贡献 211 | 212 | 欢迎提交 Issue 和 Pull Request 来帮助改进项目! 213 | 214 | ## 许可证 215 | 216 | 本项目基于 MIT 许可证开源 - 详见 LICENSE 文件 -------------------------------------------------------------------------------- /app/api/generate/route.ts: -------------------------------------------------------------------------------- 1 | import { spawn, type ChildProcess } from 'child_process'; 2 | 3 | export async function POST(request: Request) { 4 | const encoder = new TextEncoder(); 5 | const options = await request.json(); 6 | 7 | const customReadable = new ReadableStream({ 8 | async start(controller) { 9 | try { 10 | // Construct command arguments 11 | const args = [ 12 | 'run', 13 | 'start', 14 | '--mode', options.mode, 15 | '--region', options.region, 16 | '--count', options.totalCount.toString(), 17 | '--workers', options.workerCount.toString(), 18 | '--max-q-per-worker', options.maxQPerWorker.toString(), 19 | '--attempts', options.maxAttempts.toString(), 20 | '--batch', options.batchSize.toString(), 21 | '--delay', options.delay.toString() 22 | ]; 23 | 24 | // Log the command being executed 25 | const commandStr = `AI_PROVIDER=${options.provider} bun ${args.join(' ')}`; 26 | controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'log', message: `Executing command: ${commandStr}` })}\n\n`)); 27 | 28 | // Spawn the bun process with environment variables 29 | const childProcess: ChildProcess = spawn('bun', args, { 30 | env: { 31 | ...process.env, 32 | AI_PROVIDER: options.provider, 33 | // 传递所有 API keys 34 | GROQ_API_KEY: process.env.GROQ_API_KEY || '', 35 | QIANFAN_ACCESS_KEY: process.env.QIANFAN_ACCESS_KEY || '', 36 | QIANFAN_SECRET_KEY: process.env.QIANFAN_SECRET_KEY || '', 37 | OPENAI_API_KEY: process.env.OPENAI_API_KEY || '', 38 | OPENAI_BASE_URL: process.env.OPENAI_BASE_URL || '', 39 | PATH: process.env.PATH || '' // 确保 PATH 环境变量也被传递 40 | } 41 | }); 42 | 43 | // Handle process events 44 | childProcess.stdout?.on('data', (data: Buffer) => { 45 | try { 46 | const message = data.toString(); 47 | controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'log', message })}\n\n`)); 48 | } catch (error) { 49 | // Ignore enqueue errors if the stream is already closed 50 | } 51 | }); 52 | 53 | childProcess.stderr?.on('data', (data: Buffer) => { 54 | try { 55 | const message = data.toString(); 56 | controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', message })}\n\n`)); 57 | } catch (error) { 58 | // Ignore enqueue errors if the stream is already closed 59 | } 60 | }); 61 | 62 | childProcess.on('close', (code: number | null) => { 63 | try { 64 | controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'end', code })}\n\n`)); 65 | controller.close(); 66 | } catch (error) { 67 | // Ignore enqueue errors if the stream is already closed 68 | } 69 | }); 70 | 71 | childProcess.on('error', (error: Error) => { 72 | try { 73 | controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', message: error.message })}\n\n`)); 74 | controller.close(); 75 | } catch (err) { 76 | // Ignore enqueue errors if the stream is already closed 77 | } 78 | }); 79 | } catch (error) { 80 | try { 81 | controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', message: 'Failed to start generation process' })}\n\n`)); 82 | controller.close(); 83 | } catch (err) { 84 | // Ignore enqueue errors if the stream is already closed 85 | } 86 | } 87 | }, 88 | cancel(controller) { 89 | // Handle stream cancellation 90 | try { 91 | controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'log', message: 'Stream cancelled by client' })}\n\n`)); 92 | controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'end', code: -1 })}\n\n`)); 93 | } catch (error) { 94 | // Ignore enqueue errors if the stream is already closed 95 | } 96 | } 97 | }); 98 | 99 | return new Response(customReadable, { 100 | headers: { 101 | 'Content-Type': 'text/event-stream', 102 | 'Cache-Control': 'no-cache', 103 | 'Connection': 'keep-alive', 104 | }, 105 | }); 106 | } -------------------------------------------------------------------------------- /app/api/generate/stop/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | 3 | export async function POST() { 4 | try { 5 | // Send SIGTERM to all running bun processes 6 | const killCommand = process.platform === 'win32' ? 'taskkill /F /IM bun.exe' : 'pkill -15 bun'; 7 | await new Promise((resolve, reject) => { 8 | const { exec } = require('child_process'); 9 | exec(killCommand, (error: Error | null) => { 10 | if (error) { 11 | // Ignore error if no process were found 12 | if (error.message.includes('no process found') || 13 | error.message.includes('not found') || 14 | error.message.includes('no process killed')) { 15 | resolve(null); 16 | } else { 17 | reject(error); 18 | } 19 | } else { 20 | resolve(null); 21 | } 22 | }); 23 | }); 24 | 25 | // Give processes a moment to clean up 26 | await new Promise(resolve => setTimeout(resolve, 500)); 27 | 28 | // Force kill any remaining processes 29 | const forceKillCommand = process.platform === 'win32' ? 'taskkill /F /IM bun.exe' : 'pkill -9 bun'; 30 | await new Promise((resolve) => { 31 | const { exec } = require('child_process'); 32 | exec(forceKillCommand, () => resolve(null)); 33 | }); 34 | 35 | return NextResponse.json({ 36 | success: true, 37 | message: 'Generation process stopped successfully' 38 | }); 39 | } catch (error) { 40 | console.error('Error stopping generation process:', error); 41 | return NextResponse.json( 42 | { 43 | error: 'Failed to stop generation process', 44 | details: error instanceof Error ? error.message : 'Unknown error' 45 | }, 46 | { status: 500 } 47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /app/api/regions/route.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import { NextResponse } from 'next/server'; 3 | import path from 'path'; 4 | import { Region } from '../../../config/config'; 5 | 6 | export async function POST(request: Request) { 7 | try { 8 | const newRegion: Region = await request.json(); 9 | 10 | // Validate the new region data 11 | if (!newRegion.name || !newRegion.pinyin || !newRegion.description) { 12 | return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); 13 | } 14 | 15 | // Read the current config file 16 | const configPath = path.join(process.cwd(), 'config', 'config.ts'); 17 | let configContent = await fs.readFile(configPath, 'utf-8'); 18 | 19 | // Parse the regions array 20 | const regionsMatch = configContent.match(/export const regions: Region\[\] = \[([\s\S]*?)\];/); 21 | if (!regionsMatch) { 22 | return NextResponse.json({ error: 'Could not parse config file' }, { status: 500 }); 23 | } 24 | 25 | // Add the new region 26 | const newRegionStr = ` 27 | { 28 | name: "${newRegion.name}", 29 | pinyin: "${newRegion.pinyin}", 30 | description: "${newRegion.description}" 31 | },`; 32 | 33 | // Insert the new region before the closing bracket 34 | configContent = configContent.replace( 35 | /export const regions: Region\[\] = \[([\s\S]*?)\];/, 36 | `export const regions: Region[] = [${regionsMatch[1]}${newRegionStr}\n];` 37 | ); 38 | 39 | // Write the updated config back to file 40 | await fs.writeFile(configPath, configContent, 'utf-8'); 41 | 42 | return NextResponse.json({ success: true }); 43 | } catch (error) { 44 | console.error('Error adding region:', error); 45 | return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); 46 | } 47 | } -------------------------------------------------------------------------------- /app/components/AddRegionModal.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { 4 | Button, 5 | Input, 6 | Modal, 7 | ModalBody, 8 | ModalContent, 9 | ModalFooter, 10 | ModalHeader, 11 | Textarea 12 | } from '@heroui/react'; 13 | import { Dispatch, SetStateAction } from 'react'; 14 | import { Region } from '../../config/config'; 15 | 16 | type AddRegionModalProps = { 17 | isOpen: boolean; 18 | onClose: () => void; 19 | newRegion: Partial; 20 | setNewRegion: Dispatch>>; 21 | onSubmit: (region: Partial) => Promise; 22 | }; 23 | 24 | /** 25 | * Modal component for adding new regions 26 | */ 27 | export function AddRegionModal({ 28 | isOpen, 29 | onClose, 30 | newRegion, 31 | setNewRegion, 32 | onSubmit 33 | }: AddRegionModalProps) { 34 | 35 | // Handle form submission 36 | const handleSubmit = async (e: React.FormEvent) => { 37 | e.preventDefault(); 38 | await onSubmit(newRegion); 39 | }; 40 | 41 | return ( 42 | 47 | 48 | 49 |

50 | 51 | Add New Region 52 |

53 |
54 | 55 |
56 | setNewRegion({ ...newRegion, name: e.target.value })} 61 | className="w-full" 62 | startContent={ 63 | 64 | } 65 | /> 66 | setNewRegion({ ...newRegion, pinyin: e.target.value })} 71 | className="w-full" 72 | startContent={ 73 | 74 | } 75 | /> 76 |