├── 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 |
--------------------------------------------------------------------------------
/.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 | [](https://github.com/WonderLand33/prompt-optimizer)
4 | [](https://github.com/WonderLand33/prompt-optimizer)
5 | [](https://github.com/WonderLand33/prompt-optimizer/issues)
6 | [](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 |
344 |
345 |
346 |
347 | {/* Error Message */}
348 | {error && (
349 |
350 |
351 |
{error}
352 |
353 | )}
354 |
355 | {/* Streaming Content */}
356 | {isStreaming && streamingContent && (
357 |
358 |
359 |
362 |
363 |
364 |
365 | {streamingContent}
366 | |
367 |
368 |
369 |
370 | )}
371 |
372 | {/* Output Section */}
373 | {optimizedPrompt && !isStreaming && (
374 |
375 |
376 |
379 |
395 |
396 |
397 |
398 | {optimizedPrompt}
399 |
400 |
401 |
402 | )}
403 |
404 | {/* Tips Section */}
405 |
406 |
407 | 💡 {t('tipsTitle')}
408 |
409 |
410 | {t('tips').map((tip, index) => (
411 | - • {tip}
412 | ))}
413 |
414 |
415 |
416 |
417 | {/* Footer */}
418 |
423 |
424 | )
425 | }
426 |
427 | export default App
--------------------------------------------------------------------------------