├── postcss.config.js ├── .env.example ├── src ├── main.jsx ├── utils │ └── mockApi.js ├── index.css ├── components │ └── Turnstile.jsx ├── i18n │ └── index.js └── App.jsx ├── vite.config.js ├── public ├── favicon.ico └── vite.svg ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── pull_request_template.md ├── wrangler.toml ├── .gitignore ├── scripts ├── dev.js └── deploy.js ├── tailwind.config.js ├── LICENSE ├── package.json ├── tests └── api.test.js ├── index.html ├── README.md └── functions └── api └── optimize-prompt.js /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Cloudflare Turnstile Configuration 2 | TURNSTILE_SITE_KEY=your_turnstile_site_key_here 3 | 4 | # Environment 5 | ENVIRONMENT=development -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | build: { 7 | outDir: 'dist', 8 | assetsDir: 'assets' 9 | }, 10 | server: { 11 | port: 3000 12 | } 13 | }) -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能请求 3 | about: 为这个项目建议一个想法 4 | title: '[FEATURE] ' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **你的功能请求是否与问题相关?请描述。** 11 | 清晰简洁地描述问题是什么。例如:我总是感到沮丧当 [...] 12 | 13 | **描述你想要的解决方案** 14 | 清晰简洁地描述你想要发生什么。 15 | 16 | **描述你考虑过的替代方案** 17 | 清晰简洁地描述你考虑过的任何替代解决方案或功能。 18 | 19 | **附加信息** 20 | 在这里添加关于功能请求的任何其他信息或截图。 -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "prompt-optimizer" 2 | compatibility_date = "2023-12-01" 3 | 4 | [env.production.vars] 5 | ENVIRONMENT = "production" 6 | 7 | [[env.production.kv_namespaces]] 8 | binding = "PROMPT_KV" 9 | id = "your-kv-namespace-id" 10 | 11 | [build] 12 | command = "npm run build" 13 | cwd = "." 14 | watch_dir = "src" 15 | 16 | [[pages.rules]] 17 | rule = "/api/*" 18 | function = "api/[[path]]" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 报告 3 | about: 创建一个报告来帮助我们改进 4 | title: '[BUG] ' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Bug 描述** 11 | 清晰简洁地描述这个 bug。 12 | 13 | **重现步骤** 14 | 重现该行为的步骤: 15 | 1. 访问 '...' 16 | 2. 点击 '....' 17 | 3. 滚动到 '....' 18 | 4. 看到错误 19 | 20 | **预期行为** 21 | 清晰简洁地描述你期望发生什么。 22 | 23 | **截图** 24 | 如果适用,添加截图来帮助解释你的问题。 25 | 26 | **环境信息 (请完成以下信息):** 27 | - 操作系统: [例如 iOS] 28 | - 浏览器 [例如 chrome, safari] 29 | - 版本 [例如 22] 30 | 31 | **移动设备 (请完成以下信息):** 32 | - 设备: [例如 iPhone6] 33 | - 操作系统: [例如 iOS8.1] 34 | - 浏览器 [例如 stock browser, safari] 35 | - 版本 [例如 22] 36 | 37 | **附加信息** 38 | 在这里添加关于问题的任何其他信息。 -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 描述 2 | 3 | 请简要描述这个 PR 的更改内容。 4 | 5 | ## 更改类型 6 | 7 | 请删除不相关的选项: 8 | 9 | - [ ] Bug 修复 (不破坏现有功能的更改) 10 | - [ ] 新功能 (添加功能的不破坏现有功能的更改) 11 | - [ ] 破坏性更改 (会导致现有功能无法正常工作的修复或功能) 12 | - [ ] 文档更新 13 | 14 | ## 测试 15 | 16 | 请描述你测试了哪些内容以验证你的更改。请提供相关测试配置的说明。 17 | 18 | - [ ] 本地开发环境测试 19 | - [ ] 构建测试 20 | - [ ] 功能测试 21 | - [ ] 回归测试 22 | 23 | ## 检查清单 24 | 25 | - [ ] 我的代码遵循了这个项目的代码风格指南 26 | - [ ] 我已经对我的代码进行了自我审查 27 | - [ ] 我已经对我的代码进行了注释,特别是在难以理解的区域 28 | - [ ] 我已经对相应的文档进行了更改 29 | - [ ] 我的更改没有产生新的警告 30 | - [ ] 我已经添加了证明我的修复有效或我的功能工作的测试 31 | - [ ] 新的和现有的单元测试在我的更改下都通过了本地测试 32 | 33 | ## 相关 Issue 34 | 35 | 修复 # (issue 编号) 36 | 37 | ## 截图 (如果适用) 38 | 39 | 请添加截图来帮助解释你的更改。 40 | 41 | ## 附加信息 42 | 43 | 在这里添加关于 PR 的任何其他信息。 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Production builds 8 | dist/ 9 | build/ 10 | 11 | # Environment variables 12 | .env 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | # Wrangler 19 | .wrangler/ 20 | 21 | # IDE 22 | .vscode/ 23 | .idea/ 24 | *.swp 25 | *.swo 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | 31 | # Logs 32 | logs 33 | *.log 34 | 35 | # Runtime data 36 | pids 37 | *.pid 38 | *.seed 39 | *.pid.lock 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage/ 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity -------------------------------------------------------------------------------- /scripts/dev.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { spawn } from 'child_process' 4 | import fs from 'fs' 5 | 6 | console.log('🚀 启动开发环境...\n') 7 | 8 | // 检查环境变量文件 9 | if (!fs.existsSync('.env.development')) { 10 | console.log('⚠️ .env.development 文件不存在,创建默认配置...') 11 | const defaultEnv = `# Development Environment Variables 12 | VITE_TURNSTILE_SITE_KEY=1x00000000000000000000AA 13 | VITE_API_BASE_URL=http://localhost:3000` 14 | 15 | fs.writeFileSync('.env.development', defaultEnv) 16 | console.log('✅ 已创建 .env.development 文件') 17 | } 18 | 19 | // 启动开发服务器 20 | console.log('🌐 启动 Vite 开发服务器...') 21 | const viteProcess = spawn('npm', ['run', 'dev'], { 22 | stdio: 'inherit', 23 | shell: true 24 | }) 25 | 26 | viteProcess.on('close', (code) => { 27 | console.log(`\n开发服务器已停止 (退出码: ${code})`) 28 | }) 29 | 30 | // 处理 Ctrl+C 31 | process.on('SIGINT', () => { 32 | console.log('\n🛑 正在停止开发服务器...') 33 | viteProcess.kill('SIGINT') 34 | process.exit(0) 35 | }) -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | darkMode: 'class', 8 | theme: { 9 | extend: { 10 | colors: { 11 | 'openai-green': '#10a37f', 12 | 'openai-dark': '#0d0d0d', 13 | 'openai-gray': '#f7f7f8', 14 | 'openai-border': '#e5e5e5', 15 | 'openai-text': '#2d333a', 16 | 'openai-light-gray': '#6e6e80', 17 | // 暗色主题颜色 18 | 'dark-bg': '#1a1a1a', 19 | 'dark-surface': '#2d2d2d', 20 | 'dark-border': '#404040', 21 | 'dark-text': '#e5e5e5', 22 | 'dark-text-secondary': '#a0a0a0', 23 | 'dark-input': '#333333' 24 | }, 25 | fontFamily: { 26 | 'sans': ['Söhne', 'ui-sans-serif', 'system-ui', '-apple-system', 'Segoe UI', 'Roboto', 'Ubuntu', 'Cantarell', 'Noto Sans', 'sans-serif'] 27 | } 28 | }, 29 | }, 30 | plugins: [], 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 WonderLand33 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/utils/mockApi.js: -------------------------------------------------------------------------------- 1 | // Mock API for development 2 | export const mockOptimizePrompt = async (prompt) => { 3 | // 模拟API延迟 4 | await new Promise(resolve => setTimeout(resolve, 2000)) 5 | 6 | // 模拟优化后的prompt 7 | const optimizedPrompt = `## 优化后的Prompt 8 | 9 | **角色设定**: 你是一个专业的${getPromptDomain(prompt)}专家。 10 | 11 | **任务描述**: ${prompt} 12 | 13 | **输出要求**: 14 | 1. 请提供详细、准确的回答 15 | 2. 使用清晰的结构和逻辑 16 | 3. 包含具体的例子或步骤 17 | 4. 确保信息的实用性和可操作性 18 | 19 | **输出格式**: 20 | - 使用标准的markdown格式 21 | - 重要信息用**粗体**标注 22 | - 如有步骤,请使用有序列表 23 | 24 | **注意事项**: 25 | - 保持专业性和准确性 26 | - 避免模糊或含糊的表述 27 | - 如有不确定的信息,请明确说明 28 | 29 | 请基于以上要求完成任务。` 30 | 31 | return { optimizedPrompt } 32 | } 33 | 34 | function getPromptDomain(prompt) { 35 | const domains = { 36 | '代码': ['代码', '编程', '开发', 'code', 'programming'], 37 | '写作': ['写作', '文章', '内容', '文案', 'writing'], 38 | '分析': ['分析', '数据', '研究', 'analysis'], 39 | '设计': ['设计', '创意', 'design'], 40 | '教育': ['教学', '学习', '教育', 'education'], 41 | '商业': ['商业', '营销', '市场', 'business'] 42 | } 43 | 44 | for (const [domain, keywords] of Object.entries(domains)) { 45 | if (keywords.some(keyword => prompt.toLowerCase().includes(keyword))) { 46 | return domain 47 | } 48 | } 49 | 50 | return 'AI助手' 51 | } -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { execSync } from 'child_process' 4 | import fs from 'fs' 5 | import path from 'path' 6 | 7 | const PROJECT_NAME = 'prompt-optimizer' 8 | 9 | console.log('🚀 开始部署 AI Prompt 优化工具...\n') 10 | 11 | // 检查是否已登录 Wrangler 12 | try { 13 | execSync('wrangler whoami', { stdio: 'pipe' }) 14 | console.log('✅ Wrangler 已登录') 15 | } catch (error) { 16 | console.log('❌ 请先登录 Wrangler: wrangler login') 17 | process.exit(1) 18 | } 19 | 20 | // 构建项目 21 | console.log('📦 构建项目...') 22 | try { 23 | execSync('npm run build', { stdio: 'inherit' }) 24 | console.log('✅ 项目构建完成') 25 | } catch (error) { 26 | console.log('❌ 构建失败') 27 | process.exit(1) 28 | } 29 | 30 | // 检查 dist 目录 31 | if (!fs.existsSync('dist')) { 32 | console.log('❌ dist 目录不存在') 33 | process.exit(1) 34 | } 35 | 36 | // 部署到 Cloudflare Pages 37 | console.log('🌐 部署到 Cloudflare Pages...') 38 | try { 39 | execSync(`wrangler pages deploy dist --project-name ${PROJECT_NAME}`, { 40 | stdio: 'inherit' 41 | }) 42 | console.log('✅ 部署成功!') 43 | } catch (error) { 44 | console.log('❌ 部署失败') 45 | process.exit(1) 46 | } 47 | 48 | console.log('\n🎉 部署完成!') 49 | console.log('📝 请确保在 Cloudflare Dashboard 中配置以下环境变量:') 50 | console.log(' - OPENAI_API_KEY') 51 | console.log(' - TURNSTILE_SECRET_KEY') 52 | console.log(' - OPENAI_MODEL (可选)') 53 | console.log(' - OPENAI_API_URL (可选)') 54 | console.log('\n🔗 访问你的应用: https://prompt-optimizer.pages.dev') -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prompt-optimizer", 3 | "version": "1.0.0", 4 | "description": "AI Prompt Optimization Tool built with React and Cloudflare", 5 | "type": "module", 6 | "main": "index.js", 7 | "keywords": [ 8 | "ai", 9 | "prompt", 10 | "optimization", 11 | "openai", 12 | "cloudflare", 13 | "react", 14 | "sse", 15 | "dark-mode" 16 | ], 17 | "author": "WonderLand33", 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/WonderLand33/prompt-optimizer.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/WonderLand33/prompt-optimizer/issues" 25 | }, 26 | "homepage": "https://github.com/WonderLand33/prompt-optimizer#readme", 27 | "scripts": { 28 | "dev": "vite", 29 | "dev:setup": "node scripts/dev.js", 30 | "build": "vite build", 31 | "preview": "vite preview", 32 | "deploy": "node scripts/deploy.js", 33 | "deploy:simple": "wrangler pages deploy dist", 34 | "functions:dev": "wrangler pages dev dist --compatibility-date=2023-12-01", 35 | "functions:deploy": "wrangler pages deploy dist --compatibility-date=2023-12-01", 36 | "test:api": "node tests/api.test.js" 37 | }, 38 | "dependencies": { 39 | "react": "^18.2.0", 40 | "react-dom": "^18.2.0", 41 | "lucide-react": "^0.294.0" 42 | }, 43 | "devDependencies": { 44 | "@types/react": "^18.2.43", 45 | "@types/react-dom": "^18.2.17", 46 | "@vitejs/plugin-react": "^4.2.1", 47 | "autoprefixer": "^10.4.16", 48 | "postcss": "^8.4.32", 49 | "tailwindcss": "^3.3.6", 50 | "vite": "^5.0.8", 51 | "wrangler": "^3.19.0" 52 | } 53 | } -------------------------------------------------------------------------------- /tests/api.test.js: -------------------------------------------------------------------------------- 1 | // API 测试文件 2 | // 这个文件可以用来测试 Cloudflare Functions 3 | 4 | const API_BASE_URL = 'http://localhost:8788' // Wrangler dev server URL 5 | 6 | async function testOptimizePrompt() { 7 | console.log('🧪 测试 Prompt 优化 API...') 8 | 9 | const testPrompt = '写一篇关于人工智能的文章' 10 | 11 | try { 12 | const response = await fetch(`${API_BASE_URL}/api/optimize-prompt`, { 13 | method: 'POST', 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | }, 17 | body: JSON.stringify({ 18 | prompt: testPrompt, 19 | turnstileToken: 'test-token' 20 | }), 21 | }) 22 | 23 | const data = await response.json() 24 | 25 | if (response.ok) { 26 | console.log('✅ API 测试成功') 27 | console.log('原始 Prompt:', testPrompt) 28 | console.log('优化后 Prompt:', data.optimizedPrompt) 29 | } else { 30 | console.log('❌ API 测试失败:', data.error) 31 | } 32 | } catch (error) { 33 | console.log('❌ 网络错误:', error.message) 34 | } 35 | } 36 | 37 | async function testCORS() { 38 | console.log('🧪 测试 CORS...') 39 | 40 | try { 41 | const response = await fetch(`${API_BASE_URL}/api/optimize-prompt`, { 42 | method: 'OPTIONS', 43 | }) 44 | 45 | if (response.ok) { 46 | console.log('✅ CORS 测试成功') 47 | } else { 48 | console.log('❌ CORS 测试失败') 49 | } 50 | } catch (error) { 51 | console.log('❌ CORS 测试错误:', error.message) 52 | } 53 | } 54 | 55 | // 运行测试 56 | async function runTests() { 57 | console.log('🚀 开始 API 测试...\n') 58 | 59 | await testCORS() 60 | console.log('') 61 | await testOptimizePrompt() 62 | 63 | console.log('\n✨ 测试完成') 64 | } 65 | 66 | // 如果直接运行此文件 67 | if (import.meta.url === `file://${process.argv[1]}`) { 68 | runTests() 69 | } 70 | 71 | export { testOptimizePrompt, testCORS } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | AI Prompt 优化工具 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 14 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 15 | sans-serif; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | background-color: #ffffff; 19 | transition: background-color 0.3s ease; 20 | } 21 | 22 | .dark body { 23 | background-color: #1a1a1a; 24 | } 25 | 26 | .gradient-text { 27 | background: linear-gradient(90deg, #10a37f 0%, #1a73e8 100%); 28 | -webkit-background-clip: text; 29 | -webkit-text-fill-color: transparent; 30 | background-clip: text; 31 | } 32 | 33 | /* 暗色模式下的渐变文字保持相同效果 */ 34 | .dark .gradient-text { 35 | background: linear-gradient(90deg, #10a37f 0%, #1a73e8 100%); 36 | -webkit-background-clip: text; 37 | -webkit-text-fill-color: transparent; 38 | background-clip: text; 39 | } 40 | 41 | .loading-dots { 42 | display: inline-block; 43 | } 44 | 45 | .loading-dots::after { 46 | content: ''; 47 | animation: dots 1.5s steps(4, end) infinite; 48 | } 49 | 50 | @keyframes dots { 51 | 0%, 20% { content: ''; } 52 | 40% { content: '.'; } 53 | 60% { content: '..'; } 54 | 80%, 100% { content: '...'; } 55 | } 56 | 57 | /* 流式内容动画 */ 58 | .streaming-cursor { 59 | animation: blink 1s infinite; 60 | } 61 | 62 | @keyframes blink { 63 | 0%, 50% { opacity: 1; } 64 | 51%, 100% { opacity: 0; } 65 | } 66 | 67 | /* 流式内容容器 */ 68 | .streaming-content { 69 | position: relative; 70 | overflow: hidden; 71 | } 72 | 73 | .streaming-content::after { 74 | content: ''; 75 | position: absolute; 76 | top: 0; 77 | right: 0; 78 | width: 2px; 79 | height: 100%; 80 | background: linear-gradient(90deg, transparent, #10a37f); 81 | animation: typing-indicator 2s ease-in-out infinite; 82 | } 83 | 84 | @keyframes typing-indicator { 85 | 0%, 100% { opacity: 0; } 86 | 50% { opacity: 1; } 87 | } 88 | 89 | /* 状态消息动画 */ 90 | .status-fade-in { 91 | animation: fadeInUp 0.3s ease-out; 92 | } 93 | 94 | @keyframes fadeInUp { 95 | from { 96 | opacity: 0; 97 | transform: translateY(10px); 98 | } 99 | to { 100 | opacity: 1; 101 | transform: translateY(0); 102 | } 103 | } -------------------------------------------------------------------------------- /src/components/Turnstile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react' 2 | 3 | const Turnstile = forwardRef(({ 4 | siteKey = import.meta.env.VITE_TURNSTILE_SITE_KEY || "1x00000000000000000000AA", 5 | onVerify, 6 | theme = 'light', 7 | language = 'en' 8 | }, ref) => { 9 | const containerRef = useRef(null) 10 | const widgetId = useRef(null) 11 | 12 | useImperativeHandle(ref, () => ({ 13 | getResponse: () => { 14 | if (window.turnstile && widgetId.current !== null) { 15 | return window.turnstile.getResponse(widgetId.current) 16 | } 17 | return null 18 | }, 19 | reset: () => { 20 | if (window.turnstile && widgetId.current !== null) { 21 | window.turnstile.reset(widgetId.current) 22 | } 23 | } 24 | })) 25 | 26 | useEffect(() => { 27 | const cleanup = () => { 28 | if (window.turnstile && widgetId.current !== null) { 29 | window.turnstile.remove(widgetId.current) 30 | widgetId.current = null 31 | } 32 | if (containerRef.current) { 33 | containerRef.current.innerHTML = '' 34 | } 35 | } 36 | 37 | const renderTurnstile = () => { 38 | if (window.turnstile && containerRef.current) { 39 | // Map language codes to Turnstile supported languages 40 | const turnstileLanguage = language === 'zh' ? 'zh-CN' : 'en' 41 | 42 | console.log('Rendering Turnstile with theme:', theme, 'language:', turnstileLanguage) 43 | 44 | widgetId.current = window.turnstile.render(containerRef.current, { 45 | sitekey: siteKey, 46 | theme: theme, 47 | language: turnstileLanguage, 48 | callback: (token) => { 49 | if (onVerify) { 50 | onVerify(token) 51 | } 52 | }, 53 | 'error-callback': () => { 54 | console.error('Turnstile error') 55 | }, 56 | 'expired-callback': () => { 57 | console.log('Turnstile expired') 58 | } 59 | }) 60 | } 61 | } 62 | 63 | // Clean up previous widget before rendering new one 64 | cleanup() 65 | 66 | if (window.turnstile) { 67 | renderTurnstile() 68 | } else { 69 | // Wait for Turnstile to load 70 | const checkTurnstile = setInterval(() => { 71 | if (window.turnstile) { 72 | clearInterval(checkTurnstile) 73 | renderTurnstile() 74 | } 75 | }, 100) 76 | 77 | return () => { 78 | clearInterval(checkTurnstile) 79 | cleanup() 80 | } 81 | } 82 | 83 | return cleanup 84 | }, [siteKey, onVerify, theme, language]) 85 | 86 | return
87 | }) 88 | 89 | Turnstile.displayName = 'Turnstile' 90 | 91 | export default Turnstile -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | // 多语言配置 2 | export const languages = { 3 | en: 'English', 4 | zh: '中文' 5 | } 6 | 7 | export const translations = { 8 | en: { 9 | // Header 10 | title: 'AI Prompt Optimizer', 11 | toggleTheme: 'Toggle Theme', 12 | 13 | // Main content 14 | mainTitle: 'Make AI Better Understand Your', 15 | promptText: 'Prompt', 16 | mainDescription: 'Enter your original prompt, and our AI will help you optimize it to make it clearer, more specific, and more effective.', 17 | 18 | // Form 19 | inputLabel: 'Original Prompt', 20 | inputPlaceholder: 'Please enter the prompt you want to optimize...', 21 | optimizeButton: 'Optimize Prompt', 22 | optimizing: 'Optimizing...', 23 | 24 | // Output 25 | outputLabel: 'Optimized Prompt', 26 | copy: 'Copy', 27 | copied: 'Copied', 28 | 29 | // Tips 30 | tipsTitle: 'Optimization Tips', 31 | tips: [ 32 | 'Provide specific context and background information', 33 | 'Clearly specify the expected output format', 34 | 'Use clear and concise language', 35 | 'Include relevant examples or references', 36 | 'Specify target audience or use case' 37 | ], 38 | 39 | // Footer 40 | poweredBy: 'Powered by OpenAI & Cloudflare', 41 | 42 | // Error messages 43 | errorEmptyPrompt: 'Please enter a prompt to optimize', 44 | errorTurnstileNotCompleted: 'Please complete the verification', 45 | errorVerificationFailed: 'Verification failed', 46 | errorRequestFailed: 'Request failed, please try again', 47 | errorServerError: 'Server internal error', 48 | errorAIUnavailable: 'AI service temporarily unavailable, please try again later', 49 | 50 | // Language selector 51 | language: 'Language', 52 | selectLanguage: 'Select Language' 53 | }, 54 | zh: { 55 | // Header 56 | title: 'AI Prompt 优化工具', 57 | toggleTheme: '切换主题', 58 | 59 | // Main content 60 | mainTitle: '让AI更好地理解你的', 61 | promptText: 'Prompt', 62 | mainDescription: '输入你的原始Prompt,我们的AI将帮助你优化它,使其更加清晰、具体和有效。', 63 | 64 | // Form 65 | inputLabel: '原始 Prompt', 66 | inputPlaceholder: '请输入你想要优化的Prompt...', 67 | optimizeButton: '优化 Prompt', 68 | optimizing: '优化中...', 69 | 70 | // Output 71 | outputLabel: '优化后的 Prompt', 72 | copy: '复制', 73 | copied: '已复制', 74 | 75 | // Tips 76 | tipsTitle: '优化建议', 77 | tips: [ 78 | '提供具体的上下文和背景信息', 79 | '明确指定期望的输出格式', 80 | '使用清晰、简洁的语言', 81 | '包含相关的示例或参考', 82 | '指定目标受众或使用场景' 83 | ], 84 | 85 | // Footer 86 | poweredBy: 'Powered by OpenAI & Cloudflare', 87 | 88 | // Error messages 89 | errorEmptyPrompt: '请输入需要优化的Prompt', 90 | errorTurnstileNotCompleted: '请完成验证码验证', 91 | errorVerificationFailed: '验证码验证失败', 92 | errorRequestFailed: '请求失败,请重试', 93 | errorServerError: '服务器内部错误', 94 | errorAIUnavailable: 'AI服务暂时不可用,请稍后重试', 95 | 96 | // Language selector 97 | language: '语言', 98 | selectLanguage: '选择语言' 99 | } 100 | } 101 | 102 | // 获取浏览器语言偏好 103 | export const getBrowserLanguage = () => { 104 | const browserLang = navigator.language || navigator.userLanguage 105 | if (browserLang.startsWith('zh')) { 106 | return 'zh' 107 | } 108 | return 'en' // 默认英文 109 | } 110 | 111 | // 获取翻译文本 112 | export const getTranslation = (language, key) => { 113 | return translations[language]?.[key] || translations.en[key] || key 114 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Prompt 优化工具 2 | 3 | [![GitHub stars](https://img.shields.io/github/stars/WonderLand33/prompt-optimizer?style=social)](https://github.com/WonderLand33/prompt-optimizer) 4 | [![GitHub forks](https://img.shields.io/github/forks/WonderLand33/prompt-optimizer?style=social)](https://github.com/WonderLand33/prompt-optimizer) 5 | [![GitHub issues](https://img.shields.io/github/issues/WonderLand33/prompt-optimizer)](https://github.com/WonderLand33/prompt-optimizer/issues) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 7 | 8 | 一个基于 Cloudflare Pages 和 Functions 的 AI Prompt 优化工具,使用 React 作为前端框架,通过 OpenAI API 提供智能的 Prompt 优化服务。 9 | 10 | 🌟 **[在线演示](https://systemprompt.icu)** | 📖 **[项目文档](https://github.com/WonderLand33/prompt-optimizer/wiki)** | 🐛 **[问题反馈](https://github.com/WonderLand33/prompt-optimizer/issues)** 11 | 12 | ## 功能特性 13 | 14 | - 🤖 **AI 驱动**: 使用 OpenAI GPT 模型优化 Prompt 15 | - 🛡️ **安全验证**: 集成 Cloudflare Turnstile 防止滥用 16 | - 🎨 **OpenAI 风格 UI**: 仿照 OpenAI 官网的设计风格 17 | - 🌙 **夜间模式**: 支持明暗主题切换,自动适配系统偏好 18 | - 📡 **流式输出**: 支持 Server-Sent Events (SSE) 实时显示优化过程 19 | - ⚡ **快速部署**: 基于 Cloudflare Pages 和 Functions 20 | - 🔒 **环境变量配置**: 所有敏感信息通过环境变量管理 21 | - 📱 **响应式设计**: 完美适配桌面端和移动端 22 | 23 | ## 技术栈 24 | 25 | - **前端**: React + Vite + Tailwind CSS 26 | - **后端**: Cloudflare Functions (Node.js) 27 | - **部署**: Cloudflare Pages + Wrangler 28 | - **AI 服务**: OpenAI API 29 | - **验证**: Cloudflare Turnstile 30 | 31 | ## 快速开始 32 | 33 | ### 1. 安装依赖 34 | 35 | ```bash 36 | npm install 37 | ``` 38 | 39 | ### 2. 配置环境变量 40 | 41 | 复制 `.env.example` 为 `.env` 并填入相应的配置: 42 | 43 | ```bash 44 | cp .env.example .env 45 | ``` 46 | 47 | 需要配置的环境变量: 48 | - `OPENAI_API_KEY`: OpenAI API 密钥 49 | - `OPENAI_API_URL`: OpenAI API 地址 50 | - `OPENAI_MODEL`: 使用的模型 51 | - `TURNSTILE_SECRET_KEY`: Cloudflare Turnstile 密钥 52 | - `TURNSTILE_SITE_KEY`: Cloudflare Turnstile 站点密钥 53 | - `OPENAI_PROMPT`: 优化的 Prompt 54 | 55 | ### 3. 本地开发 56 | 57 | ```bash 58 | # 启动开发服务器 59 | npm run dev 60 | 61 | # 在另一个终端启动 Functions 开发服务器 62 | npm run functions:dev 63 | ``` 64 | 65 | ### 4. 构建项目 66 | 67 | ```bash 68 | npm run build 69 | ``` 70 | 71 | ### 5. 部署到 Cloudflare 72 | 73 | 首先确保已安装并登录 Wrangler: 74 | 75 | ```bash 76 | # 安装 Wrangler(如果还没安装) 77 | npm install -g wrangler 78 | 79 | # 登录 Cloudflare 80 | wrangler login 81 | ``` 82 | 83 | 然后部署项目: 84 | 85 | ```bash 86 | # 部署到 Cloudflare Pages 87 | npm run deploy 88 | ``` 89 | 90 | ## 配置说明 91 | 92 | ### Cloudflare Turnstile 设置 93 | 94 | 1. 访问 [Cloudflare Dashboard](https://dash.cloudflare.com/) 95 | 2. 进入 "Turnstile" 部分 96 | 3. 创建新的站点 97 | 4. 获取 Site Key 和 Secret Key 98 | 5. 将密钥添加到环境变量中 99 | 100 | ### OpenAI API 设置 101 | 102 | 1. 访问 [OpenAI Platform](https://platform.openai.com/) 103 | 2. 创建 API Key 104 | 3. 将 API Key 添加到环境变量中 105 | 106 | ### Wrangler 配置 107 | 108 | 在 `wrangler.toml` 中配置环境变量: 109 | 110 | ```toml 111 | [env.production.vars] 112 | OPENAI_API_KEY = "your_api_key" 113 | TURNSTILE_SECRET_KEY = "your_secret_key" 114 | ``` 115 | 116 | ## 项目结构 117 | 118 | ``` 119 | prompt.icu/ 120 | ├── src/ # React 前端源码 121 | │ ├── App.jsx # 主应用组件 122 | │ ├── main.jsx # 应用入口 123 | │ └── index.css # 全局样式 124 | ├── functions/ # Cloudflare Functions 125 | │ └── api/ 126 | │ └── optimize-prompt.js # Prompt 优化 API 127 | ├── public/ # 静态资源 128 | ├── dist/ # 构建输出 129 | ├── package.json # 项目配置 130 | ├── wrangler.toml # Cloudflare 配置 131 | ├── vite.config.js # Vite 配置 132 | ├── tailwind.config.js # Tailwind 配置 133 | └── README.md # 项目文档 134 | ``` 135 | 136 | ## API 接口 137 | 138 | ### POST /api/optimize-prompt 139 | 140 | 优化 Prompt 的 API 接口。 141 | 142 | **请求体:** 143 | ```json 144 | { 145 | "prompt": "需要优化的原始 Prompt", 146 | "turnstileToken": "Turnstile 验证 token" 147 | } 148 | ``` 149 | 150 | **响应:** 151 | ```json 152 | { 153 | "optimizedPrompt": "优化后的 Prompt" 154 | } 155 | ``` 156 | 157 | ## 开发路线图 158 | 159 | - [x] 基础 Prompt 优化功能 160 | - [x] Cloudflare Turnstile 集成 161 | - [x] OpenAI 风格 UI 设计 162 | - [x] 夜间模式支持 163 | - [x] SSE 流式输出 164 | - [x] 多语言支持 (i18n) 165 | - [ ] Prompt 模板库 166 | - [ ] 历史记录功能 167 | - [ ] 用户账户系统 168 | - [ ] API 使用统计 169 | - [ ] 更多 AI 模型支持 170 | 171 | ## 贡献指南 172 | 173 | 我们欢迎所有形式的贡献!请查看 [贡献指南](CONTRIBUTING.md) 了解详细信息。 174 | 175 | ### 如何贡献 176 | 177 | 1. Fork 这个仓库 178 | 2. 创建你的特性分支 (`git checkout -b feature/AmazingFeature`) 179 | 3. 提交你的更改 (`git commit -m 'Add some AmazingFeature'`) 180 | 4. 推送到分支 (`git push origin feature/AmazingFeature`) 181 | 5. 打开一个 Pull Request 182 | 183 | ### 开发环境设置 184 | 185 | ```bash 186 | # 克隆仓库 187 | git clone https://github.com/WonderLand33/prompt-optimizer.git 188 | cd prompt-optimizer 189 | 190 | # 安装依赖 191 | npm install 192 | 193 | # 配置环境变量 194 | cp .env.example .env 195 | # 编辑 .env 文件,填入你的 API 密钥 196 | 197 | # 启动开发服务器 198 | npm run dev 199 | ``` 200 | 201 | ## 问题反馈 202 | 203 | 如果你遇到任何问题或有功能建议,请: 204 | 205 | 1. 查看 [已知问题](https://github.com/WonderLand33/prompt-optimizer/issues) 206 | 2. 如果问题不存在,请 [创建新的 Issue](https://github.com/WonderLand33/prompt-optimizer/issues/new) 207 | 208 | ## 致谢 209 | 210 | - [OpenAI](https://openai.com/) - 提供强大的 AI 模型 211 | - [Cloudflare](https://cloudflare.com/) - 提供优秀的边缘计算平台 212 | - [React](https://reactjs.org/) - 构建用户界面的 JavaScript 库 213 | - [Tailwind CSS](https://tailwindcss.com/) - 实用优先的 CSS 框架 214 | - [Lucide](https://lucide.dev/) - 美观的开源图标库 215 | 216 | ## 许可证 217 | 218 | 本项目基于 [MIT License](LICENSE) 开源。 219 | 220 | ## 联系方式 221 | 222 | - GitHub: [@WonderLand33](https://github.com/WonderLand33) 223 | - 项目链接: [https://github.com/WonderLand33/prompt-optimizer](https://github.com/WonderLand33/prompt-optimizer) 224 | 225 | --- 226 | 227 | ⭐ 如果这个项目对你有帮助,请给它一个 Star! -------------------------------------------------------------------------------- /functions/api/optimize-prompt.js: -------------------------------------------------------------------------------- 1 | // Cloudflare Function for optimizing prompts using OpenAI API 2 | 3 | export async function onRequestPost(context) { 4 | const { request, env } = context; 5 | 6 | // 记录请求开始时间 7 | const startTime = Date.now(); 8 | 9 | // 验证环境变量(临时日志,部署后可删除) 10 | console.log('🔧 Environment check:', env); 11 | console.log(env.OPENAI_API_URL) 12 | 13 | // CORS headers for SSE 14 | const corsHeaders = { 15 | 'Access-Control-Allow-Origin': '*', 16 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 17 | 'Access-Control-Allow-Headers': 'Content-Type', 18 | }; 19 | 20 | // SSE headers 21 | const sseHeaders = { 22 | 'Content-Type': 'text/event-stream', 23 | 'Cache-Control': 'no-cache', 24 | 'Connection': 'keep-alive', 25 | ...corsHeaders 26 | }; 27 | 28 | try { 29 | // Parse request body 30 | const { prompt, turnstileToken, language = 'en' } = await request.json(); 31 | 32 | // Multi-language error messages 33 | const errorMessages = { 34 | en: { 35 | noPrompt: 'Please provide a prompt to optimize', 36 | noTurnstile: 'Please complete the verification', 37 | turnstileFailed: 'Verification failed', 38 | aiUnavailable: 'AI service is temporarily unavailable, please try again later', 39 | serverError: 'Internal server error' 40 | }, 41 | zh: { 42 | noPrompt: '请提供需要优化的Prompt', 43 | noTurnstile: '请完成验证码验证', 44 | turnstileFailed: '验证码验证失败', 45 | aiUnavailable: 'AI服务暂时不可用,请稍后重试', 46 | serverError: '服务器内部错误' 47 | } 48 | }; 49 | 50 | const messages = errorMessages[language] || errorMessages.en; 51 | 52 | if (!prompt || !prompt.trim()) { 53 | return new Response(JSON.stringify({ error: messages.noPrompt }), { 54 | status: 400, 55 | headers: { 'Content-Type': 'application/json', ...corsHeaders } 56 | }); 57 | } 58 | 59 | if (!turnstileToken) { 60 | return new Response(JSON.stringify({ error: messages.noTurnstile }), { 61 | status: 400, 62 | headers: { 'Content-Type': 'application/json', ...corsHeaders } 63 | }); 64 | } 65 | 66 | // Verify Turnstile token 67 | const turnstileResponse = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', { 68 | method: 'POST', 69 | headers: { 70 | 'Content-Type': 'application/x-www-form-urlencoded', 71 | }, 72 | body: `secret=${env.TURNSTILE_SECRET_KEY}&response=${turnstileToken}`, 73 | }); 74 | 75 | const turnstileResult = await turnstileResponse.json(); 76 | 77 | if (!turnstileResult.success) { 78 | return new Response(JSON.stringify({ error: messages.turnstileFailed }), { 79 | status: 400, 80 | headers: { 'Content-Type': 'application/json', ...corsHeaders } 81 | }); 82 | } 83 | 84 | // Prepare the optimization prompt with multi-language support 85 | // Use environment variables based on language 86 | const systemPrompt = language === 'zh' ? env.SYSTEM_PROMPT_ZH : env.SYSTEM_PROMPT_EN; 87 | 88 | const userPrompts = { 89 | en: `Please optimize the following prompt: 90 | 91 | ${prompt}`, 92 | zh: `请优化以下Prompt: 93 | 94 | ${prompt}` 95 | }; 96 | 97 | const userPrompt = userPrompts[language] || userPrompts.en; 98 | 99 | // Create SSE stream 100 | const { readable, writable } = new TransformStream(); 101 | const writer = writable.getWriter(); 102 | 103 | // Helper function to send SSE data 104 | const sendSSE = (data, event = 'data') => { 105 | const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`; 106 | return writer.write(new TextEncoder().encode(message)); 107 | }; 108 | 109 | // Start the streaming process 110 | (async () => { 111 | try { 112 | // Call OpenAI API with streaming 113 | const openaiResponse = await fetch(env.OPENAI_API_URL, { 114 | method: 'POST', 115 | headers: { 116 | 'Authorization': `Bearer ${env.OPENAI_API_KEY}`, 117 | 'Content-Type': 'application/json', 118 | }, 119 | body: JSON.stringify({ 120 | model: env.OPENAI_API_MODEL, 121 | messages: [ 122 | { role: 'system', content: systemPrompt }, 123 | { role: 'user', content: userPrompt } 124 | ], 125 | max_tokens: 2000, 126 | temperature: 0.7, 127 | stream: true, // 启用流式输出 128 | }), 129 | }); 130 | 131 | if (!openaiResponse.ok) { 132 | const errorData = await openaiResponse.json(); 133 | console.error('OpenAI API Error:', errorData); 134 | await sendSSE({ error: messages.aiUnavailable }, 'error'); 135 | await writer.close(); 136 | return; 137 | } 138 | 139 | // Process streaming response 140 | const reader = openaiResponse.body.getReader(); 141 | const decoder = new TextDecoder(); 142 | let buffer = ''; 143 | let fullContent = ''; 144 | 145 | while (true) { 146 | const { done, value } = await reader.read(); 147 | 148 | if (done) break; 149 | 150 | buffer += decoder.decode(value, { stream: true }); 151 | const lines = buffer.split('\n'); 152 | buffer = lines.pop() || ''; 153 | 154 | for (const line of lines) { 155 | if (line.startsWith('data: ')) { 156 | const data = line.slice(6); 157 | 158 | if (data === '[DONE]') { 159 | break; 160 | } 161 | 162 | try { 163 | const parsed = JSON.parse(data); 164 | const content = parsed.choices?.[0]?.delta?.content; 165 | 166 | if (content) { 167 | fullContent += content; 168 | // Send incremental content 169 | await sendSSE({ 170 | content: content, 171 | fullContent: fullContent 172 | }, 'chunk'); 173 | } 174 | } catch (e) { 175 | // Skip invalid JSON 176 | continue; 177 | } 178 | } 179 | } 180 | } 181 | 182 | // Send completion event 183 | const duration = Date.now() - startTime; 184 | console.log(`✅ Prompt optimization successful - Duration: ${duration}ms`); 185 | 186 | await sendSSE({ 187 | status: 'complete', 188 | optimizedPrompt: fullContent, 189 | duration: duration 190 | }, 'complete'); 191 | 192 | } catch (error) { 193 | const duration = Date.now() - startTime; 194 | console.error(`❌ Function Error (Duration: ${duration}ms):`, error); 195 | 196 | await sendSSE({ 197 | error: messages.serverError, 198 | timestamp: new Date().toISOString() 199 | }, 'error'); 200 | } finally { 201 | await writer.close(); 202 | } 203 | })(); 204 | 205 | return new Response(readable, { 206 | status: 200, 207 | headers: sseHeaders 208 | }); 209 | 210 | } catch (error) { 211 | const duration = Date.now() - startTime; 212 | console.error(`❌ Function Error (Duration: ${duration}ms):`, error); 213 | 214 | // Default error message for catch block (when language might not be available) 215 | const defaultErrorMessage = 'Internal server error'; 216 | 217 | return new Response(JSON.stringify({ 218 | error: defaultErrorMessage, 219 | timestamp: new Date().toISOString() 220 | }), { 221 | status: 500, 222 | headers: { 'Content-Type': 'application/json', ...corsHeaders } 223 | }); 224 | } 225 | } 226 | 227 | // Handle OPTIONS request for CORS 228 | export async function onRequestOptions() { 229 | return new Response(null, { 230 | status: 200, 231 | headers: { 232 | 'Access-Control-Allow-Origin': '*', 233 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 234 | 'Access-Control-Allow-Headers': 'Content-Type', 235 | }, 236 | }); 237 | } -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react' 2 | import { Send, Sparkles, Copy, Check, AlertCircle, Moon, Sun, Globe } from 'lucide-react' 3 | import Turnstile from './components/Turnstile' 4 | import { mockOptimizePrompt } from './utils/mockApi' 5 | import { languages, getBrowserLanguage, getTranslation } from './i18n' 6 | 7 | function App() { 8 | const [inputPrompt, setInputPrompt] = useState('') 9 | const [optimizedPrompt, setOptimizedPrompt] = useState('') 10 | const [isLoading, setIsLoading] = useState(false) 11 | const [isStreaming, setIsStreaming] = useState(false) 12 | const [streamingContent, setStreamingContent] = useState('') 13 | const [error, setError] = useState('') 14 | const [copied, setCopied] = useState(false) 15 | const [statusMessage, setStatusMessage] = useState('') 16 | const [isDarkMode, setIsDarkMode] = useState(false) 17 | const [language, setLanguage] = useState('en') // 默认英文 18 | const [showLanguageMenu, setShowLanguageMenu] = useState(false) 19 | const turnstileRef = useRef(null) 20 | 21 | // 初始化主题和语言 22 | useEffect(() => { 23 | // 初始化主题 24 | const savedTheme = localStorage.getItem('theme') 25 | const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches 26 | 27 | if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { 28 | setIsDarkMode(true) 29 | document.documentElement.classList.add('dark') 30 | } 31 | 32 | // 初始化语言 33 | const savedLanguage = localStorage.getItem('language') 34 | if (savedLanguage && languages[savedLanguage]) { 35 | setLanguage(savedLanguage) 36 | } else { 37 | // 使用浏览器语言偏好,但默认为英文 38 | const browserLang = getBrowserLanguage() 39 | setLanguage(browserLang) 40 | localStorage.setItem('language', browserLang) 41 | } 42 | }, []) 43 | 44 | // 更新页面标题 45 | useEffect(() => { 46 | document.title = getTranslation(language, 'title') 47 | }, [language]) 48 | 49 | // 点击外部关闭语言菜单 50 | useEffect(() => { 51 | const handleClickOutside = (event) => { 52 | if (showLanguageMenu && !event.target.closest('.language-selector')) { 53 | setShowLanguageMenu(false) 54 | } 55 | } 56 | 57 | document.addEventListener('mousedown', handleClickOutside) 58 | return () => { 59 | document.removeEventListener('mousedown', handleClickOutside) 60 | } 61 | }, [showLanguageMenu]) 62 | 63 | // 切换主题 64 | const toggleTheme = () => { 65 | const newDarkMode = !isDarkMode 66 | setIsDarkMode(newDarkMode) 67 | 68 | if (newDarkMode) { 69 | document.documentElement.classList.add('dark') 70 | localStorage.setItem('theme', 'dark') 71 | } else { 72 | document.documentElement.classList.remove('dark') 73 | localStorage.setItem('theme', 'light') 74 | } 75 | } 76 | 77 | // 切换语言 78 | const changeLanguage = (newLanguage) => { 79 | setLanguage(newLanguage) 80 | localStorage.setItem('language', newLanguage) 81 | setShowLanguageMenu(false) 82 | } 83 | 84 | // 获取翻译文本的辅助函数 85 | const t = (key) => getTranslation(language, key) 86 | 87 | const handleSubmit = async (e) => { 88 | e.preventDefault() 89 | 90 | if (!inputPrompt.trim()) { 91 | setError(t('errorEmptyPrompt')) 92 | return 93 | } 94 | 95 | setIsLoading(true) 96 | setIsStreaming(false) 97 | setError('') 98 | setOptimizedPrompt('') 99 | setStreamingContent('') 100 | setStatusMessage('') 101 | 102 | try { 103 | // 获取Turnstile token (开发环境跳过验证) 104 | let turnstileToken = turnstileRef.current?.getResponse() 105 | if (!turnstileToken && import.meta.env.DEV) { 106 | turnstileToken = 'dev-token' // 开发环境使用假token 107 | } 108 | if (!turnstileToken) { 109 | throw new Error(t('errorVerification')) 110 | } 111 | 112 | if (import.meta.env.DEV) { 113 | // 开发环境使用模拟流式API 114 | await simulateStreamingResponse(inputPrompt) 115 | } else { 116 | // 生产环境使用真实SSE API 117 | await handleSSEResponse(inputPrompt, turnstileToken) 118 | } 119 | 120 | // 重置Turnstile 121 | if (turnstileRef.current) { 122 | turnstileRef.current.reset() 123 | } 124 | } catch (err) { 125 | setError(err.message) 126 | setIsStreaming(false) 127 | } finally { 128 | setIsLoading(false) 129 | } 130 | } 131 | 132 | // 处理SSE响应 133 | const handleSSEResponse = async (prompt, turnstileToken) => { 134 | const response = await fetch('/api/optimize-prompt', { 135 | method: 'POST', 136 | headers: { 137 | 'Content-Type': 'application/json', 138 | }, 139 | body: JSON.stringify({ 140 | prompt: prompt, 141 | turnstileToken, 142 | language: language 143 | }), 144 | }) 145 | 146 | if (!response.ok) { 147 | throw new Error(t('errorRequestFailed')) 148 | } 149 | 150 | const reader = response.body.getReader() 151 | const decoder = new TextDecoder() 152 | let buffer = '' 153 | 154 | setIsLoading(false) 155 | setIsStreaming(true) 156 | 157 | try { 158 | while (true) { 159 | const { done, value } = await reader.read() 160 | 161 | if (done) break 162 | 163 | buffer += decoder.decode(value, { stream: true }) 164 | const lines = buffer.split('\n') 165 | buffer = lines.pop() || '' 166 | 167 | for (const line of lines) { 168 | if (line.startsWith('event: ') && lines[lines.indexOf(line) + 1]?.startsWith('data: ')) { 169 | const event = line.slice(7) 170 | const dataLine = lines[lines.indexOf(line) + 1] 171 | const data = dataLine.slice(6) 172 | 173 | try { 174 | const parsed = JSON.parse(data) 175 | 176 | switch (event) { 177 | case 'status': 178 | setStatusMessage(parsed.message) 179 | break 180 | case 'chunk': 181 | setStreamingContent(parsed.fullContent) 182 | break 183 | case 'complete': 184 | setOptimizedPrompt(parsed.optimizedPrompt) 185 | setStreamingContent('') 186 | setStatusMessage('') 187 | setIsStreaming(false) 188 | break 189 | case 'error': 190 | throw new Error(parsed.error) 191 | } 192 | } catch (e) { 193 | console.error('解析SSE数据失败:', e) 194 | } 195 | } 196 | } 197 | } 198 | } finally { 199 | reader.releaseLock() 200 | } 201 | } 202 | 203 | // 模拟流式响应(开发环境) 204 | const simulateStreamingResponse = async (prompt) => { 205 | setIsLoading(false) 206 | setIsStreaming(true) 207 | 208 | await new Promise(resolve => setTimeout(resolve, 300)) 209 | 210 | // 模拟流式输出 211 | const mockResult = await mockOptimizePrompt(prompt) 212 | const text = mockResult.optimizedPrompt 213 | 214 | for (let i = 0; i <= text.length; i += 3) { 215 | setStreamingContent(text.slice(0, i)) 216 | await new Promise(resolve => setTimeout(resolve, 30)) 217 | } 218 | 219 | setOptimizedPrompt(text) 220 | setStreamingContent('') 221 | setIsStreaming(false) 222 | } 223 | 224 | const copyToClipboard = async () => { 225 | try { 226 | await navigator.clipboard.writeText(optimizedPrompt) 227 | setCopied(true) 228 | setTimeout(() => setCopied(false), 2000) 229 | } catch (err) { 230 | console.error('复制失败:', err) 231 | } 232 | } 233 | 234 | return ( 235 |
236 | {/* Header */} 237 |
238 |
239 |
240 |
241 |
242 | 243 |
244 |

245 | {t('title')} 246 |

247 |
248 |
249 | {/* 语言选择器 */} 250 |
251 | 258 | {showLanguageMenu && ( 259 |
260 | {Object.entries(languages).map(([code, name]) => ( 261 | 270 | ))} 271 |
272 | )} 273 |
274 | {/* 主题切换按钮 */} 275 | 286 |
287 |
288 |
289 |
290 | 291 | {/* Main Content */} 292 |
293 |
294 |

295 | {t('mainTitle')} 296 | {t('promptText')} 297 |

298 |

299 | {t('mainDescription')} 300 |

301 |
302 | 303 |
304 | {/* Input Section */} 305 |
306 | 309 |