├── .cursor └── rules │ ├── bug修复专家.mdc │ ├── code_review.mdc │ ├── md_output.mdc │ ├── pr_review.mdc │ └── 高级项目审计师.mdc ├── .cursorrules ├── .dockerignore ├── .env.example ├── .github └── workflows │ ├── docker.yml │ └── test.yml ├── .gitignore ├── .husky ├── pre-commit └── pre-commit.ps1 ├── .npmrc ├── .pnpmrc ├── .windsurfrules ├── Dockerfile ├── LICENSE ├── README.md ├── README_EN.md ├── api ├── proxy.js ├── stream.js └── vercel-status.js ├── cursor_tips.md ├── dev.md ├── docker-compose.yml ├── docker ├── generate-config.sh └── nginx.conf ├── docs ├── README.md ├── experience.md ├── prd.md ├── project-status.md ├── project-structure.md ├── scratchpad.md ├── technical-development-guide.md ├── vercel.md └── vercel_en.md ├── images ├── contrast.png ├── main.png └── vercel │ ├── import.png │ ├── redeploy.png │ └── setting.png ├── jsconfig.json ├── package.json ├── packages ├── core │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── services │ │ │ ├── history │ │ │ │ ├── errors.ts │ │ │ │ ├── manager.ts │ │ │ │ └── types.ts │ │ │ ├── llm │ │ │ │ ├── errors.ts │ │ │ │ ├── service.ts │ │ │ │ └── types.ts │ │ │ ├── model │ │ │ │ ├── defaults.ts │ │ │ │ ├── manager.ts │ │ │ │ └── types.ts │ │ │ ├── prompt │ │ │ │ ├── errors.ts │ │ │ │ ├── factory.ts │ │ │ │ ├── service.ts │ │ │ │ └── types.ts │ │ │ └── template │ │ │ │ ├── defaults.ts │ │ │ │ ├── errors.ts │ │ │ │ ├── manager.ts │ │ │ │ └── types.ts │ │ ├── types │ │ │ └── global.d.ts │ │ └── utils │ │ │ └── environment.ts │ ├── tests │ │ ├── integration │ │ │ └── llm │ │ │ │ ├── common.test.js │ │ │ │ ├── custom.test.js │ │ │ │ ├── deepseek.test.js │ │ │ │ └── gemini.test.js │ │ ├── llm.test.js │ │ ├── setup.js │ │ └── unit │ │ │ ├── history │ │ │ └── manager.test.ts │ │ │ ├── llm │ │ │ └── service.test.ts │ │ │ ├── model │ │ │ └── manager.test.ts │ │ │ ├── prompt │ │ │ └── service.test.ts │ │ │ └── template │ │ │ └── manager.test.ts │ ├── tsconfig.json │ └── vitest.config.js ├── extension │ ├── chrome.md │ ├── env.d.ts │ ├── index.html │ ├── package.json │ ├── permissions-explanation.md │ ├── postcss.config.js │ ├── privacy-policy.md │ ├── public │ │ ├── _locales │ │ │ ├── en │ │ │ │ └── messages.json │ │ │ └── zh_CN │ │ │ │ └── messages.json │ │ ├── background.js │ │ ├── favicon.ico │ │ ├── icons │ │ │ ├── icon128.png │ │ │ ├── icon16.png │ │ │ └── icon48.png │ │ └── manifest.json │ ├── publish-guide.md │ ├── screenshots.md │ ├── src │ │ ├── App.vue │ │ ├── main.ts │ │ └── style.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── ui │ ├── env.d.ts │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── assets │ │ │ └── logo.jpg │ │ ├── components.d.ts │ │ ├── components │ │ │ ├── ActionButton.vue │ │ │ ├── ContentCard.vue │ │ │ ├── FullscreenDialog.vue │ │ │ ├── HistoryDrawer.vue │ │ │ ├── InputPanel.vue │ │ │ ├── InputWithSelect.vue │ │ │ ├── LanguageSwitch.vue │ │ │ ├── MainLayout.vue │ │ │ ├── MarkdownRenderer.vue │ │ │ ├── Modal.vue │ │ │ ├── ModelManager.vue │ │ │ ├── ModelSelect.vue │ │ │ ├── OptimizePanel.vue │ │ │ ├── OutputPanel.vue │ │ │ ├── Panel.vue │ │ │ ├── PromptPanel.vue │ │ │ ├── TemplateManager.vue │ │ │ ├── TemplateSelect.vue │ │ │ ├── TestPanel.vue │ │ │ ├── ThemeToggleUI.vue │ │ │ └── Toast.vue │ │ ├── composables │ │ │ ├── index.ts │ │ │ ├── useAutoScroll.ts │ │ │ ├── useClipboard.ts │ │ │ ├── useHistoryManager.ts │ │ │ ├── useModals.ts │ │ │ ├── useModelManager.ts │ │ │ ├── useModelSelectors.ts │ │ │ ├── usePromptHistory.ts │ │ │ ├── usePromptOptimizer.ts │ │ │ ├── usePromptTester.ts │ │ │ ├── useServiceInitializer.ts │ │ │ ├── useTemplateManager.ts │ │ │ └── useToast.ts │ │ ├── directives │ │ │ └── clickOutside.ts │ │ ├── i18n │ │ │ ├── README.md │ │ │ └── locales │ │ │ │ ├── en-US.ts │ │ │ │ └── zh-CN.ts │ │ ├── index.ts │ │ ├── plugins │ │ │ └── i18n.ts │ │ ├── styles │ │ │ ├── common.css │ │ │ ├── index.css │ │ │ ├── scrollbar.css │ │ │ └── theme.css │ │ ├── types │ │ │ ├── images.d.ts │ │ │ └── vue-i18n.d.ts │ │ └── utils │ │ │ └── error.ts │ ├── tailwind.config.js │ ├── tests │ │ ├── setup.js │ │ └── unit │ │ │ └── components.test.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── vitest.config.ts └── web │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── config.js │ └── favicon.ico │ ├── src │ ├── App.vue │ └── main.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── vitest.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── vercel.json /.cursor/rules/bug修复专家.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | --- 5 | # Role:Bug修复专家 6 | 7 | ## Background:用户当前正在进行软件开发,遇到了需要解决的bug,为了不引入新的问题,需要一个bug修复专家。 8 | 9 | ## Attention:不要灰心!每一个bug都是提升代码质量的机会,让我们一起把这个问题解决,让你的项目更加完美。 10 | 11 | ## Profile: 12 | - Author: pp 13 | - Version: 2.1 14 | - Language: 中文 15 | - Description: 我是一位经验丰富的Bug修复专家,擅长分析代码、文档,精准定位并修复bug,同时确保不引入新的问题。 16 | 17 | ### Skills: 18 | - 深入理解各种编程语言和框架,能够快速阅读和理解代码逻辑。 19 | - 熟练使用调试工具,能够快速定位bug的根源。 20 | - 具备丰富的bug修复经验,能够针对不同类型的bug提出有效的解决方案。 21 | - 严格遵守代码规范,确保修复后的代码质量和可维护性。 22 | - 具备良好的沟通能力,能够与开发团队有效协作。 23 | 24 | ## Goals: 25 | - 仔细分析用户提供的bug信息,理解bug的现象和影响范围。 26 | - 结合项目代码和文档,定位bug的根本原因。 27 | - 提出针对性的修复方案,并进行代码修改。 28 | - 测试修复后的代码,确保bug被彻底解决。 29 | - 确保修复过程不会引入新的问题,只修改bug相关部分。 30 | 31 | ## Constrains: 32 | - 必须严格按照用户提供的bug信息和项目文档进行分析和修复。 33 | - 必须避免对bug之外的功能进行修改,只专注于修复bug。 34 | - 必须保证修复后的代码符合代码规范,易于阅读和维护。 35 | - 必须对修复后的代码进行充分测试,确保bug被彻底解决。 36 | - 必须在修复过程中保持耐心和细致,避免遗漏任何细节。 37 | 38 | ## Workflow: 39 | 1. 仔细阅读用户提供的bug描述,理解bug的现象和影响范围,如有疑问,及时向用户澄清。 40 | 2. 分析用户提供的代码和文档,定位bug可能出现的代码位置,并尝试复现bug。 41 | 3. 使用调试工具,逐步排查bug的根源,并分析bug产生的原因,形成明确的修复思路。 42 | 4. 针对bug的根本原因,提出修复方案,并在代码中进行修改,确保修复方案的有效性。 43 | 5. 对修复后的代码进行充分测试,包括单元测试、集成测试等,确保bug被彻底解决,并且没有引入新的问题。 44 | 45 | ## OutputFormat: 46 | - 使用markdown格式输出,清晰地展示bug的描述、分析过程、修复方案和最终代码。 47 | - 明确指出bug所在的代码文件和行号,方便用户快速定位。 48 | - 详细描述修复方案的思路,让用户理解修复的原理。 49 | - 提供修复后的代码片段,并使用代码块进行展示。 50 | - 确保输出内容结构清晰,逻辑连贯,易于阅读。 51 | 52 | ## Suggestions: 53 | - 提供给用户详细的bug描述,包括bug的现象、复现步骤和影响范围,以便我快速理解问题。 54 | - 提供当前项目相关的代码和文档,包括bug所在的代码文件和相关模块的文档,以便我进行分析和修复。 55 | - 明确指出bug的优先级和修复时间,以便我合理安排工作。 56 | - 在bug修复完成后,提供测试反馈,以便我进行进一步的调整和优化。 57 | - 积极沟通,及时反馈问题,以便我更好的完成bug修复任务。 58 | 59 | ## Initialization 60 | 作为一名Bug修复专家,我将严格遵守以上规则,使用默认中文与您交流,我会仔细分析您提供的bug信息、代码和文档,并按照工作流逐步进行bug修复。请您提供bug相关的信息吧。 -------------------------------------------------------------------------------- /.cursor/rules/code_review.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | --- 7 | description: 代码审查 8 | globs: 9 | alwaysApply: true 10 | --- 11 | # Role: 代码审查专家 12 | 13 | ## Profile 14 | - language: 中文 15 | - description: 作为一名经验丰富的代码审查专家,具备深入理解代码逻辑、识别潜在问题以及确保代码质量的能力。能够准确把握变更意图,并针对代码的合理性、必要性和潜在风险进行全面评估。 16 | - background: 多年软件开发经验,参与过大型项目的设计与开发,熟悉多种编程语言和开发框架,深入理解软件工程的最佳实践。 17 | - personality: 严谨细致,注重细节,具有批判性思维,同时保持客观公正的态度。善于沟通,能够清晰地表达审查意见,并提出改进建议。 18 | - expertise: 代码质量保证、代码规范、软件架构、安全性、性能优化、Bug 识别、最小变更原则、PR流程优化。 19 | - target_audience: 软件开发人员、项目经理、测试人员、代码提交者、PR审查者。 20 | 21 | ## Skills 22 | 23 | 1. 代码理解与分析 24 | - 代码逻辑分析: 能够快速理解代码的功能、流程和依赖关系。 25 | - 变更意图识别: 准确判断代码变更的目的和预期效果。 26 | - 潜在问题识别: 能够发现代码中潜在的Bug、性能瓶颈和安全漏洞。 27 | - 代码质量评估: 评估代码的可读性、可维护性和可扩展性。 28 | 29 | 2. 代码审查与优化 30 | - 代码规范审查: 检查代码是否符合编码规范和最佳实践。 31 | - 最小变更原则评估: 评估变更是否遵循最小变更原则,避免不必要的修改。 32 | - 代码合理性审查: 评估代码的实现方式是否合理,是否存在更优的方案。 33 | - 代码优化建议: 提出改进代码质量、性能和安全性的建议。 34 | 35 | 3. 报告编写与沟通 36 | - 审查报告编写: 能够撰写清晰、简洁、全面的代码审查报告。 37 | - 沟通协调: 能够有效地与开发人员沟通,解释审查意见,并达成共识。 38 | - 问题跟踪: 能够跟踪问题的解决进度,确保问题得到有效解决。 39 | 40 | 4. 技术知识 41 | - 熟悉多种编程语言: 精通至少一种主流编程语言,例如 Java, Python, C++, JavaScript 等。 42 | - 熟悉常用开发框架: 熟悉常用的开发框架,例如 Spring, React, Angular, Vue 等。 43 | - 熟悉软件设计原则: 深入理解 SOLID 原则、DRY 原则等软件设计原则。 44 | - 熟悉安全编码规范: 熟悉常见的安全漏洞和防御方法,例如 OWASP Top 10。 45 | 46 | ## Rules 47 | 48 | 1. 基本原则: 49 | - 准确性: 对代码进行准确的理解和分析,避免误判和遗漏。 50 | - 客观性: 保持客观公正的态度,避免主观偏见。 51 | - 全面性: 对代码进行全面的审查,包括逻辑、性能、安全等方面。 52 | - 建设性: 提出建设性的改进建议,帮助开发人员提升代码质量。 53 | 54 | 2. 行为准则: 55 | - 及时反馈: 及时提供审查结果,避免延误开发进度。 56 | - 清晰表达: 清晰地表达审查意见,避免产生歧义。 57 | - 尊重他人: 尊重开发人员的劳动成果,避免使用攻击性语言。 58 | - 持续学习: 持续学习新的技术知识,提升审查能力。 59 | 60 | 3. 限制条件: 61 | - 代码量: 审查的代码量可能有限制,需要根据实际情况调整审查范围。 62 | - 上下文: 可能缺乏完整的项目上下文,需要开发人员提供必要的背景信息。 63 | - 时间约束: 审查时间可能有限制,需要在有限的时间内完成审查任务。 64 | - 个人知识: 个人知识储备可能存在局限,需要借助其他资源进行辅助。 65 | 66 | ## Workflows 67 | 68 | - 目标: 识别代码变更意图,评估代码变更的合理性、必要性和潜在风险,并形成一份详细的审查报告。 69 | - 步骤 1: 接收代码变更,包括代码片段或 diff 文件。 **针对每个文件**详细阅读代码变更,理解其功能和逻辑。 70 | - 步骤 2: 分析代码变更意图,明确代码变更的目的和预期效果。与开发人员沟通,确认理解是否正确。 71 | - 步骤 3: 审查代码变更是否合理,包括代码的实现方式、设计模式、可读性和可维护性。检查代码是否符合编码规范和最佳实践。 72 | - 步骤 4: 审查代码变更是否必要,评估变更是否遵循最小变更原则。 确保变更只包含必要的修改,避免不必要的复杂性。 73 | - 步骤 5: 识别代码变更中潜在的 Bug、性能瓶颈和安全漏洞。 使用静态分析工具或手动检查,发现潜在问题。 74 | - 步骤 6: 撰写代码审查报告,包括**针对每个文件的**变更意图、审查结果、问题列表和改进建议。 使用清晰、简洁的语言,描述审查过程和发现的问题。 75 | - 预期结果: 提供一份包含**每个文件**代码变更意图、合理性评估、必要性评估、潜在 Bug 识别以及改进建议的详细审查报告。 76 | 77 | ## Output Format 78 | 79 | 代码审查报告必须遵循以下结构输出: 80 | 81 | ```markdown 82 | # 代码审查报告 83 | 84 | ## 总体评估 85 | - **变更范围**: [简述变更涉及的文件数量和主要模块] 86 | - **整体质量**: [高/中/低] - [简要说明] 87 | - **风险评级**: [高/中/低] - [简要说明] 88 | - **建议措施**: [通过/有条件通过/需要修改后重新审查] 89 | 90 | ## 文件审查详情 91 | 92 | ### 文件名: [文件路径] 93 | 94 | #### 1. 变更意图 95 | [简要描述该文件变更的目的和预期效果] 96 | 97 | #### 2. 代码质量评估 98 | - **可读性**: [高/中/低] - [简要说明] 99 | - **可维护性**: [高/中/低] - [简要说明] 100 | - **复杂度**: [高/中/低] - [简要说明] 101 | - **规范符合度**: [高/中/低] - [简要说明] 102 | 103 | #### 3. 问题清单 104 | 1. [问题1描述] 105 | - **严重性**: [严重/一般/轻微] 106 | - **类型**: [逻辑错误/性能问题/安全隐患/代码风格/其他] 107 | - **建议**: [修复建议] 108 | 109 | 2. [问题2描述] 110 | - **严重性**: [严重/一般/轻微] 111 | - **类型**: [逻辑错误/性能问题/安全隐患/代码风格/其他] 112 | - **建议**: [修复建议] 113 | 114 | #### 4. 优化建议 115 | 1. [建议1] 116 | 2. [建议2] 117 | 118 | ### 文件名: [下一个文件路径] 119 | ... 120 | 121 | ## 总结建议 122 | [对整体代码变更的总结性建议和改进方向] 123 | ``` 124 | 125 | ## Initialization 126 | 作为代码审查专家,你必须遵守上述Rules,按照Workflows执行任务,并使用规定的Output Format输出审查报告。 127 | -------------------------------------------------------------------------------- /.cursor/rules/md_output.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | --- 7 | description: 用于规范输出markdown内容 8 | globs: 9 | --- 10 | Format your response in markdown according to the following requirements: 11 | 12 | - When proposing an edit to a markdown file, first evaluate whether the content will contain code snippets 13 | - If the content contains no code snippets, enclose the entire response in backticks with 'markdown' as the language identifier 14 | - If the content includes code snippets, ensure all code blocks are indented with exactly 2 spaces and specify the correct language for proper rendering 15 | - Only 2-space indentation is allowed for code blocks - level 0 and 4 space indentations are not permitted 16 | - Automatically correct any code block indentation that doesn't follow the 2-space rule 17 | -------------------------------------------------------------------------------- /.cursor/rules/pr_review.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | --- 7 | description: PR审查 8 | globs: 9 | alwaysApply: false 10 | --- 11 | # Role: PR审查专家 12 | 13 | ## Profile 14 | - language: 中文 15 | - description: 作为一名经验丰富的PR审查专家,具备深入理解代码变更、评估PR质量以及确保代码合并质量的专业能力。能够准确把握PR的整体目的,并针对变更的一致性、必要性和潜在风险进行全面评估。 16 | - background: 多年软件开发经验,参与过大型项目的设计与开发,熟悉Git工作流和PR流程。熟练掌握版本控制系统(Git)、持续集成和代码评审流程。对PR规模控制、提交历史管理和分支策略有深入理解。 17 | - personality: 严谨细致,注重细节,具有全局视角,能够平衡技术细节和整体目标。善于沟通,能够清晰地表达审查意见,并提出建设性改进建议。 18 | - expertise: PR流程优化、提交历史管理、变更范围控制、测试覆盖评估、文档完善度评估、代码集成风险评估。 19 | - target_audience: 软件开发人员、项目管理人员、PR提交者、代码评审者。 20 | 21 | ## Skills 22 | 23 | 1. PR整体评估 24 | - 变更一致性分析: 评估PR中所有变更是否服务于同一目标,避免混杂无关变更。 25 | - 提交历史审查: 评估提交历史的清晰度、合理性和规范性,确保每个提交有明确目的。 26 | - PR规模控制: 评估PR的规模是否合理,提供拆分过大PR的建议。 27 | - 风险评估: 评估PR可能带来的集成风险、兼容性问题和性能影响。 28 | 29 | 2. 测试与文档评估 30 | - 测试覆盖审查: 评估PR中的测试覆盖情况,包括单元测试、集成测试和手动测试。 31 | - 测试质量评估: 评估测试的有效性和全面性,是否覆盖了各种边界情况和异常场景。 32 | - 文档完善评估: 评估代码注释、API文档、使用说明等是否完整和准确。 33 | - 文档一致性: 确保文档与代码变更保持一致,无过时或错误信息。 34 | 35 | 3. 技术评估 36 | - 代码质量评估: 评估代码的可读性、可维护性和可扩展性。 37 | - 变更合理性: 评估代码实现是否合理,是否符合项目的架构和设计原则。 38 | - 安全性审查: 识别PR中可能存在的安全漏洞和问题。 39 | - 性能影响: 评估PR对系统性能的潜在影响。 40 | 41 | ## Rules 42 | 43 | 1. PR审查基本原则: 44 | - 整体性: 首先评估PR的整体目标和变更范围,确保变更集中且一致。 45 | - 分阶段审查: 对大型PR,先进行整体评估,再进行详细代码审查。 46 | - 关注核心变更: 优先审查核心变更文件,确保主要功能正确实现。 47 | - 全面覆盖: 确保审查覆盖代码质量、测试覆盖、文档完善等各个方面。 48 | 49 | 2. PR评审行为准则: 50 | - 建设性反馈: 提供具体、可操作的改进建议,而不仅仅指出问题。 51 | - 明确合并建议: 给出明确的合并意见:可直接合并、需修改后合并或不建议合并。 52 | - 流程改进: 不仅评估代码本身,也评估和改进PR流程。 53 | - 保持沟通: 与PR提交者保持良好沟通,解释审查意见并达成共识。 54 | 55 | ## Workflow 56 | 57 | - 目标: 全面评估PR的质量、合理性和风险,提供详细的审查报告和改进建议。 58 | - 步骤 1: 获取PR的基本信息,包括PR标题、描述、关联的需求或缺陷、涉及的文件和变更范围。 59 | - 步骤 2: 评估PR的整体性和一致性,确保所有变更共同服务于同一目标,没有无关变更。 60 | - 步骤 3: 审查提交历史,评估提交的合理性、清晰度和规范性。检查是否存在过大或过小的提交。 61 | - 步骤 4: 对涉及的文件进行分类,确定核心变更文件和次要变更文件,优先审查核心变更。 62 | - 步骤 5: 进行具体文件审查,包括代码质量、变更合理性、潜在问题等方面的评估。 63 | - 步骤 6: 评估PR的测试覆盖情况,包括单元测试、集成测试和手动测试的覆盖率和质量。 64 | - 步骤 7: 评估PR的文档完善程度,包括代码注释、接口文档、使用说明等。 65 | - 步骤 8: 综合评估PR的质量和风险,形成PR审查报告。提供明确的合并建议和改进建议。 66 | - 预期结果: 提供一份包含PR整体评估、文件详细审查、测试覆盖评估、文档完善评估以及合并建议的综合PR审查报告。 67 | 68 | ## Output Format 69 | 70 | ```markdown 71 | # PR审查报告 72 | 73 | ## PR基本信息 74 | - **PR标题**: [PR标题] 75 | - **PR编号**: [PR编号] 76 | - **提交者**: [提交者用户名] 77 | - **关联需求/缺陷**: [需求/缺陷编号及描述] 78 | - **变更文件数**: [变更文件总数] 79 | 80 | ## 整体评估 81 | - **变更一致性**: [高/中/低] - [简要说明变更是否一致服务于同一目标] 82 | - **PR规模适当性**: [适当/过大/过小] - [简要说明PR规模是否合理] 83 | - **提交历史质量**: [高/中/低] - [简要说明提交历史是否清晰合理] 84 | - **测试覆盖情况**: [充分/部分/不足] - [简要说明测试覆盖情况] 85 | - **文档完善程度**: [充分/部分/不足] - [简要说明文档完善情况] 86 | - **整体质量**: [高/中/低] - [简要说明] 87 | - **合并建议**: [可直接合并/需修改后合并/不建议合并] - [简要说明] 88 | 89 | ## 核心变更文件审查 90 | 91 | ### 文件名: [文件路径] 92 | 93 | #### 1. 变更概述 94 | [简要描述变更内容和目的] 95 | 96 | #### 2. 技术评估 97 | - **实现方式**: [合理/部分合理/不合理] - [简要说明] 98 | - **代码质量**: [高/中/低] - [简要说明] 99 | - **与需求匹配度**: [高/中/低] - [简要说明] 100 | - **潜在风险**: [无/低/中/高] - [简要说明] 101 | 102 | #### 3. 问题清单 103 | 1. [问题1描述] 104 | - **严重性**: [严重/一般/轻微] 105 | - **类型**: [逻辑错误/性能问题/安全隐患/代码风格/其他] 106 | - **建议**: [修复建议] 107 | 108 | #### 4. 优化建议 109 | 1. [建议1] 110 | 2. [建议2] 111 | 112 | ### 文件名: [下一个核心文件路径] 113 | ... 114 | 115 | ## 测试评估 116 | - **单元测试覆盖率**: [百分比或定性评估] 117 | - **集成测试覆盖率**: [百分比或定性评估] 118 | - **测试质量**: [高/中/低] - [简要说明] 119 | - **测试改进建议**: [测试改进建议列表] 120 | 121 | ## 文档评估 122 | - **代码注释**: [充分/部分/不足] - [简要说明] 123 | - **接口文档**: [充分/部分/不足] - [简要说明] 124 | - **使用说明**: [充分/部分/不足/不适用] - [简要说明] 125 | - **文档改进建议**: [文档改进建议列表] 126 | 127 | ## PR流程建议 128 | - [对PR流程的改进建议,如PR拆分建议、提交历史优化建议等] 129 | 130 | ## 总结意见 131 | [对PR的总结性评价和建议] 132 | ``` 133 | 134 | ## Initialization 135 | 作为PR审查专家,你必须遵守上述Rules,按照Workflow执行任务,并使用规定的Output Format输出审查报告。针对每个PR审查请求,提供全面、专业和建设性的评估。 -------------------------------------------------------------------------------- /.cursor/rules/高级项目审计师.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 用于项目进度审计 3 | globs: 4 | --- 5 | # Role:高级项目审计师 6 | 7 | ## Background:项目进度文档是项目管理的重要组成部分,它记录了项目的进展情况、时间安排和功能实现等关键信息。然而,由于各种原因,项目进度文档中可能存在虚假项,如不真实的时间估算、未实现的功能等。这些虚假项会严重影响项目决策,导致项目延期、成本超支甚至失败。因此,对项目进度文档进行严格的审计和复核至关重要。 8 | 9 | ## Attention:你的任务至关重要,项目组需要你专业的技能和严谨的态度,找出进度文档中的虚假信息,为项目决策提供真实可靠的依据。请务必保持专注,细致入微,不要放过任何可疑之处。 10 | 11 | ## Profile: 12 | - Author: pp 13 | - Version: 2.1 14 | - Language: 中文 15 | - Description: 我是一名经验丰富的项目审计师,擅长识别项目文档中的虚假信息,并能通过代码、其他文档等多种渠道进行交叉验证。我拥有严谨的工作态度和专业的审计技能,致力于确保项目信息的真实性和可靠性。 16 | 17 | ### Skills: 18 | - 具备深入理解项目管理理论和实践的能力,能快速分析项目进度文档。 19 | - 熟悉各种项目管理工具和方法,能高效地进行数据分析和比对。 20 | - 掌握多种编程语言和技术,能快速阅读和理解项目代码。 21 | - 具备出色的沟通和协调能力,能与其他团队成员有效合作。 22 | - 具有高度的责任心和职业道德,能独立完成审计任务。 23 | 24 | ## Goals: 25 | - 仔细审查项目进度文档,识别所有虚假项。 26 | - 基于项目代码和其他文档,对进度文档中的信息进行交叉验证。 27 | - 记录所有发现的虚假项,并提供详细的证据和分析。 28 | - 输出一份详细的审计报告,指出存在的问题和改进建议。 29 | - 确保项目进度文档的真实性和可靠性。 30 | 31 | ## Constrains: 32 | - 必须严格按照审计标准和流程进行操作,确保审计过程的公正性和客观性。 33 | - 必须对所有审计发现进行详细记录,并提供充分的证据支持。 34 | - 必须保守项目机密,不得泄露任何敏感信息。 35 | - 必须保持独立思考,不被其他因素干扰审计判断。 36 | - 必须按时完成审计任务,并提交高质量的审计报告。 37 | 38 | ## Workflow: 39 | 1. 仔细阅读项目进度文档,标注所有可疑项,包括时间、功能、进度等。 40 | 2. 查阅项目代码,比对进度文档中描述的功能是否已实现,时间是否合理。 41 | 3. 查阅其他项目文档,如需求文档、设计文档等,验证进度文档信息的准确性。 42 | 4. 对比不同来源的信息,找出不一致的地方,记录并分析产生差异的原因。 43 | 5. 撰写详细的审计报告,列出所有虚假项,并提出修改建议。 44 | 45 | ## OutputFormat: 46 | - 审计报告应包含以下部分: 47 | - 项目概述:简要描述项目背景和目标。 48 | - 审计范围:明确本次审计的范围和重点。 49 | - 审计方法:详细说明本次审计采用的方法和工具。 50 | - 审计发现:列出所有发现的虚假项,并提供详细的证据和分析。 51 | - 审计结论:总结本次审计的结论,指出项目进度文档存在的问题。 52 | - 审计建议:提出改进项目进度文档的建议。 53 | - 附件:提供相关的审计证据。 54 | - 审计报告应使用清晰、简洁的语言,确保所有读者都能理解。 55 | - 审计报告应采用Markdown格式,并使用代码块展示代码示例。 56 | 57 | ## Suggestions: 58 | - 建议一:在审计过程中,要保持批判性思维,不轻易相信任何信息,进行多方验证。 59 | - 建议二:在比对代码时,要仔细阅读代码逻辑,确保代码实现的功能与文档描述一致。 60 | - 建议三:在查阅其他文档时,要注意文档的版本和时间,确保使用最新版本的文档。 61 | - 建议四:在记录虚假项时,要详细描述虚假项的具体内容和产生的原因,提供足够的信息。 62 | - 建议五:在撰写审计报告时,要使用清晰、简洁的语言,确保所有读者都能理解报告内容。 63 | 64 | ## Initialization 65 | 作为一名高级项目审计师,我必须严格遵守审计规则。我会使用默认的中文与你交流,你好,我是你的专属审计师。我将严格按照工作流执行审计工作,请提供项目进度文档、项目代码和其他相关文档。 66 | -------------------------------------------------------------------------------- /.cursorrules: -------------------------------------------------------------------------------- 1 | # AI编程规则指南 2 | 3 | ## 1. 开发环境规范 4 | 5 | ### 1.1 系统环境 6 | - 当前为Windows系统环境 7 | - 使用Windows命令行语法 8 | - 注意路径分隔符使用 `\` 而非 `/` 9 | 10 | ### 1.2 测试规范 11 | - 每次代码修改后必须执行 `npm run test` 12 | - 确保所有测试用例通过 13 | - 新功能必须包含对应的测试用例 14 | - 测试覆盖: 15 | - 单元测试 16 | - 集成测试 17 | - 异常场景测试 18 | 19 | ## 2. 文档管理规范 20 | 21 | ### 2.1 经验文档管理 22 | - 位置:`experience.md` 23 | - 记录内容: 24 | - 复用组件信息 25 | - 依赖库版本 26 | - 模型配置信息 27 | - Bug修复经验 28 | - 最佳实践总结 29 | - 分类存储: 30 | - 架构设计 31 | - 错误处理 32 | - 测试规范 33 | - Vue开发 34 | - 工具配置 35 | - 重构经验 36 | 37 | ### 2.2 草稿本使用规范 38 | 位置:`scratchpad.md` 39 | 40 | #### 任务记录格式 41 | ```markdown 42 | ## 任务:[任务名称] - [日期] 43 | ### 目标 44 | [任务目标描述] 45 | 46 | ### 计划步骤 47 | [ ] 1. [具体步骤] 48 | - 预期结果: 49 | - 风险评估: 50 | [x] 2. [已完成步骤] 51 | - 完成时间: 52 | - 实际结果: 53 | 54 | ### 问题记录 55 | 1. [问题描述] 56 | - 原因: 57 | - 解决方案: 58 | - 经验总结: 59 | 60 | ### 里程碑 61 | - [x] [已完成里程碑] 62 | - [ ] [待完成里程碑] 63 | ``` 64 | 65 | ## 3. 代码规范 66 | 67 | ### 3.1 API集成规范 68 | - 业务逻辑与API配置解耦 69 | - 统一使用OpenAI兼容格式 70 | - 独立管理提示词模板 71 | - 敏感信息使用环境变量 72 | 73 | ### 3.2 错误处理规范 74 | ```typescript 75 | try { 76 | await apiCall(); 77 | } catch (err) { 78 | console.error("[错误类型]", err.context); 79 | throw new Error("友好的错误提示"); 80 | } 81 | ``` 82 | 83 | ### 3.3 类型定义规范 84 | ```typescript 85 | interface ModelConfig { 86 | name: string; // 必填 87 | baseURL: string; // 必填 88 | models: string[]; // 必填 89 | } 90 | ``` 91 | 92 | ## 4. 工作流程规范 93 | 94 | ### 4.1 新功能开发流程 95 | 1. 需求文档分析 96 | 2. 技术方案设计 97 | 3. 编写测试用例 98 | 4. 功能实现 99 | 5. 测试验证 100 | 6. 文档更新 101 | 102 | ### 4.2 Bug修复流程 103 | 1. 问题复现与分析 104 | 2. 制定修复方案 105 | 3. 编写测试用例 106 | 4. 实施修复 107 | 5. 验证修复效果 108 | 6. 更新经验文档 109 | 110 | ### 4.3 代码审查要点 111 | 1. 类型安全 112 | 2. 错误处理 113 | 3. 测试覆盖 114 | 4. 代码风格 115 | 5. 性能影响 116 | 117 | ## 5. 项目文档结构 118 | 必读文档: 119 | - `fileNames.md`:项目地图 120 | - `docs/prd.md`:产品需求 121 | - `docs/app-flow.md`:应用流程 122 | - `docs/tech-stack.md`:技术栈 123 | - `docs/file-structure.md`:文件结构 124 | - `docs/frontend-guidelines.md`:前端指南 125 | 126 | ## 6. 会话管理规范 127 | 128 | ### 6.1 开始阶段 129 | 1. 检查任务上下文 130 | 2. 确认开发环境 131 | 3. 制定实施计划 132 | 133 | ### 6.2 执行阶段 134 | 1. 步骤确认 135 | 2. 代码生成 136 | 3. 测试验证 137 | 4. 文档更新 138 | 139 | ### 6.3 结束阶段 140 | 1. 总结完成内容 141 | 2. 记录遇到的问题 142 | 3. 更新经验文档 143 | 4. 规划下次任务 144 | 145 | ## 7. 上下文管理 146 | 1. 聚焦关键信息 147 | 2. 避免无关操作 148 | 3. 保持响应精确 149 | 4. 复用已有方案 150 | 5. 及时同步文档 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # 依赖目录 2 | node_modules 3 | **/node_modules 4 | 5 | # 构建输出 6 | dist 7 | **/dist 8 | build 9 | **/build 10 | 11 | # 开发工具配置 12 | .git 13 | .gitignore 14 | .idea 15 | .vscode 16 | *.sublime-* 17 | *.swp 18 | *.swo 19 | 20 | # 环境文件 21 | .env 22 | .env.* 23 | !.env.example 24 | 25 | # 日志文件 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | pnpm-debug.log* 30 | *.log 31 | 32 | # 测试覆盖率 33 | coverage 34 | **/coverage 35 | 36 | # 临时文件 37 | .DS_Store 38 | Thumbs.db 39 | *.tmp 40 | *.temp 41 | 42 | # 文档和其他 43 | *.md 44 | LICENSE 45 | docs 46 | tests 47 | **/__tests__ 48 | **/*.test.* 49 | **/*.spec.* 50 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # API密钥配置示例 2 | # 复制此文件到 .env.local 并填入你的API密钥 3 | 4 | # OpenAI API配置 5 | VITE_OPENAI_API_KEY=your_openai_api_key 6 | 7 | # Gemini API配置 8 | VITE_GEMINI_API_KEY=your_gemini_api_key 9 | 10 | # DeepSeek API配置 11 | VITE_DEEPSEEK_API_KEY=your_deepseek_api_key 12 | 13 | # SiliconFlow API配置 14 | VITE_SILICONFLOW_API_KEY=your_siliconflow_api_key 15 | 16 | # 自定义API配置(可选) 17 | VITE_CUSTOM_API_KEY=your_custom_api_key 18 | VITE_CUSTOM_API_BASE_URL=your_custom_api_base_url 19 | VITE_CUSTOM_API_MODEL=your_custom_model_name -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build and Push 2 | 3 | on: 4 | push: 5 | branches: [ main, master ] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | 10 | env: 11 | REGISTRY: docker.io 12 | IMAGE_NAME: linshen/prompt-optimizer 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | 21 | steps: 22 | - name: 检出代码 23 | uses: actions/checkout@v4 24 | 25 | - name: 安装 pnpm 26 | uses: pnpm/action-setup@v2 27 | with: 28 | version: 10.5.2 29 | run_install: false 30 | 31 | - name: 设置 Node.js 环境 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: '22' 35 | cache: 'pnpm' 36 | 37 | - name: 安装依赖 38 | run: pnpm install 39 | 40 | - name: 运行构建 41 | run: pnpm build 42 | 43 | - name: 运行测试 44 | run: pnpm test 45 | 46 | - name: 获取package.json版本号 47 | id: version 48 | run: | 49 | VERSION=$(grep -m1 '"version":' package.json | cut -d'"' -f4) 50 | echo "version=$VERSION" >> $GITHUB_OUTPUT 51 | echo "Version from package.json: $VERSION" 52 | 53 | - name: 登录到Docker Hub 54 | uses: docker/login-action@v3 55 | with: 56 | username: ${{ secrets.DOCKERHUB_USERNAME }} 57 | password: ${{ secrets.DOCKERHUB_TOKEN }} 58 | 59 | - name: Set up QEMU 60 | uses: docker/setup-qemu-action@v3 61 | 62 | - name: 设置Docker Buildx 63 | uses: docker/setup-buildx-action@v3 64 | 65 | - name: 构建并推送Docker镜像 66 | uses: docker/build-push-action@v6 67 | with: 68 | context: . 69 | platforms: linux/amd64,linux/arm64 70 | push: ${{ github.event_name != 'pull_request' }} 71 | tags: | 72 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }} 73 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest 74 | cache-from: type=gha 75 | cache-to: type=gha,mode=max 76 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ main, master ] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | pull_request: 10 | branches: [ main, master ] 11 | paths-ignore: 12 | - '**.md' 13 | - 'docs/**' 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: 检出代码 21 | uses: actions/checkout@v4 22 | 23 | - name: 安装 pnpm 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: 10.5.2 27 | run_install: false 28 | 29 | - name: 设置 Node.js 环境 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: '22' 33 | cache: 'pnpm' 34 | 35 | - name: 安装依赖 36 | run: pnpm install 37 | 38 | - name: 运行构建 39 | run: pnpm build 40 | 41 | - name: 运行测试 42 | run: pnpm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dist 2 | packages/extension/*.zip 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | pnpm-debug.log* 11 | lerna-debug.log* 12 | 13 | node_modules 14 | dist 15 | dist-ssr 16 | *.local 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | 29 | # Environment files 30 | .env.local 31 | .env.*.local 32 | .env.development.local 33 | .env.test.local 34 | .env.production.local 35 | 36 | .vscode 37 | .vercel 38 | *.code-workspace 39 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # 检查是否存在package-lock.json或yarn.lock文件 5 | if [ -f "package-lock.json" ]; then 6 | echo "错误: 检测到package-lock.json文件。" 7 | echo "本项目强制使用pnpm作为包管理器,请删除package-lock.json并使用pnpm install安装依赖。" 8 | exit 1 9 | fi 10 | 11 | if [ -f "yarn.lock" ]; then 12 | echo "错误: 检测到yarn.lock文件。" 13 | echo "本项目强制使用pnpm作为包管理器,请删除yarn.lock并使用pnpm install安装依赖。" 14 | exit 1 15 | fi 16 | 17 | # 确保pnpm-lock.yaml存在 18 | if [ ! -f "pnpm-lock.yaml" ]; then 19 | echo "警告: 未检测到pnpm-lock.yaml文件。" 20 | echo "请确保使用pnpm install安装依赖。" 21 | fi -------------------------------------------------------------------------------- /.husky/pre-commit.ps1: -------------------------------------------------------------------------------- 1 | # PowerShell版本的pre-commit钩子 2 | 3 | # 检查是否存在package-lock.json或yarn.lock文件 4 | if (Test-Path "package-lock.json") { 5 | Write-Host "错误: 检测到package-lock.json文件。" -ForegroundColor Red 6 | Write-Host "本项目强制使用pnpm作为包管理器,请删除package-lock.json并使用pnpm install安装依赖。" -ForegroundColor Red 7 | exit 1 8 | } 9 | 10 | if (Test-Path "yarn.lock") { 11 | Write-Host "错误: 检测到yarn.lock文件。" -ForegroundColor Red 12 | Write-Host "本项目强制使用pnpm作为包管理器,请删除yarn.lock并使用pnpm install安装依赖。" -ForegroundColor Red 13 | exit 1 14 | } 15 | 16 | # 确保pnpm-lock.yaml存在 17 | if (-not (Test-Path "pnpm-lock.yaml")) { 18 | Write-Host "警告: 未检测到pnpm-lock.yaml文件。" -ForegroundColor Yellow 19 | Write-Host "请确保使用pnpm install安装依赖。" -ForegroundColor Yellow 20 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | engine-strict=true 3 | auto-install-peers=true 4 | strict-peer-dependencies=false 5 | enable-pre-post-scripts=true 6 | public-hoist-pattern[]=*esbuild* 7 | public-hoist-pattern[]=*vue-demi* 8 | -------------------------------------------------------------------------------- /.pnpmrc: -------------------------------------------------------------------------------- 1 | save-workspace-protocol=false 2 | save-prefix='~' 3 | strict-peer-dependencies=false 4 | auto-install-peers=true 5 | resolution-mode=highest -------------------------------------------------------------------------------- /.windsurfrules: -------------------------------------------------------------------------------- 1 | # 环境说明 2 | 当前是windows系统,注意使用windows命令行而非linux命令行。 3 | 4 | # 使用草稿本 5 | 请读取scratchpad.md文件。 6 | 你应该使用 `scratchpad.md` 文件作为草稿本来组织你的想法。特别是当你收到新任务时,你应该首先查看草稿本的内容,必要时清除旧的不同任务,先解释任务内容,然后规划完成任务所需的步骤。你可以使用待办标记来指示进度,例如: 7 | [X] 任务1 8 | [ ] 任务2 9 | 当你完成一个子任务时,也要在草稿本中更新任务进度。 10 | 特别是当你完成一个里程碑时,使用草稿本来反思和规划将有助于提高你的任务完成深度。 11 | 目标是帮助你同时掌握任务的全局视图和进度。在规划下一步时始终参考草稿本。 12 | 13 | # 使用经验 14 | 请读取experience.md文件。 15 | 在与用户交互过程中,如果你发现项目中有任何可重用的内容(例如:库的版本、模型名称),特别是关于你犯的错误的修复或收到的纠正,你应该在 `experience.md` 文件做记录,以避免再次犯同样的错误。 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-slim AS base 2 | ENV PNPM_HOME="/pnpm" 3 | ENV PATH="$PNPM_HOME:$PATH" 4 | RUN npm install -g corepack@latest && corepack enable 5 | 6 | FROM base AS build 7 | COPY . /app 8 | WORKDIR /app 9 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile 10 | RUN pnpm run build 11 | 12 | FROM nginx:stable-alpine 13 | COPY docker/nginx.conf /etc/nginx/conf.d/default.conf 14 | COPY --from=build /app/packages/web/dist /usr/share/nginx/html 15 | # 复制启动脚本 16 | COPY docker/generate-config.sh /docker-entrypoint.d/40-generate-config.sh 17 | RUN chmod +x /docker-entrypoint.d/40-generate-config.sh 18 | 19 | EXPOSE 80 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 linshenkx 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. -------------------------------------------------------------------------------- /api/proxy.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'edge' 3 | }; 4 | 5 | export default async function handler(req) { 6 | // 处理CORS预检请求 7 | if (req.method === 'OPTIONS') { 8 | return new Response(null, { 9 | status: 200, 10 | headers: { 11 | 'Access-Control-Allow-Origin': '*', 12 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 13 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-KEY', 14 | 'Access-Control-Max-Age': '86400', 15 | }, 16 | }); 17 | } 18 | 19 | try { 20 | // 解析请求数据 21 | const { searchParams } = new URL(req.url); 22 | const targetUrl = searchParams.get('targetUrl'); 23 | 24 | if (!targetUrl) { 25 | return new Response(JSON.stringify({ error: '缺少目标URL参数' }), { 26 | status: 400, 27 | headers: { 'Content-Type': 'application/json' }, 28 | }); 29 | } 30 | 31 | // 确保targetUrl是有效的URL 32 | let validTargetUrl; 33 | try { 34 | validTargetUrl = new URL(decodeURIComponent(targetUrl)).toString(); 35 | console.log('目标URL:', validTargetUrl); 36 | } catch (error) { 37 | return new Response(JSON.stringify({ error: `无效的目标URL: ${error.message}` }), { 38 | status: 400, 39 | headers: { 'Content-Type': 'application/json' }, 40 | }); 41 | } 42 | 43 | // 准备请求头 44 | const headers = new Headers(); 45 | req.headers.forEach((value, key) => { 46 | // 排除一些特定的头,这些头可能会导致问题 47 | if (!['host', 'connection', 'content-length'].includes(key.toLowerCase())) { 48 | headers.set(key, value); 49 | } 50 | }); 51 | 52 | // 获取请求体 53 | let body = null; 54 | if (req.method !== 'GET' && req.method !== 'HEAD') { 55 | body = await req.text(); 56 | } 57 | 58 | // 发送请求到目标URL 59 | const fetchResponse = await fetch(validTargetUrl, { 60 | method: req.method, 61 | headers, 62 | body, 63 | }); 64 | 65 | // 读取响应数据 66 | const data = await fetchResponse.text(); 67 | 68 | // 创建响应头 69 | const responseHeaders = new Headers(); 70 | fetchResponse.headers.forEach((value, key) => { 71 | responseHeaders.set(key, value); 72 | }); 73 | 74 | // 设置CORS头 75 | responseHeaders.set('Access-Control-Allow-Origin', '*'); 76 | responseHeaders.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 77 | responseHeaders.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-KEY'); 78 | 79 | // 返回响应 80 | return new Response(data, { 81 | status: fetchResponse.status, 82 | statusText: fetchResponse.statusText, 83 | headers: responseHeaders, 84 | }); 85 | } catch (error) { 86 | console.error('代理请求失败:', error); 87 | return new Response(JSON.stringify({ error: `代理请求失败: ${error.message}` }), { 88 | status: 500, 89 | headers: { 90 | 'Content-Type': 'application/json', 91 | 'Access-Control-Allow-Origin': '*', 92 | }, 93 | }); 94 | } 95 | } -------------------------------------------------------------------------------- /api/stream.js: -------------------------------------------------------------------------------- 1 | // api/stream.js 2 | export const config = { 3 | runtime: 'edge' 4 | }; 5 | 6 | export default async function handler(req) { 7 | console.log('流式代理请求开始处理:', new Date().toISOString()); 8 | 9 | // 处理CORS预检请求 10 | if (req.method === 'OPTIONS') { 11 | console.log('处理CORS预检请求'); 12 | return new Response(null, { 13 | status: 200, 14 | headers: { 15 | 'Access-Control-Allow-Origin': '*', 16 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 17 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-KEY', 18 | 'Access-Control-Max-Age': '86400', 19 | }, 20 | }); 21 | } 22 | 23 | try { 24 | // 解析请求数据 25 | const { searchParams } = new URL(req.url); 26 | const targetUrl = searchParams.get('targetUrl'); 27 | 28 | if (!targetUrl) { 29 | console.error('缺少目标URL参数'); 30 | return new Response(JSON.stringify({ error: '缺少目标URL参数' }), { 31 | status: 400, 32 | headers: { 'Content-Type': 'application/json' }, 33 | }); 34 | } 35 | 36 | // 确保targetUrl是有效的URL 37 | let validTargetUrl; 38 | try { 39 | validTargetUrl = new URL(decodeURIComponent(targetUrl)).toString(); 40 | console.log('目标URL:', validTargetUrl); 41 | } catch (error) { 42 | console.error('无效的目标URL:', error); 43 | return new Response(JSON.stringify({ error: `无效的目标URL: ${error.message}` }), { 44 | status: 400, 45 | headers: { 'Content-Type': 'application/json' }, 46 | }); 47 | } 48 | 49 | // 准备请求头 50 | const headers = new Headers(); 51 | req.headers.forEach((value, key) => { 52 | // 排除一些特定的头,这些头可能会导致问题 53 | if (!['host', 'connection', 'content-length'].includes(key.toLowerCase())) { 54 | headers.set(key, value); 55 | } 56 | }); 57 | console.log('请求方法:', req.method); 58 | console.log('请求头数量:', [...headers.keys()].length); 59 | 60 | // 获取请求体 61 | let body = null; 62 | if (req.method !== 'GET' && req.method !== 'HEAD') { 63 | body = await req.text(); 64 | console.log('请求体长度:', body?.length || 0); 65 | } 66 | 67 | console.log('开始向目标URL发送请求:', new Date().toISOString()); 68 | // 发送请求到目标URL 69 | const fetchResponse = await fetch(validTargetUrl, { 70 | method: req.method, 71 | headers, 72 | body, 73 | duplex: 'half', // 支持流式请求 74 | }); 75 | console.log('收到目标URL响应:', new Date().toISOString(), '状态码:', fetchResponse.status); 76 | 77 | // 创建响应头 78 | const responseHeaders = new Headers(); 79 | fetchResponse.headers.forEach((value, key) => { 80 | responseHeaders.set(key, value); 81 | }); 82 | 83 | // 设置CORS头 84 | responseHeaders.set('Access-Control-Allow-Origin', '*'); 85 | responseHeaders.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 86 | responseHeaders.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-KEY'); 87 | 88 | // 检查是否是SSE流 89 | const contentType = fetchResponse.headers.get('content-type'); 90 | const isEventStream = contentType?.includes('text/event-stream'); 91 | console.log('响应内容类型:', contentType, '是否为SSE流:', isEventStream); 92 | 93 | if (isEventStream) { 94 | responseHeaders.set('Content-Type', 'text/event-stream'); 95 | responseHeaders.set('Cache-Control', 'no-cache'); 96 | responseHeaders.set('Connection', 'keep-alive'); 97 | // 确保不缓冲数据 98 | responseHeaders.set('X-Accel-Buffering', 'no'); 99 | } 100 | 101 | // 创建并返回流式响应,使用TransformStream确保数据立即传输 102 | const { readable, writable } = new TransformStream(); 103 | console.log('创建TransformStream完成'); 104 | 105 | // 启动数据传输过程 106 | (async () => { 107 | const writer = writable.getWriter(); 108 | const reader = fetchResponse.body.getReader(); 109 | 110 | try { 111 | console.log('开始流式传输数据:', new Date().toISOString()); 112 | let chunkCount = 0; 113 | let totalBytes = 0; 114 | 115 | while (true) { 116 | const { done, value } = await reader.read(); 117 | if (done) { 118 | console.log('流式传输完成:', new Date().toISOString()); 119 | console.log(`总共传输 ${chunkCount} 个数据块,${totalBytes} 字节`); 120 | await writer.close(); 121 | break; 122 | } 123 | 124 | // 立即写入数据并刷新 125 | await writer.write(value); 126 | chunkCount++; 127 | totalBytes += value.length; 128 | 129 | if (chunkCount % 10 === 0) { 130 | console.log(`已传输 ${chunkCount} 个数据块,${totalBytes} 字节`); 131 | } 132 | } 133 | } catch (error) { 134 | console.error('流式传输错误:', error); 135 | writer.abort(error); 136 | } 137 | })(); 138 | 139 | console.log('返回流式响应'); 140 | return new Response(readable, { 141 | status: fetchResponse.status, 142 | statusText: fetchResponse.statusText, 143 | headers: responseHeaders, 144 | }); 145 | } catch (error) { 146 | console.error('流式代理请求失败:', error); 147 | return new Response(JSON.stringify({ error: `流式代理请求失败: ${error.message}` }), { 148 | status: 500, 149 | headers: { 150 | 'Content-Type': 'application/json', 151 | 'Access-Control-Allow-Origin': '*', 152 | }, 153 | }); 154 | } 155 | } -------------------------------------------------------------------------------- /api/vercel-status.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'edge' 3 | }; 4 | 5 | export default async function handler(request) { 6 | return new Response( 7 | JSON.stringify({ 8 | status: 'available', 9 | environment: 'vercel', 10 | proxySupport: true, 11 | version: '1.0.0' 12 | }), 13 | { 14 | status: 200, 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | 'Access-Control-Allow-Origin': '*', 18 | 'Access-Control-Allow-Methods': 'GET, OPTIONS', 19 | 'Access-Control-Allow-Headers': 'Content-Type' 20 | } 21 | } 22 | ); 23 | } -------------------------------------------------------------------------------- /cursor_tips.md: -------------------------------------------------------------------------------- 1 | # AI辅助开发指南 2 | 3 | **1. 简介:** 4 | * 强调采用结构化方法进行AI辅助开发的重要性 5 | * 说明这些指南旨在帮助开发者高效利用AI工具,最小化token使用,减少错误 6 | 7 | **2. 项目设置:** 8 | * 创建项目地图(`fileNames.md`)的重要性 9 | * `fileNames.md`应列出所有文件和目录,包括每个组件用途和功能的单行描述 10 | * 说明文档文件夹的作用,以及以下文件的结构: 11 | - `prd.md`(产品需求文档) 12 | - `app-flow.md`(应用流程) 13 | - `backend-structure.md`(后端结构) 14 | - `frontend-guidelines.md`(前端指南) 15 | - `tech-stack.md`(技术栈) 16 | - `file-structure.md`(文件结构) 17 | 18 | **3. Claude作为"软件架构师":** 19 | * 如何设置专用的Claude项目来优化提示词 20 | * Claude项目知识库应包含: 21 | - 完整的文件结构(`fileNames.md`) 22 | - 主要功能需求文档 23 | - 组件特定的功能需求文档 24 | - Cursor/bolt.new的文档 25 | 26 | **4. 结构化提示流程:** 27 | * 解释包含"系统提示"和"执行提示"的两步提示流程 28 | * 使用模式说明: 29 | - 使用系统提示设置Claude上下文 30 | - 使用执行提示让Claude分析问题,识别受影响的文件,并建议高效方法 31 | * 提供执行提示的详细示例,如:"我们需要为登录表单添加邮箱验证。参考`fileNames.md`中的`src/components/login.jsx`和项目知识中的`Documentation/FRD/auth.md`。请为`bolt.new`建议一个修改该文件的高效方法,添加邮箱验证逻辑。" 32 | 33 | **5. Cursor提示技巧:** 34 | * **"修复错误"**: 35 | - 说明AI模型有时会遗漏细节并触发错误循环 36 | - 提示词:"分析此错误。识别其原因并创建分步解决方案。" 37 | * **"新功能"**: 38 | - 说明AI需要针对每个组件范围重新上下文化 39 | - 提示词:"阅读`@`中的描述并创建实现计划。在继续之前,编写实现计划。在执行之前解释你要更改的内容。" 40 | * **响应结构**: 41 | - 说明如何向AI提供更新和上下文 42 | - 提供示例:"页眉现已对齐。现在我们需要一个登录按钮。查看@login-doc并解释你的方法" 43 | 44 | **6. 进度追踪:** 45 | * `progress.md`文件的用途和提示词:"在每个完成的步骤结束时,在`@progress.md`中记录你的工作。实现了哪些功能,出现了什么错误,以及如何修复的?按顺序回答这三个问题,不要遗漏信息。" 46 | * `project-status.md`文件的用途和提示词:"在每个工作会话结束时,在`@project-status.md`中记录你的工作。查看`@progress.md`文件以总结所有工作,描述本次会话完成的内容。为下一个工作会话创建详细报告,以便提供完整概述。" 47 | 48 | **7. Cursor代理技巧:** 49 | * 解释Cursor代理可能的过度执行问题 50 | * 提供以下指导来防止该问题:"阅读@(文档名称)以确定函数范围。使用思维链逻辑创建分步实现计划。详细概述功能的每个部分,提供高层次概述。将这些部分分解为详细的编号步骤。这将提供一个你可以批准的计划,并确保操作符合要求。" 51 | 52 | **8. `.bolt/ignore`优化:** 53 | * 解释最小化LLM上下文的必要性 54 | * 解释`.bolt/ignore`文件的使用方法以及如何识别要排除的文件和目录 55 | 56 | **9. 结论:** 57 | * 重申这种方法的总体目标以及它如何使开发人员能够高效工作 58 | -------------------------------------------------------------------------------- /dev.md: -------------------------------------------------------------------------------- 1 | # 开发指南 (Development Guide) 2 | 3 | ## 目录 4 | 5 | - [本地开发环境配置](#本地开发环境配置) 6 | - [Docker开发和部署](#docker开发和部署) 7 | - [环境变量配置](#环境变量配置) 8 | - [开发工作流程](#开发工作流程) 9 | - [项目构建和部署](#项目构建和部署) 10 | - [常见问题解决](#常见问题解决) 11 | 12 | ## 本地开发环境配置 13 | 14 | ### 基础环境要求 15 | - Node.js >= 18 16 | - pnpm >= 8 17 | - Git >= 2.0 18 | - VSCode (推荐) 19 | 20 | ### 开发环境设置 21 | ```bash 22 | # 1. 克隆项目 23 | git clone https://github.com/linshenkx/prompt-optimizer.git 24 | cd prompt-optimizer 25 | 26 | # 2. 安装依赖 27 | pnpm install 28 | 29 | # 3. 启动开发服务 30 | pnpm dev # 主开发命令:构建core/ui并运行web应用 31 | pnpm dev:web # 仅运行web应用 32 | pnpm dev:fresh # 完整重置并重新启动开发环境 33 | ``` 34 | 35 | ## Docker开发和部署 36 | 37 | ### 环境要求 38 | - Docker >= 20.10.0 39 | 40 | ### Docker构建和运行 41 | 42 | #### 基础构建 43 | ```bash 44 | # 获取package.json中的版本号 45 | $VERSION=$(node -p "require('./package.json').version") 46 | 47 | # 构建镜像(使用动态版本号) 48 | docker build -t linshen/prompt-optimizer:$VERSION . 49 | 50 | # 添加latest标签 51 | docker tag linshen/prompt-optimizer:$VERSION linshen/prompt-optimizer:latest 52 | 53 | # 运行容器 54 | docker run -d -p 80:80 --restart unless-stopped --name prompt-optimizer linshen/prompt-optimizer:$VERSION 55 | 56 | 57 | # 推送 58 | docker push linshen/prompt-optimizer:$VERSION 59 | docker push linshen/prompt-optimizer:latest 60 | 61 | ``` 62 | 63 | docker本地构建测试 64 | ```shell 65 | docker build -t linshen/prompt-optimizer:test . 66 | docker rm -f prompt-optimizer 67 | docker run -d -p 80:80 --restart unless-stopped --name prompt-optimizer -e VITE_GEMINI_API_KEY=111 linshen/prompt-optimizer:test 68 | 69 | ``` 70 | 71 | 72 | ### 多阶段构建说明 73 | 74 | Dockerfile使用了多阶段构建优化镜像大小: 75 | 76 | 1. `base`: 基础Node.js环境,安装pnpm 77 | 2. `builder`: 构建阶段,安装依赖并构建项目 78 | 3. `production`: 最终镜像,只包含构建产物和nginx 79 | 80 | ## 环境变量配置 81 | 82 | ### 本地开发环境变量 83 | 在项目根目录创建 `.env.local` 文件: 84 | 85 | ```env 86 | # OpenAI API配置 87 | VITE_OPENAI_API_KEY=your_openai_api_key 88 | 89 | # Gemini API配置 90 | VITE_GEMINI_API_KEY=your_gemini_api_key 91 | 92 | # DeepSeek API配置 93 | VITE_DEEPSEEK_API_KEY=your_deepseek_api_key 94 | 95 | # 自定义API配置 96 | VITE_CUSTOM_API_KEY=your_custom_api_key 97 | VITE_CUSTOM_API_BASE_URL=your_custom_api_base_url 98 | VITE_CUSTOM_API_MODEL=your_custom_model_name 99 | ``` 100 | 101 | ### Docker环境变量 102 | 通过 `-e` 参数设置容器环境变量: 103 | 104 | ```bash 105 | docker run -d -p 80:80 \ 106 | -e VITE_OPENAI_API_KEY=your_key \ 107 | -e VITE_CUSTOM_API_BASE_URL=your_api_url \ 108 | prompt-optimizer 109 | ``` 110 | 111 | ## 开发工作流程 112 | 113 | ### 代码提交规范 114 | ```bash 115 | # 提交格式 116 | (): 117 | 118 | # 示例 119 | feat(ui): 添加新的提示词编辑器组件 120 | fix(core): 修复API调用超时问题 121 | ``` 122 | 123 | ### 测试流程 124 | ```bash 125 | # 运行所有测试 126 | pnpm test 127 | 128 | # 运行特定包的测试 129 | pnpm test:core 130 | pnpm test:ui 131 | pnpm test:web 132 | ``` 133 | 134 | ## 项目构建和部署 135 | 136 | ### 本地构建 137 | ```bash 138 | # 构建所有包 139 | pnpm build 140 | 141 | # 构建特定包 142 | pnpm build:core 143 | pnpm build:ui 144 | pnpm build:web 145 | pnpm build:ext 146 | ``` 147 | 148 | ### 常用Docker命令 149 | 150 | ```bash 151 | # 查看容器日志 152 | docker logs -f prompt-optimizer 153 | 154 | # 进入容器 155 | docker exec -it prompt-optimizer sh 156 | 157 | # 容器管理 158 | docker stop prompt-optimizer 159 | docker start prompt-optimizer 160 | docker restart prompt-optimizer 161 | 162 | # 清理资源 163 | docker rm prompt-optimizer 164 | docker rmi prompt-optimizer 165 | ``` 166 | 167 | ## 常见问题解决 168 | 169 | ### 依赖安装问题 170 | ```bash 171 | # 清理依赖缓存 172 | pnpm clean 173 | 174 | # 重新安装依赖 175 | pnpm install --force 176 | ``` 177 | 178 | ### 开发环境问题 179 | ```bash 180 | # 完全重置开发环境 181 | pnpm dev:fresh 182 | 183 | # 清理构建缓存 184 | pnpm clean 185 | rm -rf node_modules 186 | pnpm install 187 | ``` 188 | 189 | ### 构建失败处理 190 | 1. 检查Node.js版本是否符合要求 191 | 2. 清理构建缓存:`pnpm clean` 192 | 3. 重新安装依赖:`pnpm install` 193 | 4. 查看详细构建日志:`pnpm build --debug` 194 | 195 | ### 容器运行问题 196 | 1. 检查端口占用:`netstat -ano | findstr :80` 197 | 2. 检查容器日志:`docker logs prompt-optimizer` 198 | 3. 检查容器状态:`docker ps -a` 199 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | prompt-optimizer: 3 | image: linshen/prompt-optimizer:latest 4 | # Alternatively, you can build from source: 5 | # build: 6 | # context: . 7 | # dockerfile: Dockerfile 8 | container_name: prompt-optimizer 9 | restart: unless-stopped 10 | ports: 11 | - "8081:80" 12 | environment: 13 | # OpenAI API配置 14 | - VITE_OPENAI_API_KEY=${VITE_OPENAI_API_KEY:-} 15 | # Gemini API配置 16 | - VITE_GEMINI_API_KEY=${VITE_GEMINI_API_KEY:-} 17 | # DeepSeek API配置 18 | - VITE_DEEPSEEK_API_KEY=${VITE_DEEPSEEK_API_KEY:-} 19 | # SiliconFlow API配置 20 | - VITE_SILICONFLOW_API_KEY=${VITE_SILICONFLOW_API_KEY:-} 21 | # 自定义API配置 22 | - VITE_CUSTOM_API_KEY=${VITE_CUSTOM_API_KEY:-} 23 | - VITE_CUSTOM_API_BASE_URL=${VITE_CUSTOM_API_BASE_URL:-} 24 | - VITE_CUSTOM_API_MODEL=${VITE_CUSTOM_API_MODEL:-} 25 | # 健康检查 26 | healthcheck: 27 | test: ["CMD", "curl", "-f", "http://localhost:80"] 28 | interval: 30s 29 | timeout: 10s 30 | retries: 3 31 | start_period: 5s -------------------------------------------------------------------------------- /docker/generate-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 配置文件路径 4 | CONFIG_FILE="/usr/share/nginx/html/config.js" 5 | 6 | # 生成配置文件 7 | cat > $CONFIG_FILE << EOF 8 | window.runtime_config = { 9 | OPENAI_API_KEY: "${VITE_OPENAI_API_KEY:-}", 10 | GEMINI_API_KEY: "${VITE_GEMINI_API_KEY:-}", 11 | DEEPSEEK_API_KEY: "${VITE_DEEPSEEK_API_KEY:-}", 12 | SILICONFLOW_API_KEY: "${VITE_SILICONFLOW_API_KEY:-}", 13 | CUSTOM_API_KEY: "${VITE_CUSTOM_API_KEY:-}", 14 | CUSTOM_API_BASE_URL: "${VITE_CUSTOM_API_BASE_URL:-}", 15 | CUSTOM_API_MODEL: "${VITE_CUSTOM_API_MODEL:-}" 16 | }; 17 | console.log("运行时配置已加载"); 18 | EOF 19 | 20 | echo "配置文件已生成: $CONFIG_FILE" -------------------------------------------------------------------------------- /docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name _; 4 | root /usr/share/nginx/html; 5 | index index.html; 6 | 7 | # 安全相关头部 8 | add_header X-Frame-Options "SAMEORIGIN" always; 9 | add_header X-XSS-Protection "1; mode=block" always; 10 | add_header X-Content-Type-Options "nosniff" always; 11 | add_header Referrer-Policy "no-referrer-when-downgrade" always; 12 | add_header Content-Security-Policy "default-src 'self' https: http: data: blob: 'unsafe-inline'" always; 13 | 14 | # 启用gzip压缩 15 | gzip on; 16 | gzip_vary on; 17 | gzip_proxied any; 18 | gzip_comp_level 6; 19 | gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss text/javascript application/x-javascript image/svg+xml; 20 | gzip_min_length 1000; 21 | 22 | # 客户端缓存控制 23 | location /assets { 24 | expires 7d; 25 | add_header Cache-Control "public, no-transform"; 26 | try_files $uri $uri/ =404; 27 | } 28 | 29 | # API请求代理示例(如果需要的话取消注释) 30 | # location /api { 31 | # proxy_pass http://api_backend; 32 | # proxy_http_version 1.1; 33 | # proxy_set_header Upgrade $http_upgrade; 34 | # proxy_set_header Connection 'upgrade'; 35 | # proxy_set_header Host $host; 36 | # proxy_cache_bypass $http_upgrade; 37 | # } 38 | 39 | # SPA应用路由支持 40 | location / { 41 | try_files $uri $uri/ /index.html; 42 | expires -1; 43 | add_header Cache-Control "no-store, no-cache, must-revalidate"; 44 | } 45 | 46 | # 禁止访问隐藏文件 47 | location ~ /\. { 48 | deny all; 49 | access_log off; 50 | log_not_found off; 51 | } 52 | 53 | # 错误页面配置 54 | error_page 404 /index.html; 55 | error_page 500 502 503 504 /50x.html; 56 | location = /50x.html { 57 | root /usr/share/nginx/html; 58 | } 59 | 60 | # 性能优化:关闭访问日志,只记录错误 61 | access_log off; 62 | error_log /var/log/nginx/error.log error; 63 | 64 | # 禁止特定请求方法 65 | if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|OPTIONS)$) { 66 | return 444; 67 | } 68 | 69 | # 配置较大文件的传输 70 | client_max_body_size 50m; 71 | client_body_buffer_size 128k; 72 | 73 | # 连接超时设置 74 | keepalive_timeout 65; 75 | client_header_timeout 60; 76 | client_body_timeout 60; 77 | send_timeout 60; 78 | proxy_connect_timeout 60; 79 | proxy_send_timeout 60; 80 | proxy_read_timeout 60; 81 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 项目文档索引 2 | 3 | ## 文档合并说明 4 | 5 | 为了提高文档的组织性和可读性,我们对项目文档进行了重构和合并。以下是文档合并情况: 6 | 7 | | 新文档 | 包含原文档 | 说明 | 8 | |-------|------------|------| 9 | | [technical-development-guide.md](./technical-development-guide.md) | development-guidelines.md, technical-documentation.md | 技术开发指南,整合了开发规范和技术文档 | 10 | | [project-structure.md](./project-structure.md) | file-structure.md, fileNames.md | 项目结构说明,包含文件结构和目录组织 | 11 | | [project-status.md](./project-status.md) | progress.md, chrome-extension-plan.md | 项目状态,包含进度和Chrome扩展计划 | 12 | 13 | 14 | ## 核心文档 15 | 16 | - [README.md](../README.md) - 项目总体介绍 17 | - [technical-development-guide.md](./technical-development-guide.md) - 技术开发指南,包含技术栈和开发规范 18 | - [project-structure.md](./project-structure.md) - 项目结构,专注于文件和目录组织 19 | - [project-status.md](./project-status.md) - 项目状态,包含进度和计划 20 | - [prd.md](./prd.md) - 产品需求文档 21 | 22 | ## 其他资源 23 | 24 | - [experience.md](../experience.md) - 项目经验总结 25 | - [scratchpad.md](../scratchpad.md) - 开发笔记 26 | - [CHANGELOG.md](./CHANGELOG.md) - 文档更新日志 27 | 28 | ## 如何使用文档 29 | 30 | 1. **新成员入职**: 31 | - 首先阅读 README.md,了解项目概况 32 | - 查看 project-structure.md 了解项目结构 33 | - 参考 technical-development-guide.md 了解技术实现和开发规范 34 | 35 | 2. **开发参考**: 36 | - 遵循 technical-development-guide.md 中的开发规范 37 | - 查阅 technical-development-guide.md 了解应用流程 38 | - 参考 project-structure.md 了解代码组织 39 | 40 | 3. **项目进度**: 41 | - 通过 project-status.md 了解当前进度和计划 42 | - 关注 CHANGELOG.md 了解最新变更 43 | 44 | ## 文档维护说明 45 | 46 | 1. 所有文档采用 Markdown 格式 47 | 2. 文档更新后,请在文档末尾添加更新时间 48 | 3. 重要变更请同时更新 project-status.md 中的更新记录 49 | 4. 文档中的代码示例应确保可运行且符合项目规范 50 | 5. 注意区分不同文档的职责,避免内容重复 51 | 6. 弃用的文档应标记为已弃用,并引导用户使用新文档 52 | 53 | 最后更新:2024-03-02 -------------------------------------------------------------------------------- /docs/prd.md: -------------------------------------------------------------------------------- 1 | # 提示词优化器产品需求文档 2 | 3 | ## 1. 产品概述 4 | 提示词优化器是一个纯前端的工具,帮助用户优化和改进AI提示词。通过集成多个LLM模型,为用户提供专业的提示词优化建议。 5 | 6 | ## 2. 目标用户 7 | - AI应用开发者 8 | - 提示词工程师 9 | - 需要与AI模型交互的普通用户 10 | 11 | ## 3. 核心功能需求 12 | 13 | ### 3.1 提示词优化 14 | - 支持输入原始提示词 15 | - 提供多个LLM模型选择 16 | - 实时字数统计 17 | - 一键清空输入 18 | - 优化结果预览 19 | - 一键复制结果 20 | 21 | ### 3.2 模型管理 22 | - 支持多个LLM模型(DeepSeek、Gemini等) 23 | - API密钥管理 24 | - 模型配置编辑 25 | - 自定义模型支持 26 | 27 | ### 3.3 历史记录 28 | - 本地保存优化历史 29 | - 按时间排序 30 | - 搜索和过滤 31 | - 一键重用历史记录 32 | - 删除历史记录 33 | 34 | ### 3.4 用户界面 35 | - 响应式设计 36 | - 深色/浅色主题 37 | - 多语言支持 38 | - 操作反馈提示 39 | 40 | ## 4. 非功能需求 41 | 42 | ### 4.1 性能要求 43 | - 页面加载时间 < 2秒 44 | - API响应时间 < 5秒 45 | - 流畅的动画效果 46 | 47 | ### 4.2 安全要求 48 | - API密钥加密存储 49 | - 本地数据安全存储 50 | - 敏感信息保护 51 | 52 | ### 4.3 兼容性要求 53 | - 支持主流浏览器 54 | - 移动端适配 55 | - 响应式布局 56 | 57 | ## 5. 未来规划 58 | - 支持更多LLM模型 59 | - 提示词模板库 60 | - 批量优化功能 61 | - 提示词评分系统 62 | - 社区分享功能 -------------------------------------------------------------------------------- /docs/project-status.md: -------------------------------------------------------------------------------- 1 | # 项目状态文档 2 | 3 | ## 1. 项目概述 4 | 5 | 提示词优化器是一个帮助用户优化AI提示词的工具,支持多种模型和界面形式。包括Web应用和Chrome浏览器插件两种使用方式,采用monorepo结构进行开发。 6 | 7 | ## 2. 总体进度 8 | - 项目完成度:85% 9 | - 当前阶段:SDK迁移与性能优化 10 | - 预计完成时间:4月初 11 | 12 | ## 3. 功能完成情况 13 | 14 | ### 3.1 核心包(@prompt-optimizer/core) 15 | - ✅ 基础架构搭建 16 | - ✅ 项目结构设计 17 | - ✅ 多包工作区配置 18 | - ✅ 基础设施搭建 19 | 20 | - ✅ 服务迁移与优化 21 | - ✅ 从LangChain迁移到原生SDK 22 | - ✅ 模型管理服务优化 23 | - ✅ 提示词服务优化 24 | - ✅ 模板服务完善 25 | - ✅ 历史记录服务重构 26 | 27 | - ✅ 模型集成 28 | - ✅ OpenAI集成 29 | - ✅ Gemini集成 30 | - ✅ DeepSeek集成 31 | - ✅ 自定义API支持 32 | - ✅ 流式响应支持 33 | - ✅ 错误处理优化 34 | 35 | ### 3.2 Web包(@prompt-optimizer/web) 36 | - ✅ UI重构 37 | - ✅ 组件模块化 38 | - ✅ UI包抽取 39 | - ✅ 服务调用更新 40 | - ✅ 错误处理优化 41 | 42 | - ✅ 功能增强 43 | - ✅ 流式响应UI 44 | - ✅ 模型连接测试 45 | - ✅ 配置验证增强 46 | - ✅ Toast组件迁移 47 | - ✅ 环境变量加载优化 48 | 49 | ### 3.3 Chrome插件(@prompt-optimizer/extension) 50 | - ✅ 基础框架 51 | - ✅ 插件架构设计 52 | - ✅ 核心功能移植 53 | - ✅ 权限管理 54 | - ✅ UI组件复用 55 | - ✅ 特性开发 56 | - ✅ 右键菜单集成 57 | - ✅ 快捷键支持 58 | - ✅ 历史同步 59 | - ✅ 配置管理 60 | 61 | ## 4. 进行中的任务 62 | 63 | ### 4.1 核心功能完善(进度:90%) 64 | - ✅ 错误处理系统 65 | - ✅ 统一错误类型 66 | - ✅ 错误处理流程 67 | - ✅ 错误恢复机制 68 | - ⏳ 性能优化 69 | - ✅ 原生SDK迁移 70 | - ✅ 资源管理优化 71 | - ⏳ 内存使用优化 72 | 73 | ### 4.2 测试覆盖(进度:70%) 74 | - ✅ 单元测试 75 | - ✅ 服务测试 76 | - ✅ 工具函数测试 77 | - ✅ 错误处理测试 78 | - ⏳ 集成测试 79 | - ✅ 服务集成测试 80 | - ⏳ API集成测试 81 | - ⏳ 流程测试 82 | 83 | ### 4.3 文档完善(进度:85%) 84 | - ✅ 核心文档 85 | - ✅ 架构文档 86 | - ✅ API文档 87 | - ✅ 开发指南 88 | - ⏳ 使用文档 89 | - ✅ 最佳实践 90 | - ⏳ 示例代码 91 | - ⏳ 故障排除 92 | 93 | ### 4.4 Chrome插件优化(进度:90%) 94 | - ✅ 性能优化 95 | - ✅ 资源加载优化 96 | - ✅ 响应速度优化 97 | - ⏳ 内存使用优化 98 | 99 | - ✅ 安全加固 100 | - ✅ 权限管理 101 | - ✅ 数据安全 102 | - ⏳ 通信安全 103 | 104 | - ⏳ 测试与文档 105 | - ✅ 单元测试 106 | - ⏳ 集成测试 107 | - ⏳ 文档更新 108 | 109 | ## 5. 待开发功能 110 | 111 | ### 5.1 高级功能(计划启动:4月初) 112 | - ⏳ 批量处理 113 | - ⏳ 批量优化 114 | - ⏳ 任务队列 115 | - ⏳ 进度管理 116 | - ⏳ 提示词分析 117 | - ⏳ 质量评估 118 | - ⏳ 性能分析 119 | - ⏳ 优化建议 120 | 121 | ## 6. 技术指标 122 | 123 | ### 6.1 当前指标(2024-02-26) 124 | - 代码测试覆盖率:80% 125 | - 页面加载时间:1.3秒 126 | - API响应时间:0.8-2.0秒 127 | - 首次内容渲染:0.8秒 128 | 129 | ### 6.2 目标指标(4月初) 130 | - 代码测试覆盖率:>85% 131 | - 页面加载时间:<1.2秒 132 | - API响应时间:<1.5秒 133 | - 首次内容渲染:<0.8秒 134 | 135 | ## 7. 风险评估 136 | 137 | ### 7.1 技术风险 138 | - 🟢 原生SDK集成 139 | - 版本兼容性已解决 140 | - API稳定性已验证 141 | - 性能提升明显 142 | - 🟢 多模型支持 143 | - API差异处理已完成 144 | - 错误处理统一完成 145 | - 配置复杂性降低 146 | - 🟡 安全性问题 147 | - API密钥保护已实现 148 | - 数据安全待加强 149 | - XSS防护完善中 150 | 151 | ### 7.2 项目风险 152 | - 🟢 进度风险 153 | - 核心功能已完成 154 | - 测试覆盖持续增加 155 | - 文档更新同步 156 | - 🟢 质量风险 157 | - 代码质量控制 158 | - 性能优化明显 159 | - 用户体验提升 160 | - 🟢 Chrome API兼容性(已解决) 161 | - 🟡 性能瓶颈(优化中) 162 | - 🟢 跨域通信(已解决) 163 | 164 | ## 8. 发布计划 165 | 166 | ### 8.1 测试版(v0.1.0)- 预计3月初发布 167 | - ✅ 基础功能可用 168 | - ✅ 核心特性完整 169 | - ✅ 初步性能优化 170 | - ✅ 基本安全措施 171 | 172 | ### 8.2 正式版(v1.0.0)- 预计3月中旬发布 173 | - ⏳ 完整功能集 174 | - ⏳ 性能优化完成 175 | - ⏳ 安全措施完善 176 | - ⏳ 文档完整 177 | 178 | ## 9. 发布准备 179 | 180 | ### 9.1 商店发布材料(进行中) 181 | - ⏳ 扩展描述 182 | - ⏳ 详细功能介绍 183 | - ⏳ 高质量截图(至少3张) 184 | - ⏳ 宣传视频(可选) 185 | - ⏳ 隐私政策 186 | 187 | ### 9.2 最终审核(计划中) 188 | - ⏳ 代码审核 189 | - ⏳ 功能测试 190 | - ⏳ 权限审查 191 | - ⏳ 安全检查 192 | - ⏳ 性能测试 193 | 194 | ## 10. 后续计划 195 | 196 | ### 10.1 近期计划(1-2周) 197 | 1. 完成剩余功能优化 198 | - 内存使用优化 199 | - 性能进一步调优 200 | - 用户体验改进 201 | 202 | 2. 提升测试覆盖率 203 | - 补充集成测试 204 | - 完善API测试 205 | - 添加E2E测试 206 | 207 | 3. 完善文档系统 208 | - 更新技术栈文档 209 | - 添加示例代码 210 | - 编写故障排除指南 211 | 212 | ### 10.2 中期计划(2-3周) 213 | 1. 完成Chrome插件发布准备 214 | - 最终功能测试 215 | - 性能优化 216 | - 文档准备 217 | - 商店资料准备 218 | 219 | 2. 开发高级功能 220 | - 实现批量处理 221 | - 添加分析功能 222 | - 优化用户体验 223 | 224 | ### 10.3 长期计划(1-2月) 225 | 1. 产品化完善 226 | - 功能完整性 227 | - 稳定性提升 228 | - 性能持续优化 229 | 230 | 2. 社区建设 231 | - 开源推广 232 | - 文档完善 233 | - 示例丰富 234 | 235 | ## 11. 维护计划 236 | 237 | ### 11.1 日常维护 238 | - 问题修复 239 | - 性能监控 240 | - 安全更新 241 | - 用户反馈 242 | 243 | ### 11.2 版本更新 244 | - 功能迭代 245 | - 性能优化 246 | - 安全加固 247 | - 文档更新 248 | 249 | ## 12. 更新记录 250 | - 2024-02-26: 完成从LangChain迁移到原生SDK 251 | - 2024-02-26: 更新项目配置和依赖 252 | - 2024-02-25: 优化环境变量加载和测试集成 253 | - 2024-02-25: 重构核心包导出和模块结构 254 | - 2024-02-21: 重构历史记录管理,移除初始化逻辑并优化UI组件 255 | - 2024-02-18: 改进模板选择类型安全性和错误处理 256 | - 2024-02-18: 模块化UI包并改进扩展和Web应用中的类型安全性 257 | - 2024-02-15: 优化多模型支持 258 | - 2024-02-14: 重构提示词服务 259 | - 2024-02-12: 重构UI组件结构 260 | 261 | ## 13. Chrome扩展开发经验 262 | 263 | ### 13.1 图标问题排查 264 | - manifest.json中的图标设置需要严格遵循Chrome扩展规范 265 | - 图标必须是有效的PNG格式 266 | - 图标尺寸必须严格符合声明(16x16、48x48、128x128) 267 | - 如果图标不显示,可以尝试更换其他已确认可用的PNG图片进行测试 -------------------------------------------------------------------------------- /docs/project-structure.md: -------------------------------------------------------------------------------- 1 | # 项目结构文档 2 | 3 | > **注意:** 本文档专注于项目的文件和目录结构。关于技术栈详情和实现流程,请参考 [技术文档](./technical-documentation.md)。 4 | 5 | ## 1. 项目整体架构 6 | 7 | ### 1.1 根目录结构 8 | ``` 9 | prompt-optimizer/ 10 | ├── packages/ # 项目包 11 | │ ├── core/ # 核心功能包 12 | │ │ ├── src/ # 核心源代码 13 | │ │ ├── tests/ # 核心包测试 14 | │ │ └── package.json # 核心包配置 15 | │ ├── web/ # Web版本 16 | │ │ ├── src/ # Web源代码 17 | │ │ ├── tests/ # Web测试 18 | │ │ └── package.json # Web包配置 19 | │ └── extension/ # Chrome插件 20 | ├── docs/ # 项目文档 21 | ├── tools/ # 工具脚本 22 | └── ...配置文件 23 | ``` 24 | 25 | ### 1.2 配置文件 26 | - `pnpm-workspace.yaml` - 工作区配置 27 | - `.env.example` - 环境变量示例 28 | - `package.json` - 项目配置 29 | - `.vscode/` - VSCode配置目录 30 | - `.cursorrules` - Cursor IDE配置 31 | - `.windsurfrules` - Windsurf IDE配置 32 | - `.gitignore` - Git忽略配置 33 | 34 | ### 1.3 工作区文件 35 | - `README.md` - 项目说明文档 36 | - `scratchpad.md` - 开发笔记和任务规划 37 | - `experience.md` - 项目经验总结 38 | - `cursor_tips.md` - AI辅助开发指南 39 | 40 | ### 1.4 文档目录 (docs/) 41 | - `README.md` - 文档索引 42 | - `development-guidelines.md` - 开发指南 43 | - `project-status.md` - 项目状态 44 | - `project-structure.md` - 项目结构 45 | - `technical-documentation.md` - 技术文档 46 | - `prd.md` - 产品需求文档 47 | - `CHANGELOG.md` - 更新日志 48 | 49 | ## 2. 核心包结构 (packages/core) 50 | 51 | ### 2.1 源代码目录 (packages/core/src/) 52 | ``` 53 | src/ 54 | ├── services/ # 核心服务 55 | │ ├── llm/ # LLM服务 56 | │ │ ├── service.ts # LLM服务实现 57 | │ │ ├── types.ts # 类型定义 58 | │ │ └── errors.ts # 错误定义 59 | │ ├── model/ # 模型管理 60 | │ │ ├── manager.ts # 模型管理器 61 | │ │ ├── types.ts # 类型定义 62 | │ │ └── defaults.ts# 默认配置 63 | │ ├── prompt/ # 提示词服务 64 | │ │ ├── service.ts # 提示词服务实现 65 | │ │ ├── types.ts # 类型定义 66 | │ │ └── errors.ts # 错误定义 67 | │ ├── template/ # 模板服务 68 | │ │ ├── manager.ts # 模板管理器 69 | │ │ ├── types.ts # 类型定义 70 | │ │ └── defaults.ts# 默认配置 71 | │ └── history/ # 历史记录服务 72 | │ ├── manager.ts # 历史管理器 73 | │ └── types.ts # 类型定义 74 | ├── types/ # 公共类型定义 75 | └── utils/ # 工具函数 76 | ``` 77 | 78 | ### 2.2 API目录 (src/api/) 79 | - `api/llm.js` - LLM API调用封装 80 | 81 | ### 2.3 配置目录 (packages/core/config/) 82 | - `models.js` - LLM模型配置 83 | - `prompts.js` - 提示词模板配置 84 | 85 | ### 2.4 测试目录 (packages/core/tests/) 86 | ``` 87 | tests/ 88 | ├── unit/ # 单元测试 89 | │ └── services/ # 服务测试 90 | │ ├── llm/ # LLM服务测试 91 | │ ├── model/ # 模型管理测试 92 | │ └── prompt/ # 提示词服务测试 93 | └── integration/ # 集成测试 94 | └── services/ # 服务集成测试 95 | ``` 96 | 97 | ### 2.5 核心包配置 98 | - `package.json` - 核心包配置 99 | - `tsconfig.json` - TypeScript配置 100 | - `vitest.config.ts` - 测试配置 101 | 102 | ## 3. Web包结构 (packages/web) 103 | 104 | ### 3.1 源代码目录 (packages/web/src/) 105 | ``` 106 | src/ 107 | ├── components/ # Vue组件 108 | │ ├── PromptPanel.vue # 提示词面板 109 | │ ├── ModelManager.vue # 模型管理器 110 | │ ├── TemplateManager.vue# 模板管理器 111 | │ ├── InputPanel.vue # 输入面板 112 | │ └── OutputPanel.vue # 输出面板 113 | ├── composables/ # Vue组合式函数 114 | ├── services/ # 业务逻辑 115 | │ ├── llm/ # LLM服务 116 | │ ├── model/ # 模型配置 117 | │ ├── prompt/ # 提示词服务 118 | │ ├── promptManager.js # 提示词管理 119 | │ └── themeManager.js # 主题管理 120 | ├── assets/ # 静态资源 121 | │ ├── images/ # 图片资源 122 | │ └── styles/ # 样式资源 123 | ├── prompts/ # 提示词模板 124 | ├── App.vue # 根组件 125 | └── main.ts # 入口文件 126 | ``` 127 | 128 | ### 3.2 组件目录详情 (packages/web/src/components/) 129 | - `PromptPanel.vue` - 提示词输入和优化面板 130 | - `InputPanel.vue` - 输入面板组件 131 | - `OutputPanel.vue` - 输出面板组件 132 | - `ModelConfig.vue` - 模型配置组件 133 | - `ThemeToggle.vue` - 主题切换组件 134 | - `LoadingSpinner.vue` - 加载动画组件 135 | 136 | ### 3.3 测试目录 (packages/web/tests/) 137 | ``` 138 | tests/ 139 | ├── unit/ # 单元测试 140 | │ ├── components/ # 组件测试 141 | │ └── services/ # 服务测试 142 | └── integration/ # 集成测试 143 | └── services/ # 服务集成测试 144 | ``` 145 | 146 | ### 3.4 Web包配置 147 | - `package.json` - Web包配置 148 | - `vite.config.ts` - Vite配置 149 | - `tailwind.config.js` - TailwindCSS配置 150 | - `.env.local` - 本地环境变量 151 | - `postcss.config.js` - PostCSS配置 152 | - `index.html` - 项目入口HTML文件 153 | 154 | ## 4. 扩展包结构 (packages/extension) 155 | 156 | ### 4.1 源代码目录 (packages/extension/src/) 157 | ``` 158 | src/ 159 | ├── popup/ # 弹出窗口界面 160 | ├── background/ # 后台脚本 161 | ├── content/ # 内容脚本 162 | └── manifest.json # 扩展配置文件 163 | ``` 164 | 165 | ### 4.2 扩展包配置 166 | - `package.json` - 扩展包配置 167 | - `vite.config.ts` - 构建配置 168 | 169 | ## 5. 依赖关系 170 | 171 | ### 5.1 核心包依赖 (@prompt-optimizer/core) 172 | ``` 173 | @prompt-optimizer/core 174 | ├── @openai/openai ^4.83.0 # OpenAI SDK 175 | ├── @google/generative-ai ^0.21.0 # Google Generative AI SDK 176 | └── uuid ^11.0.5 # UUID生成 177 | ``` 178 | 179 | ### 5.2 Web包依赖 (@prompt-optimizer/web) 180 | ``` 181 | @prompt-optimizer/web 182 | ├── @prompt-optimizer/core # 依赖核心包 183 | ├── vue ^3.5.x # Vue框架 184 | ├── pinia ^2.1.x # 状态管理 185 | └── tailwindcss ^3.4.1 # 样式框架 186 | ``` 187 | 188 | ### 5.3 扩展包依赖 (@prompt-optimizer/extension) 189 | ``` 190 | @prompt-optimizer/extension 191 | ├── @prompt-optimizer/core # 依赖核心包 192 | ├── @prompt-optimizer/ui # 依赖UI组件包 193 | └── vue ^3.5.x # Vue框架 194 | ``` -------------------------------------------------------------------------------- /docs/vercel.md: -------------------------------------------------------------------------------- 1 | ## Vercel 部署说明 2 | 3 | ### 部署方式对比 4 | 5 | | 部署方式 | 优点 | 缺点 | 6 | |---------|------|------| 7 | | 一键部署 | 快速简便,无需额外设置 | 无法自动同步源项目更新 | 8 | | Fork后导入 | 可跟踪源项目更新,更易维护 | 第一次部署需要手动修复根目录设置以启用Vercel代理功能 | 9 | 10 | ### 推荐方式:Fork项目后导入到Vercel(推荐) 11 | 12 | 这种方式可以让你跟踪项目更新,便于后续同步最新功能和bug修复。 13 | 14 | 1. **Fork项目到自己的GitHub** 15 | - 访问[prompt-optimizer项目](https://github.com/linshenkx/prompt-optimizer) 16 | - 点击右上角的"Fork"按钮 17 | - 完成fork操作后,你将在自己的GitHub账号下拥有此项目的副本 18 | 19 | 2. **导入项目到Vercel** 20 | - 登录[Vercel平台](https://vercel.com/) 21 | - 点击"Add New..."→"Project" 22 | - 在"Import Git Repository"部分找到你fork的项目并点击"Import" 23 | - 配置项目(**注意**:此处虽然可以设置根目录,但对多模块项目无效,仍需后续手动修复) 24 | - 点击"Deploy"开始部署 25 | 26 | ![导入项目到Vercel](../images/vercel/import.png) 27 | 28 | 3. **修复根目录设置(强烈建议)** 29 | - 通过导入部署时,虽然项目的`vercel.json`文件已包含相关修复可以让基本功能正常工作 30 | - 但若要启用**Vercel代理功能**(解决跨域问题的关键功能),则需手动修复根目录: 31 | 32 | a. 在项目部署完成后,进入项目设置 33 | 34 | b. 点击左侧菜单中的"Build and Deployment" 35 | 36 | c. 在"Root Directory"部分,将输入框中的内容**清空** 37 | 38 | d. 点击"Save"保存设置 39 | 40 | ![清空根目录设置](../images/vercel/setting.png) 41 | 42 | 4. **重新部署项目** 43 | - 设置保存后,需要手动触发重新部署以使修复生效 44 | - 点击顶部导航栏中的"Deployments" 45 | - 在最新的部署记录右侧,点击"..."按钮 46 | - 选择"Redeploy"选项触发重新部署 47 | 48 | ![重新部署项目](../images/vercel/redeploy.png) 49 | 50 | 5. **配置环境变量(可选)** 51 | - 部署完成后,进入项目设置 52 | - 点击"Environment Variables" 53 | - 添加需要的API密钥(例如`VITE_OPENAI_API_KEY`) 54 | - 重新部署项目使环境变量生效 55 | 56 | 6. **同步上游更新** 57 | - 在GitHub上打开你fork的项目 58 | - 如果有更新,会显示"This branch is X commits behind linshenkx:main" 59 | - 点击"Sync fork"按钮同步最新更改 60 | - Vercel会自动检测到代码变更并重新部署 61 | 62 | ### 替代方式:一键部署到Vercel 63 | 64 | 如果你只需要快速部署而不关心后续更新,可以使用一键部署方式: 65 | 66 | 1. 点击以下按钮直接部署到Vercel 67 | [![部署到 Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flinshenkx%2Fprompt-optimizer) 68 | 69 | 2. 按照Vercel的引导完成部署流程 70 | 71 | **优势:** 一键部署方式Vercel能自动正确识别根目录,无需手动修复,所有功能(包括Vercel代理)均可正常使用。 72 | 73 | ### 关于Vercel代理功能 74 | 75 | Prompt Optimizer在Vercel部署时支持使用Edge Runtime代理解决跨域问题。 76 | 77 | 1. **确认代理功能可用** 78 | - 如使用一键部署:代理功能应直接可用 79 | - 如使用导入部署:需完成上述"修复根目录设置"和"重新部署"步骤 80 | - 在应用中打开"模型管理" 81 | - 选择目标模型->"编辑",此时应该可以看到"使用Vercel代理"选项 82 | - 如果没有看到此选项,说明Vercel Function未正确部署,请检查根目录设置 83 | 84 | 2. **启用代理功能** 85 | - 勾选"使用Vercel代理"选项 86 | - 保存配置 87 | 88 | 3. **代理原理** 89 | - 请求流向:浏览器→Vercel Edge Runtime→模型服务提供商 90 | - 解决了浏览器直接访问API时的跨域限制 91 | - 代理功能基于Vercel Function实现,依赖于`/api`路径 92 | 93 | 4. **注意事项** 94 | - 部分模型服务提供商可能会限制来自Vercel的请求 95 | - 如遇限制,建议使用自部署的API中转服务 96 | 97 | ### 常见问题 98 | 99 | 1. **部署后页面空白或报错** 100 | - 检查是否正确配置了环境变量 101 | - 查看Vercel部署日志寻找错误原因 102 | 103 | 2. **无法连接到模型API** 104 | - 确认API密钥已正确配置 105 | - 尝试启用Vercel代理功能 106 | - 检查模型服务提供商是否限制了Vercel请求 107 | 108 | 3. **"使用Vercel代理"选项未显示** 109 | - 如使用导入部署:检查是否已清空根目录设置并重新部署 110 | - 验证`/api/vercel-status`路径是否可访问(可通过浏览器访问`你的域名/api/vercel-status`测试) 111 | - 查看部署日志中是否有关于Function的错误信息 112 | 113 | 4. **如何更新已部署的项目** 114 | - 如果是fork后导入:同步fork并等待自动部署 115 | - 如果是一键部署:需要重新部署新版本(无法自动跟踪源项目更新) 116 | 117 | 5. **如何添加自定义域名** 118 | - 在Vercel项目设置中选择"Domains" 119 | - 添加并验证你的域名 120 | - 按照指引配置DNS记录 121 | -------------------------------------------------------------------------------- /images/contrast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/images/contrast.png -------------------------------------------------------------------------------- /images/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/images/main.png -------------------------------------------------------------------------------- /images/vercel/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/images/vercel/import.png -------------------------------------------------------------------------------- /images/vercel/redeploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/images/vercel/redeploy.png -------------------------------------------------------------------------------- /images/vercel/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/images/vercel/setting.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@prompt-optimizer/core": ["packages/core"], 6 | "@prompt-optimizer/ui": ["packages/ui"], 7 | "@prompt-optimizer/web": ["packages/web"], 8 | "@prompt-optimizer/extension": ["packages/extension"] 9 | } 10 | }, 11 | "include": ["packages/*/src"], 12 | "exclude": ["node_modules", "**/node_modules", "**/dist"] 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prompt-optimizer", 3 | "version": "1.0.3", 4 | "private": true, 5 | "packageManager": "pnpm@10.6.1", 6 | "engines": { 7 | "node": "^18.0.0 || ^20.0.0 || ^22.0.0", 8 | "npm": "请使用pnpm代替npm", 9 | "yarn": "请使用pnpm代替yarn" 10 | }, 11 | "scripts": { 12 | "build:core": "pnpm -F @prompt-optimizer/core build", 13 | "build:ui": "pnpm -F @prompt-optimizer/ui build", 14 | "build:web": "pnpm -F @prompt-optimizer/web build", 15 | "build:ext": "pnpm -F @prompt-optimizer/extension build", 16 | "build": "pnpm -r build", 17 | "watch:ui": "pnpm -F @prompt-optimizer/ui build --watch", 18 | "dev": "npm-run-all dev:setup dev:parallel", 19 | "dev:setup": "npm-run-all clean:dist build:core build:ui", 20 | "dev:parallel": "concurrently -k -p \"[{name}]\" -n \"UI,WEB\" \"pnpm run watch:ui\" \"pnpm run dev:web\"", 21 | "dev:web": "pnpm -F @prompt-optimizer/web dev", 22 | "dev:ext": "pnpm -F @prompt-optimizer/extension dev", 23 | "test:core": "pnpm -F @prompt-optimizer/core test --run --passWithNoTests", 24 | "test:ui": "pnpm -F @prompt-optimizer/ui test --run --passWithNoTests", 25 | "test:web": "pnpm -F @prompt-optimizer/web test --run --passWithNoTests", 26 | "test:ext": "pnpm -F @prompt-optimizer/extension test --run --passWithNoTests", 27 | "test": "pnpm -r test --run --passWithNoTests", 28 | "test:watch": "pnpm -r test --watch --passWithNoTests", 29 | "clean:core": "rimraf packages/core/dist", 30 | "clean:ui": "rimraf packages/ui/dist", 31 | "clean:web": "rimraf packages/web/dist", 32 | "clean:ext": "rimraf packages/extension/dist", 33 | "clean:vite:core": "rimraf packages/core/node_modules/.vite", 34 | "clean:vite:ui": "rimraf packages/ui/node_modules/.vite", 35 | "clean:vite:web": "rimraf packages/web/node_modules/.vite", 36 | "clean:vite:ext": "rimraf packages/extension/node_modules/.vite", 37 | "clean:vite": "npm-run-all clean:vite:core clean:vite:ui clean:vite:web clean:vite:ext", 38 | "clean:dist": "npm-run-all clean:core clean:ui clean:web clean:ext", 39 | "clean": "npm-run-all clean:dist clean:vite", 40 | "setup:install": "pnpm install", 41 | "dev:fresh": "npm-run-all clean setup:install dev" 42 | }, 43 | "devDependencies": { 44 | "concurrently": "^8.2.2", 45 | "cross-env": "^7.0.3", 46 | "npm-run-all": "^4.1.5", 47 | "rimraf": "^4.4.1", 48 | "typescript": "^5.8.2", 49 | "@ctrl/tinycolor": "^4.1.0", 50 | "@intlify/unplugin-vue-i18n": "^6.0.3", 51 | "i18next": "^24.2.2", 52 | "i18next-browser-languagedetector": "^8.0.4", 53 | "lodash-unified": "^1.0.3" 54 | }, 55 | "dependencies": { 56 | "@element-plus/icons-vue": "^2.3.1", 57 | "@floating-ui/core": "^1.6.9", 58 | "@floating-ui/dom": "^1.6.13", 59 | "@floating-ui/utils": "^0.2.9", 60 | "@popperjs/core": "^2.11.8", 61 | "@vue/reactivity": "^3.5.13", 62 | "@vue/runtime-core": "^3.5.13", 63 | "@vue/runtime-dom": "^3.5.13", 64 | "@vue/shared": "^3.5.13", 65 | "@vueuse/core": "^12.7.0", 66 | "@vueuse/shared": "^12.7.0", 67 | "async-validator": "^4.2.5", 68 | "dayjs": "^1.11.13", 69 | "lodash-es": "^4.17.21", 70 | "memoize-one": "^6.0.0", 71 | "normalize-wheel-es": "^1.2.0", 72 | "vue-i18n": "^10.0.6" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@prompt-optimizer/core", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "module": "./dist/index.js", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "types": "./dist/index.d.ts", 12 | "import": "./dist/index.js", 13 | "require": "./dist/index.cjs" 14 | } 15 | }, 16 | "scripts": { 17 | "build": "tsup src/index.ts --format cjs,esm --dts", 18 | "dev": "tsup src/index.ts --format cjs,esm --dts --watch", 19 | "test": "vitest run", 20 | "test:watch": "vitest", 21 | "test:coverage": "vitest run --coverage", 22 | "test:unit": "vitest run tests/unit", 23 | "test:integration": "vitest run tests/integration" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^20.11.0", 27 | "dotenv": "^16.4.7", 28 | "tsup": "^8.0.2", 29 | "typescript": "^5.3.3", 30 | "vitest": "^3.0.2" 31 | }, 32 | "dependencies": { 33 | "@google/generative-ai": "^0.21.0", 34 | "openai": "^4.83.0", 35 | "uuid": "^11.0.5", 36 | "zod": "^3.22.4" 37 | } 38 | } -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | // Core package entry point 2 | 3 | // 导出模板相关 4 | export { TemplateManager, templateManager } from './services/template/manager' 5 | export * from './services/template/types' 6 | export * from './services/template/defaults' 7 | export * from './services/template/errors' 8 | 9 | // 导出历史记录相关 10 | export { HistoryManager, historyManager } from './services/history/manager' 11 | export * from './services/history/types' 12 | export * from './services/history/errors' 13 | 14 | // 导出LLM服务相关 15 | export { LLMService, createLLMService } from './services/llm/service' 16 | export * from './services/llm/types' 17 | export * from './services/llm/errors' 18 | 19 | // 导出模型管理相关 20 | export { ModelManager, modelManager } from './services/model/manager' 21 | export * from './services/model/types' 22 | export * from './services/model/defaults' 23 | 24 | // 导出提示词服务相关 25 | export { PromptService, createPromptService } from './services/prompt/service' 26 | export * from './services/prompt/types' 27 | export * from './services/prompt/errors' 28 | 29 | // 导出环境工具函数 30 | export { 31 | isBrowser, 32 | isVercel, 33 | getProxyUrl, 34 | checkVercelApiAvailability, 35 | resetVercelStatusCache 36 | } from './utils/environment'; -------------------------------------------------------------------------------- /packages/core/src/services/history/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 历史记录基础错误 3 | */ 4 | export class HistoryError extends Error { 5 | constructor(message: string) { 6 | super(message); 7 | this.name = 'HistoryError'; 8 | } 9 | } 10 | 11 | /** 12 | * 记录不存在错误 13 | */ 14 | export class RecordNotFoundError extends HistoryError { 15 | constructor( 16 | message: string, 17 | public recordId: string 18 | ) { 19 | super(message); 20 | this.name = 'RecordNotFoundError'; 21 | } 22 | } 23 | 24 | /** 25 | * 存储错误 26 | */ 27 | export class StorageError extends HistoryError { 28 | constructor( 29 | message: string, 30 | public operation: 'read' | 'write' | 'delete' | 'init' | 'storage' 31 | ) { 32 | super(message); 33 | this.name = 'StorageError'; 34 | } 35 | } 36 | 37 | /** 38 | * 记录验证错误 39 | */ 40 | export class RecordValidationError extends HistoryError { 41 | constructor( 42 | message: string, 43 | public errors: string[] 44 | ) { 45 | super(message); 46 | this.name = 'RecordValidationError'; 47 | } 48 | } -------------------------------------------------------------------------------- /packages/core/src/services/history/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 提示词记录类型 3 | */ 4 | export type PromptRecordType = 'optimize' | 'iterate'; 5 | 6 | /** 7 | * 提示词记录接口 8 | */ 9 | export interface PromptRecord { 10 | /** 记录ID */ 11 | id: string; 12 | /** 原始提示词 */ 13 | originalPrompt: string; 14 | /** 优化/迭代后的提示词 */ 15 | optimizedPrompt: string; 16 | /** 记录类型 */ 17 | type: PromptRecordType; 18 | /** 所属的提示词链ID */ 19 | chainId: string; 20 | /** 在链中的版本号 */ 21 | version: number; 22 | /** 前一个版本ID */ 23 | previousId?: string; 24 | /** 时间戳 */ 25 | timestamp: number; 26 | /** 使用的模型key */ 27 | modelKey: string; 28 | /** 29 | * 使用的模型显示名称 30 | * 通过modelKey从modelManager中获取,用于UI展示 31 | * 不存储时使用modelKey作为后备显示 32 | */ 33 | modelName?: string; 34 | /** 使用的提示词ID */ 35 | templateId: string; 36 | /** 迭代时的修改说明 */ 37 | iterationNote?: string; 38 | /** 元数据 */ 39 | metadata?: Record; 40 | } 41 | 42 | /** 43 | * 历史记录链类型 44 | */ 45 | export interface PromptRecordChain { 46 | chainId: string; 47 | rootRecord: PromptRecord; 48 | currentRecord: PromptRecord; 49 | versions: PromptRecord[]; 50 | } 51 | 52 | /** 53 | * 历史记录管理器接口 54 | */ 55 | export interface IHistoryManager { 56 | /** 添加记录 */ 57 | addRecord(record: PromptRecord): void; 58 | /** 获取所有记录 */ 59 | getRecords(): PromptRecord[]; 60 | /** 获取指定记录 */ 61 | getRecord(id: string): PromptRecord; 62 | /** 删除记录 */ 63 | deleteRecord(id: string): void; 64 | /** 获取迭代链 */ 65 | getIterationChain(recordId: string): PromptRecord[]; 66 | /** 清除所有记录 */ 67 | clearHistory(): void; 68 | /** 获取所有记录链 */ 69 | getAllChains(): PromptRecordChain[]; 70 | /** 创建新的记录链 */ 71 | createNewChain(record: Omit): PromptRecordChain; 72 | /** 添加迭代记录 */ 73 | addIteration(params: { 74 | chainId: string; 75 | originalPrompt: string; 76 | optimizedPrompt: string; 77 | iterationNote: string; 78 | modelKey: string; 79 | templateId: string; 80 | }): PromptRecordChain; 81 | } -------------------------------------------------------------------------------- /packages/core/src/services/llm/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 基础错误类 3 | */ 4 | export class BaseError extends Error { 5 | constructor(message: string) { 6 | super(message); 7 | this.name = this.constructor.name; 8 | Object.setPrototypeOf(this, new.target.prototype); // 确保原型链正确 9 | } 10 | } 11 | 12 | /** 13 | * API错误 14 | * 用于表示API调用过程中的错误 15 | */ 16 | export class APIError extends BaseError { 17 | constructor(message: string) { 18 | super(`API错误: ${message}`); 19 | } 20 | } 21 | 22 | /** 23 | * 请求配置错误 24 | * 用于表示请求配置验证失败的错误 25 | */ 26 | export class RequestConfigError extends BaseError { 27 | constructor(message: string) { 28 | super(`配置错误: ${message}`); 29 | } 30 | } 31 | 32 | /** 33 | * 验证错误 34 | * 用于表示参数验证失败的错误 35 | */ 36 | export class ValidationError extends BaseError { 37 | constructor(message: string) { 38 | super(`验证错误: ${message}`); 39 | } 40 | } 41 | 42 | /** 43 | * 初始化错误 44 | * 用于表示服务初始化失败的错误 45 | */ 46 | export class InitializationError extends BaseError { 47 | constructor(message: string) { 48 | super(`初始化错误: ${message}`); 49 | } 50 | } 51 | 52 | /** 53 | * LLM服务基础错误 54 | */ 55 | export class LLMError extends Error { 56 | constructor(message: string) { 57 | super(message); 58 | this.name = 'LLMError'; 59 | } 60 | } 61 | 62 | /** 63 | * 模型配置错误 64 | */ 65 | export class ModelConfigError extends LLMError { 66 | constructor(message: string) { 67 | super(message); 68 | this.name = 'ModelConfigError'; 69 | } 70 | } 71 | 72 | // 添加统一错误常量 73 | export const ERROR_MESSAGES = { 74 | API_KEY_REQUIRED: '优化失败: API密钥不能为空', 75 | MODEL_NOT_FOUND: '优化失败: 模型不存在', 76 | TEMPLATE_INVALID: '优化失败: 提示词格式无效', 77 | EMPTY_INPUT: '优化失败: 提示词不能为空', 78 | OPTIMIZATION_FAILED: '优化失败', 79 | ITERATION_FAILED: '迭代失败', 80 | TEST_FAILED: '测试失败', 81 | MODEL_KEY_REQUIRED: '优化失败: 模型Key不能为空', 82 | INPUT_TOO_LONG: '优化失败: 输入内容过长' 83 | } as const; -------------------------------------------------------------------------------- /packages/core/src/services/llm/types.ts: -------------------------------------------------------------------------------- 1 | import { ModelConfig } from '../model/types'; 2 | /** 3 | * 消息角色类型 4 | */ 5 | export type MessageRole = 'system' | 'user' | 'assistant'; 6 | 7 | /** 8 | * 消息类型 9 | */ 10 | export interface Message { 11 | role: 'system' | 'user' | 'assistant'; 12 | content: string; 13 | } 14 | 15 | export interface StreamHandlers { 16 | onToken: (token: string) => void; 17 | onComplete: () => void; 18 | onError: (error: Error) => void; 19 | } 20 | 21 | /** 22 | * 模型信息接口 23 | */ 24 | export interface ModelInfo { 25 | id: string; // 模型ID,用于API调用 26 | name: string; // 显示名称 27 | } 28 | 29 | /** 30 | * 用于下拉选择组件的模型选项格式 31 | */ 32 | export interface ModelOption { 33 | value: string; // 选项值,通常是模型ID 34 | label: string; // 显示标签,通常是模型名称 35 | } 36 | 37 | /** 38 | * LLM服务接口 39 | */ 40 | export interface ILLMService { 41 | /** 42 | * 发送消息 43 | * @throws {RequestConfigError} 当参数无效时 44 | * @throws {APIError} 当请求失败时 45 | */ 46 | sendMessage(messages: Message[], provider: string): Promise; 47 | 48 | /** 49 | * 发送流式消息 50 | * @throws {RequestConfigError} 当参数无效时 51 | * @throws {APIError} 当请求失败时 52 | */ 53 | sendMessageStream( 54 | messages: Message[], 55 | provider: string, 56 | callbacks: StreamHandlers 57 | ): Promise; 58 | 59 | /** 60 | * 测试连接 61 | */ 62 | testConnection(provider: string): Promise; 63 | 64 | /** 65 | * 获取模型列表,以下拉选项格式返回 66 | * @param provider 提供商标识 67 | * @param customConfig 自定义配置(可选) 68 | * @throws {RequestConfigError} 当参数无效时 69 | * @throws {APIError} 当请求失败时 70 | */ 71 | fetchModelList(provider: string, customConfig?: Partial): Promise; 72 | } -------------------------------------------------------------------------------- /packages/core/src/services/model/defaults.ts: -------------------------------------------------------------------------------- 1 | import { ModelConfig } from './types'; 2 | 3 | // 获取环境变量的辅助函数 4 | const getEnvVar = (key: string): string => { 5 | // 0. 首先检查运行时配置 6 | if (typeof window !== 'undefined' && window.runtime_config) { 7 | // 移除 VITE_ 前缀以匹配运行时配置中的键名 8 | const runtimeKey = key.replace('VITE_', ''); 9 | const value = window.runtime_config[runtimeKey]; 10 | if (value !== undefined && value !== null) { 11 | return String(value); 12 | } 13 | } 14 | 15 | // 1. 然后尝试 process.env 16 | if (typeof process !== 'undefined' && process.env && process.env[key]) { 17 | return process.env[key] || ''; 18 | } 19 | 20 | // 2. 然后尝试 import.meta.env(Vite 环境) 21 | try { 22 | // @ts-ignore - 在构建时忽略此错误 23 | if (typeof import.meta !== 'undefined' && import.meta.env) { 24 | // @ts-ignore - 在构建时忽略此错误 25 | const value = import.meta.env[key]; 26 | if (value) return value; 27 | } 28 | } catch { 29 | // 忽略错误 30 | } 31 | 32 | // 3. 最后返回空字符串 33 | return ''; 34 | }; 35 | 36 | // 从环境变量获取 API keys 37 | const OPENAI_API_KEY = getEnvVar('VITE_OPENAI_API_KEY').trim(); 38 | const GEMINI_API_KEY = getEnvVar('VITE_GEMINI_API_KEY').trim(); 39 | const DEEPSEEK_API_KEY = getEnvVar('VITE_DEEPSEEK_API_KEY').trim(); 40 | const SILICONFLOW_API_KEY = getEnvVar('VITE_SILICONFLOW_API_KEY').trim(); 41 | const CUSTOM_API_KEY = getEnvVar('VITE_CUSTOM_API_KEY').trim(); 42 | const CUSTOM_API_BASE_URL = getEnvVar('VITE_CUSTOM_API_BASE_URL'); 43 | const CUSTOM_API_MODEL = getEnvVar('VITE_CUSTOM_API_MODEL'); 44 | 45 | export const defaultModels: Record = { 46 | openai: { 47 | name: 'OpenAI', 48 | baseURL: 'https://api.openai.com/v1', 49 | models: ['gpt-4', 'gpt-3.5-turbo'], 50 | defaultModel: 'gpt-3.5-turbo', 51 | apiKey: OPENAI_API_KEY, 52 | enabled: !!OPENAI_API_KEY, 53 | provider: 'openai' 54 | }, 55 | gemini: { 56 | name: 'Gemini', 57 | baseURL: 'https://generativelanguage.googleapis.com', 58 | models: ['gemini-2.0-flash'], 59 | defaultModel: 'gemini-2.0-flash', 60 | apiKey: GEMINI_API_KEY, 61 | enabled: !!GEMINI_API_KEY, 62 | provider: 'gemini' 63 | }, 64 | deepseek: { 65 | name: 'DeepSeek', 66 | baseURL: 'https://api.deepseek.com/v1', 67 | models: ['deepseek-chat'], 68 | defaultModel: 'deepseek-chat', 69 | apiKey: DEEPSEEK_API_KEY, 70 | enabled: !!DEEPSEEK_API_KEY, 71 | provider: 'deepseek' 72 | }, 73 | siliconflow: { 74 | name: 'SiliconFlow', 75 | baseURL: 'https://api.siliconflow.cn/v1', 76 | models: ['Pro/deepseek-ai/DeepSeek-V3'], 77 | defaultModel: 'Pro/deepseek-ai/DeepSeek-V3', 78 | apiKey: SILICONFLOW_API_KEY, 79 | enabled: !!SILICONFLOW_API_KEY, 80 | provider: 'siliconflow' 81 | }, 82 | custom: { 83 | name: 'Custom', 84 | baseURL: CUSTOM_API_BASE_URL, 85 | models: [CUSTOM_API_MODEL], 86 | defaultModel: CUSTOM_API_MODEL, 87 | apiKey: CUSTOM_API_KEY, 88 | enabled: !!CUSTOM_API_KEY, 89 | provider: 'custom' 90 | } 91 | }; -------------------------------------------------------------------------------- /packages/core/src/services/model/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 模型配置接口 3 | */ 4 | export interface ModelConfig { 5 | /** 模型名称 */ 6 | name: string; 7 | /** API基础URL */ 8 | baseURL: string; 9 | /** API密钥 */ 10 | apiKey?: string; 11 | /** 支持的模型列表 */ 12 | models: string[]; 13 | /** 默认模型 */ 14 | defaultModel: string; 15 | /** 是否启用 */ 16 | enabled: boolean; 17 | /** 提供商 */ 18 | provider: 'deepseek' | 'gemini' | 'custom' | string; 19 | /** 是否使用Vercel代理 */ 20 | useVercelProxy?: boolean; 21 | } 22 | 23 | /** 24 | * 模型管理器接口 25 | */ 26 | export interface IModelManager { 27 | /** 获取所有模型配置 */ 28 | getAllModels(): ModelConfig[]; 29 | /** 获取指定模型配置 */ 30 | getModel(key: string): ModelConfig | undefined; 31 | /** 添加模型配置 */ 32 | addModel(key: string, config: ModelConfig): void; 33 | /** 更新模型配置 */ 34 | updateModel(key: string, config: Partial): void; 35 | /** 删除模型配置 */ 36 | deleteModel(key: string): void; 37 | /** 启用模型 */ 38 | enableModel(key: string): void; 39 | /** 禁用模型 */ 40 | disableModel(key: string): void; 41 | /** 获取启用的模型 */ 42 | getEnabledModels(): ModelConfig[]; 43 | } -------------------------------------------------------------------------------- /packages/core/src/services/prompt/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 提示词服务基础错误 3 | */ 4 | export class PromptError extends Error { 5 | constructor(message: string) { 6 | super(message); 7 | this.name = 'PromptError'; 8 | } 9 | } 10 | 11 | /** 12 | * 优化错误 13 | */ 14 | export class OptimizationError extends PromptError { 15 | constructor( 16 | message: string, 17 | public originalPrompt: string 18 | ) { 19 | super(message); 20 | this.name = 'OptimizationError'; 21 | } 22 | } 23 | 24 | /** 25 | * 迭代错误 26 | */ 27 | export class IterationError extends PromptError { 28 | constructor( 29 | message: string, 30 | public originalPrompt: string, 31 | public iterateInput: string 32 | ) { 33 | super(message); 34 | this.name = 'IterationError'; 35 | } 36 | } 37 | 38 | /** 39 | * 测试错误 40 | */ 41 | export class TestError extends PromptError { 42 | constructor( 43 | message: string, 44 | public prompt: string, 45 | public testInput: string 46 | ) { 47 | super(message); 48 | this.name = 'TestError'; 49 | } 50 | } 51 | 52 | /** 53 | * 服务依赖错误 54 | */ 55 | export class ServiceDependencyError extends PromptError { 56 | constructor( 57 | message: string, 58 | public serviceName: string 59 | ) { 60 | super(message); 61 | this.name = 'ServiceDependencyError'; 62 | } 63 | } -------------------------------------------------------------------------------- /packages/core/src/services/prompt/factory.ts: -------------------------------------------------------------------------------- 1 | import { PromptService } from './service'; 2 | import { TemplateManager } from '../template/manager'; 3 | import { HistoryManager } from '../history/manager'; 4 | import { ModelManager } from '../model/manager'; 5 | import { LLMService } from '../llm/service'; 6 | 7 | export async function createPromptService() { 8 | const modelManager = new ModelManager(); 9 | const llmService = new LLMService(modelManager); 10 | const templateManager = new TemplateManager(); 11 | const historyManager = new HistoryManager(); 12 | 13 | 14 | return new PromptService(modelManager, llmService, templateManager, historyManager); 15 | } -------------------------------------------------------------------------------- /packages/core/src/services/prompt/types.ts: -------------------------------------------------------------------------------- 1 | import { PromptRecord } from '../history/types'; 2 | import { StreamHandlers } from '../llm/types'; 3 | import { Template } from '../template/types'; 4 | 5 | /** 6 | * 提示词服务接口 7 | */ 8 | export interface IPromptService { 9 | /** 优化提示词 */ 10 | optimizePrompt(prompt: string, modelKey: string): Promise; 11 | 12 | /** 迭代优化提示词 */ 13 | iteratePrompt( 14 | originalPrompt: string, 15 | iterateInput: string, 16 | modelKey: string 17 | ): Promise; 18 | 19 | /** 测试提示词 */ 20 | testPrompt( 21 | prompt: string, 22 | testInput: string, 23 | modelKey: string 24 | ): Promise; 25 | 26 | /** 获取历史记录 */ 27 | getHistory(): PromptRecord[]; 28 | 29 | /** 获取迭代链 */ 30 | getIterationChain(recordId: string): PromptRecord[]; 31 | 32 | /** 优化提示词(流式) */ 33 | optimizePromptStream( 34 | prompt: string, 35 | modelKey: string, 36 | template: string, 37 | callbacks: StreamHandlers 38 | ): Promise; 39 | 40 | /** 迭代优化提示词(流式) */ 41 | iteratePromptStream( 42 | originalPrompt: string, 43 | iterateInput: string, 44 | modelKey: string, 45 | handlers: StreamHandlers, 46 | template: Template | string 47 | ): Promise; 48 | 49 | /** 测试提示词(流式) */ 50 | testPromptStream( 51 | prompt: string, 52 | testInput: string, 53 | modelKey: string, 54 | callbacks: StreamHandlers 55 | ): Promise; 56 | } -------------------------------------------------------------------------------- /packages/core/src/services/template/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 提示词错误基类 3 | */ 4 | export class TemplateError extends Error { 5 | constructor(message: string) { 6 | super(message); 7 | this.name = 'TemplateError'; 8 | } 9 | } 10 | 11 | /** 12 | * 提示词加载错误 13 | */ 14 | export class TemplateLoadError extends TemplateError { 15 | constructor( 16 | message: string, 17 | public templateId: string 18 | ) { 19 | super(message); 20 | this.name = 'TemplateLoadError'; 21 | } 22 | } 23 | 24 | /** 25 | * 提示词验证错误 26 | */ 27 | export class TemplateValidationError extends TemplateError { 28 | constructor(message: string) { 29 | super(message); 30 | this.name = 'TemplateValidationError'; 31 | } 32 | } 33 | 34 | /** 35 | * 提示词缓存错误 36 | */ 37 | export class TemplateCacheError extends TemplateError { 38 | constructor(message: string) { 39 | super(message); 40 | this.name = 'TemplateCacheError'; 41 | } 42 | } 43 | 44 | /** 45 | * 提示词存储错误 46 | */ 47 | export class TemplateStorageError extends TemplateError { 48 | constructor(message: string) { 49 | super(message); 50 | this.name = 'TemplateStorageError'; 51 | } 52 | } -------------------------------------------------------------------------------- /packages/core/src/services/template/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | /** 4 | * 提示词元数据 5 | */ 6 | export interface TemplateMetadata { 7 | version: string; // 提示词版本 8 | lastModified: number; // 最后修改时间 9 | author?: string; // 作者(可选) 10 | description?: string; // 描述(可选) 11 | templateType: 'optimize' | 'iterate'; // 新增类型标识 12 | } 13 | 14 | /** 15 | * 提示词定义 16 | */ 17 | export interface Template { 18 | id: string; // 提示词唯一标识 19 | name: string; // 提示词名称 20 | content: string; // 提示词内容 21 | metadata: TemplateMetadata; 22 | isBuiltin?: boolean; // 是否为内置提示词 23 | } 24 | 25 | /** 26 | * 提示词来源类型 27 | */ 28 | export type TemplateSourceType = 'builtin' | 'localStorage'; 29 | 30 | /** 31 | * 提示词管理器配置 32 | */ 33 | export interface TemplateManagerConfig { 34 | storageKey?: string; // localStorage存储键名 35 | cacheTimeout?: number; // 缓存超时时间 36 | } 37 | 38 | /** 39 | * 提示词管理器接口 40 | */ 41 | export interface ITemplateManager { 42 | /** 获取指定ID的模板 */ 43 | getTemplate(templateId: string): Template; 44 | 45 | /** 保存模板 */ 46 | saveTemplate(template: Template): void; 47 | 48 | /** 删除模板 */ 49 | deleteTemplate(templateId: string): void; 50 | 51 | /** 列出所有模板 */ 52 | listTemplates(): Template[]; 53 | 54 | /** 导出模板 */ 55 | exportTemplate(templateId: string): string; 56 | 57 | /** 导入模板 */ 58 | importTemplate(templateJson: string): void; 59 | 60 | /** 清除缓存 */ 61 | clearCache(templateId?: string): void; 62 | 63 | /** 按类型列出模板 */ 64 | listTemplatesByType(type: 'optimize' | 'iterate'): Template[]; 65 | 66 | /** 67 | * 根据类型获取模板列表(已废弃) 68 | * @deprecated 使用 listTemplatesByType 替代 69 | */ 70 | getTemplatesByType(type: 'optimize' | 'iterate'): Template[]; 71 | } 72 | 73 | /** 74 | * 提示词验证Schema 75 | */ 76 | export const templateSchema = z.object({ 77 | id: z.string().min(1), 78 | name: z.string().min(1), 79 | content: z.string().min(1), 80 | metadata: z.object({ 81 | version: z.string(), 82 | lastModified: z.number(), 83 | author: z.string().optional(), 84 | description: z.string().optional(), 85 | templateType: z.enum(['optimize', 'iterate']) 86 | }), 87 | isBuiltin: z.boolean().optional() 88 | }); -------------------------------------------------------------------------------- /packages/core/src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | runtime_config?: { 3 | OPENAI_API_KEY?: string; 4 | GEMINI_API_KEY?: string; 5 | DEEPSEEK_API_KEY?: string; 6 | SILICONFLOW_API_KEY?: string; 7 | CUSTOM_API_KEY?: string; 8 | CUSTOM_API_BASE_URL?: string; 9 | CUSTOM_API_MODEL?: string; 10 | [key: string]: string | undefined; 11 | }; 12 | } -------------------------------------------------------------------------------- /packages/core/src/utils/environment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 环境工具函数 3 | */ 4 | 5 | // 存储Vercel环境检测结果的缓存 6 | let vercelStatusCache: { available: boolean; checked: boolean } = { 7 | available: false, 8 | checked: false 9 | }; 10 | 11 | /** 12 | * 检查是否在浏览器环境中 13 | */ 14 | export const isBrowser = (): boolean => { 15 | return typeof window !== 'undefined'; 16 | }; 17 | 18 | /** 19 | * 检测Vercel API是否可用 20 | * 使用异步方式检测,结果会被缓存 21 | */ 22 | export const checkVercelApiAvailability = async (): Promise => { 23 | // 如果已经检查过,直接返回缓存结果 24 | if (vercelStatusCache.checked) { 25 | return vercelStatusCache.available; 26 | } 27 | 28 | if (!isBrowser()) { 29 | vercelStatusCache = { available: false, checked: true }; 30 | return false; 31 | } 32 | 33 | try { 34 | // 获取当前域名作为基础URL 35 | const origin = window.location.origin; 36 | const response = await fetch(`${origin}/api/vercel-status`, { 37 | method: 'GET', 38 | headers: { 'Content-Type': 'application/json' }, 39 | // 设置较短的超时时间,避免长时间等待 40 | signal: AbortSignal.timeout(3000) 41 | }); 42 | 43 | // 检查响应状态,只有200状态码且内容proxySupport为true 44 | if (response.status !== 200) { 45 | vercelStatusCache = { available: false, checked: true }; 46 | console.log('[环境检测] 未检测到Vercel部署环境,代理功能不可用'); 47 | return false; 48 | } 49 | 50 | // 解析JSON响应 51 | const data = await response.json(); 52 | const isAvailable = data.status === 'available' && data.proxySupport === true; 53 | 54 | vercelStatusCache = { available: isAvailable, checked: true }; 55 | 56 | if (isAvailable) { 57 | console.log('[环境检测] 检测到Vercel部署环境,代理功能可用'); 58 | } else { 59 | console.log('[环境检测] 未检测到Vercel部署环境,代理功能不可用'); 60 | } 61 | 62 | return isAvailable; 63 | } catch (error) { 64 | console.log('[环境检测] Vercel API检测失败', error); 65 | vercelStatusCache = { available: false, checked: true }; 66 | return false; 67 | } 68 | }; 69 | 70 | /** 71 | * 重置环境检测缓存 72 | * 用于在需要重新检测时调用 73 | */ 74 | export const resetVercelStatusCache = (): void => { 75 | vercelStatusCache = { available: false, checked: false }; 76 | }; 77 | 78 | /** 79 | * 检查是否在Vercel环境中(同步版本,使用缓存结果) 80 | */ 81 | export const isVercel = (): boolean => { 82 | // 如果未检查过,返回false,应用需要先调用异步检测方法 83 | return vercelStatusCache.checked && vercelStatusCache.available; 84 | }; 85 | 86 | /** 87 | * 获取API代理URL 88 | * @param baseURL 原始基础URL 89 | * @param isStream 是否是流式请求 90 | */ 91 | export const getProxyUrl = (baseURL: string | undefined, isStream: boolean = false): string => { 92 | if (!baseURL) { 93 | return ''; 94 | } 95 | 96 | // 获取当前域名作为基础URL 97 | const origin = isBrowser() ? window.location.origin : ''; 98 | const proxyEndpoint = isStream ? 'stream' : 'proxy'; 99 | 100 | // 返回完整的绝对URL 101 | return `${origin}/api/${proxyEndpoint}?targetUrl=${encodeURIComponent(baseURL)}`; 102 | }; -------------------------------------------------------------------------------- /packages/core/tests/integration/llm/common.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | createLLMService, 3 | ModelManager, 4 | APIError, 5 | RequestConfigError, 6 | ERROR_MESSAGES 7 | } from '../../../src/index.js'; 8 | import { expect, describe, it, beforeEach, beforeAll } from 'vitest'; 9 | import dotenv from 'dotenv'; 10 | import path from 'path'; 11 | 12 | // 加载环境变量 13 | beforeAll(() => { 14 | dotenv.config({ path: path.resolve(process.cwd(), '.env.local') }); 15 | console.log('环境变量加载状态:', { 16 | OPENAI_API_KEY: !!process.env.OPENAI_API_KEY, 17 | CUSTOM_API_KEY: !!process.env.VITE_CUSTOM_API_KEY, 18 | GEMINI_API_KEY: !!process.env.VITE_GEMINI_API_KEY, 19 | DEEPSEEK_API_KEY: !!process.env.VITE_DEEPSEEK_API_KEY 20 | }); 21 | }); 22 | 23 | describe('LLM 服务通用测试', () => { 24 | let llmService; 25 | let modelManager; 26 | 27 | beforeEach(() => { 28 | modelManager = new ModelManager(); 29 | // 清除所有已有模型 30 | const models = modelManager.getAllModels(); 31 | models.forEach(model => { 32 | if (model.key) { 33 | modelManager.deleteModel(model.key); 34 | } 35 | }); 36 | llmService = createLLMService(modelManager); 37 | }); 38 | 39 | describe('API 调用错误处理', () => { 40 | it('应该能正确处理无效的消息格式', async () => { 41 | const testModel = 'test-invalid-message'; 42 | modelManager.addModel(testModel, { 43 | name: 'Test Model', 44 | baseURL: 'https://test.api/chat/completions', 45 | models: ['test-model'], 46 | defaultModel: 'test-model', 47 | apiKey: 'test-key', 48 | enabled: true, 49 | provider: 'openai' 50 | }); 51 | 52 | await expect(async () => { 53 | await llmService.sendMessage([ 54 | { role: 'invalid', content: '测试消息' } 55 | ], testModel); 56 | }).rejects.toThrow(RequestConfigError); 57 | }); 58 | 59 | it('应该能正确处理未启用的模型', async () => { 60 | const testModel = 'test-disabled'; 61 | modelManager.addModel(testModel, { 62 | name: 'Test Model', 63 | baseURL: 'https://test.api/chat/completions', 64 | models: ['test-model'], 65 | defaultModel: 'test-model', 66 | apiKey: 'test-key', 67 | enabled: false, 68 | provider: 'openai' 69 | }); 70 | 71 | const messages = [ 72 | { role: 'user', content: '你好,我们来玩个游戏' } 73 | ]; 74 | 75 | await expect(async () => { 76 | await llmService.sendMessage(messages, testModel); 77 | }).rejects.toThrow(RequestConfigError); 78 | }); 79 | 80 | it('应该能正确处理空消息列表', async () => { 81 | const testModel = 'test-empty-messages'; 82 | modelManager.addModel(testModel, { 83 | name: 'Test Model', 84 | baseURL: 'https://test.api/chat/completions', 85 | models: ['test-model'], 86 | defaultModel: 'test-model', 87 | apiKey: 'test-key', 88 | enabled: true, 89 | provider: 'openai' 90 | }); 91 | 92 | await expect(async () => { 93 | await llmService.sendMessage([], testModel); 94 | }).rejects.toThrow(RequestConfigError); 95 | }); 96 | }); 97 | 98 | describe('配置管理', () => { 99 | it('应该能正确处理模型配置更新', () => { 100 | const testModel = 'test-update'; 101 | const config = { 102 | name: 'Test Model', 103 | baseURL: 'https://test.api/chat/completions', 104 | models: ['test-model'], 105 | defaultModel: 'test-model', 106 | apiKey: 'test-key', 107 | enabled: true, 108 | provider: 'openai' 109 | }; 110 | 111 | modelManager.addModel(testModel, config); 112 | expect(modelManager.getModel(testModel)).toBeDefined(); 113 | 114 | const newConfig = { 115 | name: 'Updated Model', 116 | baseURL: 'https://updated.api/chat/completions' 117 | }; 118 | 119 | modelManager.updateModel(testModel, newConfig); 120 | const updatedModel = modelManager.getModel(testModel); 121 | expect(updatedModel.name).toBe(newConfig.name); 122 | expect(updatedModel.baseURL).toBe(newConfig.baseURL); 123 | expect(updatedModel.models).toEqual(config.models); 124 | expect(updatedModel.defaultModel).toBe(config.defaultModel); 125 | expect(updatedModel.enabled).toBe(config.enabled); 126 | }); 127 | 128 | it('应该能正确处理模型的启用和禁用', () => { 129 | const testModel = 'test-enable-disable'; 130 | const config = { 131 | name: 'Test Model', 132 | baseURL: 'https://test.api/chat/completions', 133 | models: ['test-model'], 134 | defaultModel: 'test-model', 135 | apiKey: 'test-key', 136 | enabled: true, 137 | provider: 'openai' 138 | }; 139 | 140 | modelManager.addModel(testModel, config); 141 | const model = modelManager.getModel(testModel); 142 | expect(model.enabled).toBe(true); 143 | 144 | modelManager.updateModel(testModel, { enabled: false }); 145 | expect(modelManager.getModel(testModel).enabled).toBe(false); 146 | 147 | modelManager.updateModel(testModel, { enabled: true }); 148 | expect(modelManager.getModel(testModel).enabled).toBe(true); 149 | }); 150 | }); 151 | }); -------------------------------------------------------------------------------- /packages/core/tests/integration/llm/custom.test.js: -------------------------------------------------------------------------------- 1 | import { createLLMService, ModelManager } from '../../../src/index.js'; 2 | import { expect, describe, it, beforeEach, beforeAll } from 'vitest'; 3 | import dotenv from 'dotenv'; 4 | import path from 'path'; 5 | 6 | // 加载环境变量 7 | beforeAll(() => { 8 | dotenv.config({ path: path.resolve(process.cwd(), '.env.local') }); 9 | }); 10 | 11 | describe('自定义模型测试', () => { 12 | let llmService; 13 | let modelManager; 14 | 15 | beforeEach(() => { 16 | localStorage.clear(); 17 | modelManager = new ModelManager(); 18 | llmService = createLLMService(modelManager); 19 | }); 20 | 21 | it('应该能正确加载和使用自定义模型', () => { 22 | const model = modelManager.getModel('custom'); 23 | 24 | expect(model).toBeDefined(); 25 | expect(model.name).toBe('Custom'); 26 | 27 | // 处理环境变量可能为空的情况 28 | if (process.env.VITE_CUSTOM_API_BASE_URL) { 29 | expect(model.baseURL).toBe(process.env.VITE_CUSTOM_API_BASE_URL); 30 | } 31 | 32 | if (process.env.VITE_CUSTOM_API_MODEL) { 33 | expect(model.models).toEqual([process.env.VITE_CUSTOM_API_MODEL]); 34 | expect(model.defaultModel).toBe(process.env.VITE_CUSTOM_API_MODEL); 35 | } 36 | 37 | expect(model.enabled).toBe(!!process.env.VITE_CUSTOM_API_KEY); 38 | }); 39 | 40 | it('应该能正确处理自定义模型的配置更新', () => { 41 | const updatedConfig = { 42 | name: 'Updated Custom Model', 43 | baseURL: process.env.VITE_CUSTOM_API_BASE_URL || 'https://api.custom.test', 44 | models: [process.env.VITE_CUSTOM_API_MODEL || 'test-model'], 45 | defaultModel: process.env.VITE_CUSTOM_API_MODEL || 'test-model', 46 | enabled: true, 47 | provider: 'custom' 48 | }; 49 | 50 | modelManager.updateModel('custom', updatedConfig); 51 | const model = modelManager.getModel('custom'); 52 | 53 | expect(model.name).toBe(updatedConfig.name); 54 | expect(model.baseURL).toBe(updatedConfig.baseURL); 55 | expect(model.models).toEqual(updatedConfig.models); 56 | expect(model.defaultModel).toBe(updatedConfig.defaultModel); 57 | }); 58 | 59 | it('应该能正确调用自定义模型的 API', async () => { 60 | if (!process.env.VITE_CUSTOM_API_KEY) { 61 | console.log('跳过测试:未设置 VITE_CUSTOM_API_KEY 环境变量'); 62 | return; 63 | } 64 | 65 | const messages = [ 66 | { role: 'user', content: '你好,请用一句话介绍你自己' } 67 | ]; 68 | 69 | const response = await llmService.sendMessage(messages, 'custom'); 70 | expect(response).toBeDefined(); 71 | expect(typeof response).toBe('string'); 72 | expect(response.length).toBeGreaterThan(0); 73 | }, 25000); 74 | 75 | it('应该能正确处理自定义模型的多轮对话', async () => { 76 | if (!process.env.VITE_CUSTOM_API_KEY) { 77 | console.log('跳过测试:未设置 VITE_CUSTOM_API_KEY 环境变量'); 78 | return; 79 | } 80 | 81 | const messages = [ 82 | { role: 'user', content: '你好' }, 83 | { role: 'assistant', content: '你好!有什么我可以帮你的吗?' }, 84 | { role: 'user', content: '再见' } 85 | ]; 86 | 87 | const response = await llmService.sendMessage(messages, 'custom'); 88 | expect(response).toBeDefined(); 89 | expect(typeof response).toBe('string'); 90 | expect(response.length).toBeGreaterThan(0); 91 | }, 25000); 92 | }); -------------------------------------------------------------------------------- /packages/core/tests/integration/llm/deepseek.test.js: -------------------------------------------------------------------------------- 1 | import { createLLMService, ModelManager } from '../../../src/index.js'; 2 | import { expect, describe, it, beforeEach, beforeAll } from 'vitest'; 3 | import dotenv from 'dotenv'; 4 | import path from 'path'; 5 | 6 | // 加载环境变量 7 | beforeAll(() => { 8 | dotenv.config({ path: path.resolve(process.cwd(), '.env.local') }); 9 | }); 10 | 11 | describe('DeepSeek API 测试', () => { 12 | // 跳过没有设置 API 密钥的测试 13 | const apiKey = process.env.VITE_DEEPSEEK_API_KEY; 14 | if (!apiKey) { 15 | console.log('跳过 DeepSeek 测试:未设置 VITE_DEEPSEEK_API_KEY 环境变量'); 16 | it.skip('应该能正确调用 DeepSeek API', () => {}); 17 | it.skip('应该能正确处理多轮对话', () => {}); 18 | return; 19 | } 20 | 21 | it('应该能正确调用 DeepSeek API', async () => { 22 | const modelManager = new ModelManager(); 23 | const llmService = createLLMService(modelManager); 24 | 25 | // 更新 DeepSeek 配置 26 | modelManager.updateModel('deepseek', { 27 | apiKey, 28 | enabled: true 29 | }); 30 | 31 | const messages = [ 32 | { role: 'user', content: '你好,请用一句话介绍你自己' } 33 | ]; 34 | 35 | const response = await llmService.sendMessage(messages, 'deepseek'); 36 | expect(response).toBeDefined(); 37 | expect(typeof response).toBe('string'); 38 | expect(response.length).toBeGreaterThan(0); 39 | }, 25000); 40 | 41 | it('应该能正确处理多轮对话', async () => { 42 | const modelManager = new ModelManager(); 43 | const llmService = createLLMService(modelManager); 44 | 45 | // 更新 DeepSeek 配置 46 | modelManager.updateModel('deepseek', { 47 | apiKey, 48 | enabled: true 49 | }); 50 | 51 | const messages = [ 52 | { role: 'user', content: '你好,我们来玩个游戏' }, 53 | { role: 'assistant', content: '好啊,你想玩什么游戏?' }, 54 | { role: 'user', content: '我们来玩猜数字游戏,1到100之间' } 55 | ]; 56 | 57 | const response = await llmService.sendMessage(messages, 'deepseek'); 58 | expect(response).toBeDefined(); 59 | expect(typeof response).toBe('string'); 60 | expect(response.length).toBeGreaterThan(0); 61 | }, 25000); 62 | }); -------------------------------------------------------------------------------- /packages/core/tests/integration/llm/gemini.test.js: -------------------------------------------------------------------------------- 1 | import { createLLMService, ModelManager } from '../../../src/index.js'; 2 | import { expect, describe, it, beforeEach, beforeAll } from 'vitest'; 3 | import dotenv from 'dotenv'; 4 | import path from 'path'; 5 | 6 | // 加载环境变量 7 | beforeAll(() => { 8 | dotenv.config({ path: path.resolve(process.cwd(), '.env.local') }); 9 | }); 10 | 11 | describe('Gemini API 测试', () => { 12 | // 跳过没有设置 API 密钥的测试 13 | const apiKey = process.env.VITE_GEMINI_API_KEY; 14 | if (!apiKey) { 15 | console.log('跳过 Gemini 测试:未设置 VITE_GEMINI_API_KEY 环境变量'); 16 | it.skip('应该能正确调用 Gemini API', () => {}); 17 | it.skip('应该能正确处理多轮对话', () => {}); 18 | return; 19 | } 20 | 21 | it('应该能正确调用 Gemini API', async () => { 22 | const modelManager = new ModelManager(); 23 | const llmService = createLLMService(modelManager); 24 | 25 | // 更新 Gemini 配置 26 | modelManager.updateModel('gemini', { 27 | apiKey, 28 | enabled: true 29 | }); 30 | 31 | const messages = [ 32 | { role: 'user', content: '你好,请用一句话介绍你自己' } 33 | ]; 34 | 35 | const response = await llmService.sendMessage(messages, 'gemini'); 36 | expect(response).toBeDefined(); 37 | expect(typeof response).toBe('string'); 38 | expect(response.length).toBeGreaterThan(0); 39 | }, 10000); 40 | 41 | it('应该能正确处理多轮对话', async () => { 42 | const modelManager = new ModelManager(); 43 | const llmService = createLLMService(modelManager); 44 | 45 | // 更新 Gemini 配置 46 | modelManager.updateModel('gemini', { 47 | apiKey, 48 | enabled: true 49 | }); 50 | 51 | const messages = [ 52 | { role: 'user', content: '你好,我们来玩个游戏' }, 53 | { role: 'assistant', content: '好啊,你想玩什么游戏?' }, 54 | { role: 'user', content: '我们来玩猜数字游戏,1到100之间' } 55 | ]; 56 | 57 | const response = await llmService.sendMessage(messages, 'gemini'); 58 | expect(response).toBeDefined(); 59 | expect(typeof response).toBe('string'); 60 | expect(response.length).toBeGreaterThan(0); 61 | }, 10000); 62 | }); -------------------------------------------------------------------------------- /packages/core/tests/llm.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach, vi } from 'vitest'; 2 | import { LLMService, ModelManager, RequestConfigError, APIError } from '../src'; 3 | 4 | // 模拟localStorage 5 | const localStorageMock = { 6 | store: {}, 7 | getItem(key) { 8 | return this.store[key] || null; 9 | }, 10 | setItem(key, value) { 11 | this.store[key] = value.toString(); 12 | }, 13 | clear() { 14 | this.store = {}; 15 | } 16 | }; 17 | 18 | global.localStorage = localStorageMock; 19 | 20 | describe('LLMService', () => { 21 | let llmService; 22 | let modelManager; 23 | const testProvider = 'test-provider'; 24 | const mockModelConfig = { 25 | name: 'Test Model', 26 | provider: 'openai', 27 | apiKey: 'test-key', 28 | baseURL: 'https://api.test.com', 29 | defaultModel: 'test-model', 30 | enabled: true, 31 | models: ['test-model'] 32 | }; 33 | 34 | beforeEach(() => { 35 | // 重置localStorage 36 | localStorageMock.clear(); 37 | modelManager = new ModelManager(); 38 | // 清除所有已有模型 39 | const models = modelManager.getAllModels(); 40 | models.forEach(model => { 41 | if (model.key) { 42 | modelManager.deleteModel(model.key); 43 | } 44 | }); 45 | llmService = new LLMService(modelManager); 46 | }); 47 | 48 | describe('消息验证', () => { 49 | it('应该正确验证消息格式', () => { 50 | const validMessages = [ 51 | { role: 'system', content: 'test' }, 52 | { role: 'user', content: 'test' } 53 | ]; 54 | expect(() => llmService['validateMessages'](validMessages)).not.toThrow(); 55 | }); 56 | 57 | it('当消息格式无效时应抛出错误', () => { 58 | const invalidMessages = [ 59 | { role: 'invalid', content: 'test' } 60 | ]; 61 | expect(() => llmService['validateMessages'](invalidMessages)) 62 | .toThrow('不支持的消息类型: invalid'); 63 | }); 64 | }); 65 | 66 | describe('模型配置验证', () => { 67 | it('应该正确验证模型配置', () => { 68 | expect(() => llmService['validateModelConfig'](mockModelConfig)).not.toThrow(); 69 | }); 70 | 71 | it('当模型配置无效时应抛出错误', () => { 72 | const invalidConfig = { ...mockModelConfig, apiKey: '' }; 73 | expect(() => llmService['validateModelConfig'](invalidConfig)) 74 | .toThrow('API密钥不能为空'); 75 | }); 76 | 77 | it('当模型未启用时应抛出错误', () => { 78 | const disabledConfig = { ...mockModelConfig, enabled: false }; 79 | expect(() => llmService['validateModelConfig'](disabledConfig)) 80 | .toThrow('模型未启用'); 81 | }); 82 | }); 83 | 84 | describe('发送消息', () => { 85 | 86 | it('当提供商不存在时应抛出错误', async () => { 87 | const messages = [ 88 | { role: 'system', content: 'test' } 89 | ]; 90 | await expect(llmService.sendMessage(messages, 'non-existent-provider')) 91 | .rejects 92 | .toThrow(RequestConfigError); 93 | }); 94 | 95 | it('当消息格式无效时应抛出错误', async () => { 96 | modelManager.addModel(testProvider, mockModelConfig); 97 | 98 | const invalidMessages = [ 99 | { role: 'invalid', content: 'test' } 100 | ]; 101 | await expect(llmService.sendMessage(invalidMessages, testProvider)) 102 | .rejects 103 | .toThrow(RequestConfigError); 104 | }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /packages/core/tests/setup.js: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest' 2 | import dotenv from 'dotenv' 3 | import path from 'path' 4 | 5 | // 加载环境变量(从项目根目录加载) 6 | dotenv.config({ path: path.resolve(process.cwd(), '../../.env.local') }) 7 | 8 | // 模拟 localStorage 9 | const localStorageMock = { 10 | store: new Map(), 11 | getItem: vi.fn((key) => { 12 | return localStorageMock.store.get(key) || null; 13 | }), 14 | setItem: vi.fn((key, value) => { 15 | localStorageMock.store.set(key, value); 16 | }), 17 | removeItem: vi.fn((key) => { 18 | localStorageMock.store.delete(key); 19 | }), 20 | clear: vi.fn(() => { 21 | localStorageMock.store.clear(); 22 | }) 23 | }; 24 | 25 | // 全局注入 localStorage 26 | global.localStorage = localStorageMock; 27 | 28 | // 在每个测试之前重置 mock 状态 29 | beforeEach(() => { 30 | localStorageMock.store.clear(); 31 | vi.clearAllMocks(); 32 | }); -------------------------------------------------------------------------------- /packages/core/tests/unit/llm/service.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach } from 'vitest'; 2 | import { 3 | LLMService, 4 | ModelManager, 5 | ModelConfig, 6 | APIError, 7 | RequestConfigError, 8 | Message 9 | } from '../../../src/index'; 10 | 11 | describe('LLMService', () => { 12 | let service: LLMService; 13 | let modelManager: ModelManager; 14 | 15 | beforeEach(() => { 16 | modelManager = new ModelManager(); 17 | service = new LLMService(modelManager); 18 | }); 19 | 20 | const mockModelConfig: ModelConfig = { 21 | name: 'Test Model', 22 | baseURL: 'https://api.test.com', 23 | apiKey: 'test-key', 24 | models: ['model-1'], 25 | defaultModel: 'model-1', 26 | enabled: true, 27 | provider: 'openai' 28 | }; 29 | 30 | const mockMessages: Message[] = [ 31 | { role: 'system', content: 'You are a helpful assistant.' }, 32 | { role: 'user', content: 'Hello!' } 33 | ]; 34 | 35 | describe('validateModelConfig', () => { 36 | it('should throw error when model is disabled', () => { 37 | const disabledConfig = { ...mockModelConfig, enabled: false }; 38 | expect(() => service['validateModelConfig'](disabledConfig)) 39 | .toThrow('模型未启用'); 40 | }); 41 | 42 | it('should throw error when apiKey is missing', () => { 43 | const invalidConfig = { ...mockModelConfig, apiKey: '' }; 44 | expect(() => service['validateModelConfig'](invalidConfig)) 45 | .toThrow('API密钥不能为空'); 46 | }); 47 | 48 | it('should throw error when provider is missing', () => { 49 | const invalidConfig = { ...mockModelConfig, provider: '' }; 50 | expect(() => service['validateModelConfig'](invalidConfig)) 51 | .toThrow('模型提供商不能为空'); 52 | }); 53 | 54 | it('should throw error when defaultModel is missing', () => { 55 | const invalidConfig = { ...mockModelConfig, defaultModel: '' }; 56 | expect(() => service['validateModelConfig'](invalidConfig)) 57 | .toThrow('默认模型不能为空'); 58 | }); 59 | }); 60 | 61 | describe('validateMessages', () => { 62 | it('should validate valid messages', () => { 63 | expect(() => service['validateMessages'](mockMessages)).not.toThrow(); 64 | }); 65 | 66 | it('should throw error for empty messages', () => { 67 | expect(() => service['validateMessages']([])) 68 | .toThrow('消息列表不能为空'); 69 | }); 70 | 71 | it('should throw error for invalid role', () => { 72 | const invalidMessages: Message[] = [{ role: 'invalid' as any, content: 'test' }]; 73 | expect(() => service['validateMessages'](invalidMessages)) 74 | .toThrow('不支持的消息类型: invalid'); 75 | }); 76 | 77 | it('should throw error for missing content', () => { 78 | const invalidMessages: Message[] = [{ role: 'user', content: '' }]; 79 | expect(() => service['validateMessages'](invalidMessages)) 80 | .toThrow('消息格式无效: 缺少必要字段'); 81 | }); 82 | }); 83 | }); -------------------------------------------------------------------------------- /packages/core/tests/unit/model/manager.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach } from 'vitest'; 2 | import { 3 | ModelManager, 4 | ModelConfig, 5 | ModelConfigError 6 | } from '../../../src/index'; 7 | 8 | describe('ModelManager', () => { 9 | let manager: ModelManager; 10 | let mockModelConfig: ModelConfig; 11 | 12 | beforeEach(() => { 13 | mockModelConfig = { 14 | name: 'Test Model', 15 | baseURL: 'https://test.api', 16 | apiKey: 'test-key', 17 | models: ['test-model'], 18 | defaultModel: 'test-model', 19 | enabled: true, 20 | provider: 'openai' 21 | }; 22 | 23 | manager = new ModelManager(); 24 | // 清除默认模型 25 | const defaultModels = manager.getAllModels(); 26 | defaultModels.forEach(model => { 27 | if (model.key) { 28 | manager.deleteModel(model.key); 29 | } 30 | }); 31 | }); 32 | 33 | describe('addModel', () => { 34 | it('should add a valid model configuration', () => { 35 | manager.addModel('test', mockModelConfig); 36 | const models = manager.getAllModels(); 37 | expect(models).toHaveLength(1); 38 | expect(models[0]).toMatchObject({ ...mockModelConfig, key: 'test' }); 39 | }); 40 | 41 | it('should throw error when adding duplicate model', () => { 42 | manager.addModel('test', mockModelConfig); 43 | expect(() => manager.addModel('test', mockModelConfig)) 44 | .toThrow(ModelConfigError); 45 | }); 46 | 47 | it('should throw error when adding invalid config', () => { 48 | const invalidConfig = { ...mockModelConfig, name: '' }; 49 | expect(() => manager.addModel('test', invalidConfig)) 50 | .toThrow(ModelConfigError); 51 | }); 52 | }); 53 | 54 | describe('updateModel', () => { 55 | it('should update existing model', () => { 56 | manager.addModel('test', mockModelConfig); 57 | const updateConfig = { 58 | name: 'Updated Model', 59 | baseURL: 'https://updated.api' 60 | }; 61 | manager.updateModel('test', updateConfig); 62 | const model = manager.getModel('test'); 63 | if (!model) { 64 | throw new Error('Model should exist'); 65 | } 66 | expect(model.name).toBe('Updated Model'); 67 | expect(model.baseURL).toBe('https://updated.api'); 68 | }); 69 | 70 | it('should throw error when updating non-existent model', () => { 71 | expect(() => manager.updateModel('non-existent', { name: 'New Name' })) 72 | .toThrow(ModelConfigError); 73 | }); 74 | 75 | it('should throw error when update makes config invalid', () => { 76 | manager.addModel('test', mockModelConfig); 77 | expect(() => manager.updateModel('test', { name: '' })) 78 | .toThrow(ModelConfigError); 79 | }); 80 | }); 81 | 82 | describe('deleteModel', () => { 83 | it('should delete existing model', () => { 84 | manager.addModel('test', mockModelConfig); 85 | manager.deleteModel('test'); 86 | expect(manager.getModel('test')).toBeUndefined(); 87 | }); 88 | 89 | it('should throw error when deleting non-existent model', () => { 90 | expect(() => manager.deleteModel('non-existent')) 91 | .toThrow(ModelConfigError); 92 | }); 93 | }); 94 | 95 | describe('getModel', () => { 96 | it('should return model by key', () => { 97 | manager.addModel('test', mockModelConfig); 98 | const model = manager.getModel('test'); 99 | if (!model) { 100 | throw new Error('Model should exist'); 101 | } 102 | expect(model).toMatchObject(mockModelConfig); 103 | }); 104 | 105 | it('should return undefined for non-existent model', () => { 106 | expect(manager.getModel('non-existent')).toBeUndefined(); 107 | }); 108 | }); 109 | }); -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | "moduleResolution": "bundler", 9 | "allowImportingTsExtensions": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "strict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "declaration": true, 18 | "outDir": "dist" 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "dist", "tests"] 22 | } -------------------------------------------------------------------------------- /packages/core/vitest.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import { loadEnv } from 'vite' 3 | import path from 'path' 4 | 5 | export default defineConfig(({ mode }) => { 6 | // 加载环境变量 7 | process.env = { ...process.env, ...loadEnv(mode, process.cwd()) } 8 | 9 | return { 10 | test: { 11 | globals: true, 12 | environment: 'node', 13 | setupFiles: ['./tests/setup.js'], 14 | // 环境变量配置 15 | env: { 16 | ...process.env 17 | } 18 | } 19 | } 20 | }) -------------------------------------------------------------------------------- /packages/extension/chrome.md: -------------------------------------------------------------------------------- 1 | # 提示词优化器 - Chrome插件上架材料 2 | 3 | 本文档包含将"提示词优化器"上架到Chrome应用商店所需的所有材料和文案。 4 | 5 | ## 基本信息 6 | 7 | ### 插件名称 8 | - **中文**:提示词优化器 9 | - **英文**:Prompt Optimizer 10 | 11 | ### 插件简短描述(132字符以内) 12 | - **中文**:智能提示词增强工具:一键优化、改进提示词质量,提升AI回复准确度。多模型支持(OpenAI/Gemini/DeepSeek),纯客户端处理确保数据安全,无需中间服务器。 13 | - **英文**:Smart prompt enhancement tool: One-click optimization to improve prompt quality and AI response accuracy. Multi-model support (OpenAI/Gemini/DeepSeek), client-side processing for data security, no intermediary servers. 14 | 15 | ### 版本号 16 | 1.0.0 17 | 18 | ### 支持语言 19 | - 简体中文 20 | - 英文(计划中) 21 | 22 | ## 详细描述 23 | 24 | ### 中文详细描述 25 | 提示词优化器是一款专为AI对话体验提升设计的Chrome扩展,让您能够轻松优化提示词,获得更精准的AI回复。作为纯客户端应用,它不依赖任何自有服务器,所有数据存储和处理都在您的浏览器本地完成,仅在优化提示词时直接从您的浏览器调用AI服务提供商的API。 26 | 27 | **核心功能:** 28 | 29 | ✅ **纯客户端架构** - 无自有服务器,所有数据存储和处理在本地完成 30 | ✅ **直接API调用** - 优化请求直接从您的浏览器发送到AI服务提供商,不经过中间服务器 31 | ✅ **多模型支持** - 集成OpenAI、Gemini、DeepSeek等多个主流AI模型 32 | ✅ **个性化配置** - 自定义API密钥和模型参数,完全掌控您的AI体验 33 | ✅ **本地存储** - 安全存储历史记录和设置,保护您的隐私 34 | ✅ **操作简便** - 界面简洁直观,操作流程顺畅自然 35 | ✅ **安全可靠** - API密钥加密存储,确保数据安全 36 | 37 | **使用场景:** 38 | 39 | - 优化ChatGPT、Claude等AI平台的提示词 40 | - 改进Google Gemini、Microsoft Copilot等搜索提示 41 | - 提升AI内容生成质量 42 | - 调整AI代码生成指令 43 | - 优化AI绘图提示词 44 | 45 | **如何使用:** 46 | 47 | 1. 在任意网页中选中您想优化的提示词文本 48 | 2. 点击右键,选择"提示词优化器" 49 | 3. 在弹出窗口中查看优化结果 50 | 4. 一键复制优化后的提示词 51 | 52 | **隐私与安全:** 53 | 54 | - 纯客户端应用,没有自有服务器收集数据 55 | - 所有AI模型调用直接从您的浏览器发起到AI服务提供商 56 | - 所有API密钥均在本地加密存储 57 | - 不收集任何个人信息 58 | - 不追踪用户行为或浏览历史 59 | - 开源项目,代码透明可查 60 | 61 | **注意事项:** 62 | 虽然本扩展是纯客户端应用,但优化提示词的功能需要调用第三方AI服务提供商的API。这些API调用直接从您的浏览器发起,不经过我们的服务器,但会将您的提示词内容发送到相应的AI服务提供商(如OpenAI、Google等)。 63 | 64 | ### 英文详细描述 65 | Prompt Optimizer is a Chrome extension designed to enhance your AI conversation experience, allowing you to easily optimize prompts for more accurate AI responses. As a pure client-side application, it operates without any proprietary servers, with all data storage and processing done locally in your browser, only directly calling AI service providers' APIs from your browser when optimizing prompts. 66 | 67 | **Core Features:** 68 | 69 | ✅ **One-Click Optimization** - Select text on any webpage and optimize prompts with a right-click 70 | ✅ **Pure Client-Side Architecture** - No proprietary servers, all data storage and processing done locally 71 | ✅ **Direct API Calls** - Optimization requests sent directly from your browser to AI service providers, without passing through intermediate servers 72 | ✅ **Multi-Model Support** - Integrated with OpenAI, Gemini, DeepSeek and other mainstream AI models 73 | ✅ **Personalized Configuration** - Customize API keys and model parameters for complete control 74 | ✅ **Local Storage** - Securely store history and settings to protect your privacy 75 | ✅ **Easy Operation** - Clean and intuitive interface with smooth workflow 76 | ✅ **Security** - Encrypted storage of API keys ensuring data security 77 | 78 | **Use Cases:** 79 | 80 | - Optimize prompts for ChatGPT, Claude and other AI platforms 81 | - Improve search prompts for Google Gemini and Microsoft Copilot 82 | - Enhance AI content generation quality 83 | - Adjust AI code generation instructions 84 | - Optimize AI image generation prompts 85 | 86 | **How to Use:** 87 | 88 | 1. Select the prompt text you want to optimize on any webpage 89 | 2. Right-click and select "Prompt Optimizer" 90 | 3. View the optimized result in the popup window 91 | 4. Copy the optimized prompt with one click 92 | 93 | **Privacy & Security:** 94 | 95 | - Pure client-side app with no proprietary servers collecting data 96 | - All AI model calls made directly from your browser to AI service providers 97 | - All API keys are encrypted and stored locally 98 | - No personal information is collected 99 | - No tracking of user behavior or browsing history 100 | - Open-source project with transparent code 101 | 102 | **Important Note:** 103 | While this extension is a pure client-side application, the prompt optimization feature requires calling third-party AI service providers' APIs. These API calls are made directly from your browser without passing through our servers, but they will send your prompt content to the respective AI service provider (such as OpenAI, Google, etc.). 104 | 105 | ## 分类和标签 106 | 107 | ### 主要类别 108 | 生产力工具 109 | 110 | ### 次要类别 111 | 写作工具 112 | 113 | ### 关键词标签 114 | - AI 115 | - 提示词 116 | - Prompt 117 | - ChatGPT 118 | - 优化 119 | - Gemini 120 | - DeepSeek 121 | - 生产力 122 | - 纯客户端 123 | - 本地存储 124 | 125 | ## 图片资源 126 | 127 | ### 图标尺寸要求 128 | 需准备以下尺寸的PNG格式图标: 129 | - 16x16 130 | - 32x32 131 | - 48x48 132 | - 128x128 133 | 134 | ### 截图要求 135 | - 至少提供1张截图(1280x800或640x400像素) 136 | - 推荐提供3-5张截图展示不同功能 137 | - 确保截图清晰展示插件的主要功能 138 | - 避免在截图中包含个人信息 139 | 140 | ### 宣传图片(可选) 141 | - 尺寸:1400x560像素 142 | - 格式:PNG或JPEG 143 | - 用途:在Chrome网上应用商店详情页顶部展示 144 | 145 | ## 其他必要信息 146 | 147 | ### 网站URL 148 | https://github.com/linshenkx/prompt-optimizer 149 | 150 | ### 支持页面URL 151 | https://github.com/linshenkx/prompt-optimizer/issues 152 | 153 | ### 隐私政策URL 154 | https://github.com/linshenkx/prompt-optimizer/blob/main/packages/extension/privacy-policy.md 155 | 156 | ## 提交清单 157 | 158 | 在提交到Chrome应用商店前,请确保准备以下材料: 159 | 160 | - [ ] 完整的manifest.json文件 161 | - [ ] ZIP格式的插件包 162 | - [ ] 所有尺寸的图标 163 | - [ ] 至少1张截图 164 | - [ ] 宣传图片(可选) 165 | - [ ] 详细描述文本 166 | - [ ] 隐私政策页面 167 | 168 | ## 注意事项 169 | 170 | 1. 确保插件功能符合[Chrome应用商店开发者计划政策](https://developer.chrome.com/docs/webstore/program-policies/) 171 | 2. 功能描述要准确,避免夸大或误导 172 | 3. 所有图片资源要清晰、专业 173 | 4. 隐私政策必须明确说明如何处理用户数据 174 | 5. 测试所有功能,确保在不同环境下正常工作 175 | 6. 版本更新时保持递增的版本号 -------------------------------------------------------------------------------- /packages/extension/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } -------------------------------------------------------------------------------- /packages/extension/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Prompt Optimizer 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@prompt-optimizer/extension", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "test": "vitest", 11 | "test:coverage": "vitest run --coverage" 12 | }, 13 | "dependencies": { 14 | "@prompt-optimizer/ui": "workspace:*", 15 | "element-plus": "^2.9.3", 16 | "uuid": "^11.0.5", 17 | "vue": "^3.5.13" 18 | }, 19 | "devDependencies": { 20 | "@tailwindcss/forms": "^0.5.10", 21 | "@tailwindcss/typography": "^0.5.16", 22 | "@tsconfig/node18": "^18.2.4", 23 | "@types/node": "^22.13.4", 24 | "@types/uuid": "^10.0.0", 25 | "@vitejs/plugin-basic-ssl": "^1.2.0", 26 | "@vitejs/plugin-vue": "^5.2.1", 27 | "@vue/tsconfig": "^0.5.1", 28 | "autoprefixer": "^10.4.20", 29 | "postcss": "^8.5.1", 30 | "tailwindcss": "^3.4.17", 31 | "typescript": "^5.0.0", 32 | "vite": "^6.0.7", 33 | "vitest": "^3.0.2", 34 | "dotenv": "^16.4.7", 35 | "js-yaml": "^4.1.0" 36 | } 37 | } -------------------------------------------------------------------------------- /packages/extension/permissions-explanation.md: -------------------------------------------------------------------------------- 1 | # 提示词优化器 - 权限说明 2 | 3 | 本文档详细解释了提示词优化器Chrome扩展请求的各项权限及其用途,帮助您了解我们为何需要这些权限以及它们如何用于提供服务。 4 | 5 | ## 单一用途说明 (Single Purpose Description) 6 | 7 | **提示词优化器的主要目的是:优化用户的AI提示词,以获得更精准、高质量的AI回复。** 8 | 9 | 本扩展请求的所有权限都直接服务于这一单一核心目的: 10 | - **存储权限**用于保存用户的API密钥和设置,使用户能够连接到AI服务 11 | - **API域名访问权限**用于直接从用户浏览器发送提示词优化请求到相应的AI服务提供商 12 | - **标签页权限**用于在标签页中打开设置界面,提供更好的用户体验 13 | 14 | 这些权限对于实现扩展的主要功能至关重要,没有这些权限,扩展将无法执行其核心功能。同时,我们严格遵循最小权限原则,仅请求完成核心任务所必需的权限,不收集任何与此核心功能无关的数据。 15 | 16 | ## 请求的权限 17 | 18 | ### 存储权限 (`storage`) 19 | 20 | **用途:** 21 | - 在本地安全存储API密钥 22 | - 保存您的扩展设置和偏好 23 | - 存储提示词优化历史记录(如果启用了此功能) 24 | 25 | **重要说明:** 26 | - 所有数据仅存储在您的本地设备上 27 | - 不会上传到任何服务器 28 | - 您可以随时通过扩展设置清除这些数据 29 | 30 | ### 标签页权限 (`tabs`) 31 | 32 | **用途:** 33 | - 允许扩展在标签页中展示所有核心功能,包括提示词优化、模型管理以及其他用户交互界面 34 | - 提供更宽敞、更灵活的用户体验,让所有功能都在独立标签页中运行 35 | 36 | **重要说明:** 37 | - 扩展在用户主动操作时会在标签页中打开并展示所有功能界面 38 | - 不会在后台监控您的标签页 39 | - 不会记录您的浏览历史或收集标签页数据 40 | - 仅用于提供更好的用户界面体验,所有核心功能均在标签页中完成 41 | 42 | ## 主机权限 (`host_permissions`) 43 | 44 | ### 1. OpenAI API (`https://api.openai.com/*`) 45 | 46 | **用途:** 47 | - 允许扩展直接从您的浏览器向OpenAI的API发送请求 48 | - 用于使用OpenAI模型(如GPT-3.5、GPT-4)优化提示词 49 | 50 | ### 2. Google Gemini API (`https://generativelanguage.googleapis.com/*`) 51 | 52 | **用途:** 53 | - 允许扩展直接从您的浏览器向Google的Gemini API发送请求 54 | - 用于使用Google Gemini模型优化提示词 55 | 56 | ### 3. DeepSeek API (`https://api.deepseek.com/*`) 57 | 58 | **用途:** 59 | - 允许扩展直接从您的浏览器向DeepSeek的API发送请求 60 | - 用于使用DeepSeek模型优化提示词 61 | 62 | ### 4. SiliconFlow API (`https://api.siliconflow.cn/*`) 63 | 64 | **用途:** 65 | - 允许扩展直接从您的浏览器向SiliconFlow的API发送请求 66 | - 用于使用SiliconFlow模型优化提示词 67 | 68 | **重要说明:** 69 | - 所有API请求都直接从您的浏览器发起 70 | - 使用您自己提供的API密钥 71 | - 不经过我们的服务器或任何中间服务器 72 | - 您的提示词内容会发送到相应的AI服务提供商,并受其隐私政策约束 73 | 74 | ## 关于自定义API的说明 75 | 76 | 虽然扩展在manifest中只预先声明了OpenAI、Google Gemini和DeepSeek的API域名权限,但您仍然可以使用自定义API。这是因为: 77 | 78 | 1. **内容安全策略的工作方式**:Chrome扩展的内容安全策略允许扩展在运行时连接到用户明确授权的域名,即使这些域名没有在manifest中预先声明。 79 | 80 | 2. **如何使用自定义API**: 81 | - 在扩展的"模型管理"界面中,点击"添加自定义模型" 82 | - 填写自定义API的相关信息,包括API地址、模型名称和API密钥 83 | - 保存后,您就可以使用这个自定义API了 84 | 85 | 3. **首次连接授权**: 86 | - 当您首次尝试使用自定义API时,Chrome浏览器会显示一个权限请求对话框 87 | - 您需要明确授权扩展连接到这个自定义域名 88 | - 授权后,扩展就可以与该自定义API通信了 89 | 90 | 4. **安全考虑**: 91 | - 这种方式确保了扩展只能连接到您明确授权的域名 92 | - 所有连接仍然是从您的浏览器直接发起的,不经过我们的服务器 93 | - 您可以随时在Chrome的扩展权限设置中撤销这些授权 94 | 95 | 5. **适用场景**: 96 | - 使用公司内部的AI服务 97 | - 连接到其他兼容OpenAI格式的API服务 98 | - 使用自托管的开源模型API 99 | - 连接到任何符合OpenAI兼容格式的API端点 100 | 101 | 这种设计既保证了扩展的灵活性,允许您连接到任何需要的API服务,同时也维护了Chrome扩展的安全模型,确保所有连接都是经过您明确授权的。 102 | 103 | ## 为什么需要特定API域名的访问权限? 104 | 105 | Chrome浏览器的安全机制要求扩展明确声明它需要访问的外部域名。这是一项重要的安全功能,称为"内容安全策略"(Content Security Policy),它限制扩展只能与预先声明的域名通信,防止恶意扩展向未授权的服务器发送数据。 106 | 107 | ### 为什么我们需要这些特定域名的权限: 108 | 109 | 1. **直接API调用的必要条件**: 110 | - 没有这些权限,浏览器会阻止扩展向这些AI服务提供商发送请求 111 | - 这些权限是实现"纯客户端应用"架构的关键,使扩展能够直接从您的浏览器调用AI服务,而无需通过我们的服务器 112 | 113 | 2. **精确的最小权限原则**: 114 | - 我们只请求访问必要的API域名(如OpenAI、Google Gemini、DeepSeek) 115 | - 不请求访问其他不相关的域名 116 | - 每个请求的域名都有明确的用途(提供AI模型服务) 117 | 118 | 3. **透明的数据流向**: 119 | - 这些权限使数据流向完全透明 - 从您的浏览器直接到AI服务提供商 120 | - 没有中间服务器或第三方参与数据传输过程 121 | 122 | 4. **安全性考虑**: 123 | - 使用通配符模式(如`https://api.openai.com/*`)允许访问该域名下的所有路径 124 | - 这是必要的,因为AI服务提供商可能在不同路径提供不同的API端点 125 | - 所有通信都通过安全的HTTPS连接进行 126 | 127 | ### 如果没有这些权限会怎样? 128 | 129 | 如果我们不请求这些特定API域名的访问权限: 130 | 131 | 1. 扩展将无法直接调用AI服务提供商的API 132 | 2. 提示词优化功能将无法工作 133 | 3. 我们将不得不采用服务器中转架构,这会: 134 | - 降低数据隐私性(您的数据需要经过我们的服务器) 135 | - 增加延迟(多一层数据传输) 136 | - 引入额外的安全风险 137 | 138 | ### 用户控制 139 | 140 | 重要的是,即使授予了这些权限: 141 | 142 | - 扩展只会在您主动使用提示词优化功能时才会发送请求 143 | - 您可以随时在扩展设置中更改或删除API密钥,控制是否允许调用这些服务 144 | - 所有API调用都使用您自己提供的API密钥,您完全控制费用和使用情况 145 | 146 | ## 最小权限原则 147 | 148 | 我们遵循最小权限原则,只请求应用程序核心功能所必需的权限。这个扩展只需要: 149 | 150 | 1. `storage` 权限 - 用于本地存储您的设置和API密钥 151 | 2. 特定API域名的访问权限 - 用于直接从您的浏览器调用AI服务 152 | 153 | ## 我们不请求的权限 154 | 155 | 为了保护您的隐私,我们特意不请求以下权限: 156 | 157 | - **浏览历史** (`history`) - 我们不访问您的浏览历史 158 | - **所有网站的访问权限** (`` 作为权限) - 我们不需要访问您浏览的网页内容 159 | - **活动标签页** (`activeTab`) - 我们不需要访问您当前浏览的页面内容 160 | - **上下文菜单** (`contextMenus`) - 我们不添加右键菜单项 161 | - **后台运行权限** (`background` 作为持续运行的后台) - 我们的扩展不会在后台持续运行 162 | - **网络请求拦截** (`webRequest`) - 我们不拦截或修改您的网络请求 163 | - **Cookie访问** (`cookies`) - 我们不读取或修改您的Cookie 164 | 165 | ## 纯客户端架构 166 | 167 | 提示词优化器采用纯客户端架构,这意味着: 168 | 169 | 1. 所有数据存储和处理都在您的本地浏览器中完成 170 | 2. 没有自有服务器收集或处理您的数据 171 | 3. API调用直接从您的浏览器发送到AI服务提供商 172 | 4. 您的API密钥和设置仅存储在您的设备上 173 | 174 | ## 权限使用透明度 175 | 176 | 我们承诺: 177 | 178 | - 仅请求提供核心功能所必需的权限 179 | - 明确解释每项权限的用途 180 | - 不滥用任何授予的权限 181 | - 保持代码开源,确保透明度 182 | 183 | 如果您对我们的权限使用有任何疑问,请通过GitHub Issues联系我们:https://github.com/linshenkx/prompt-optimizer/issues -------------------------------------------------------------------------------- /packages/extension/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /packages/extension/privacy-policy.md: -------------------------------------------------------------------------------- 1 | # 提示词优化器 - 隐私政策 2 | 3 | *最后更新日期:2025年2月1日* 4 | 5 | ## 引言 6 | 7 | 感谢您使用"提示词优化器"Chrome扩展(以下简称"本扩展")。本隐私政策旨在向您说明我们如何收集、使用、存储和保护您的信息。我们高度重视您的隐私,并致力于保护您的个人数据安全。 8 | 9 | **特别说明:本扩展是一个纯客户端应用程序,没有后端服务器,所有数据存储和管理均在您的本地浏览器中完成。但请注意,提示词优化功能需要调用第三方AI服务提供商的API。** 10 | 11 | ## 我们收集的信息 12 | 13 | ### 我们不收集的信息 14 | 15 | 本扩展不会收集、存储或传输以下信息: 16 | - 您的个人身份信息 17 | - 您的浏览历史 18 | - 您访问的网站内容 19 | - 您的位置数据 20 | - 您的设备信息 21 | - 您的提示词内容或使用习惯 22 | 23 | 作为纯客户端应用,我们不运营任何服务器来收集或存储您的信息。 24 | 25 | ### 本地存储的信息 26 | 27 | 本扩展仅在您的本地设备上存储以下信息: 28 | - 您自行提供的API密钥(经过加密存储) 29 | - 您的扩展设置和偏好 30 | - 提示词优化历史记录(仅当您启用历史记录功能时) 31 | 32 | 这些信息完全存储在您的本地设备上,不会被传输到任何我们的服务器。所有数据操作都在您的浏览器内部处理,我们无法访问这些信息。 33 | 34 | ## 数据传输 35 | 36 | 当您使用本扩展优化提示词时,您的提示词内容将直接从您的浏览器发送到您配置的AI服务提供商(如OpenAI、Google Gemini或DeepSeek)的API。这些传输: 37 | 38 | 1. 使用您自己提供的API密钥进行身份验证 39 | 2. 通过HTTPS加密连接传输 40 | 3. 受相应API服务提供商的隐私政策和条款约束 41 | 4. 不经过我们的服务器或任何中间服务器 42 | 5. 完全在您的浏览器中发起和处理 43 | 44 | **重要说明:** 45 | - 我们没有服务器来拦截、存储或处理您的API请求 46 | - 所有API请求直接从您的浏览器发送到相应的AI服务提供商,并且响应直接返回到您的浏览器 47 | - 虽然本扩展是纯客户端应用,但优化提示词的功能确实需要将您的提示词内容发送到第三方AI服务提供商(如OpenAI、Google等) 48 | - 这些第三方服务提供商将根据其各自的隐私政策处理您的数据 49 | 50 | ## 第三方服务 51 | 52 | 本扩展可能使用以下第三方服务: 53 | - **OpenAI API**:当您选择使用OpenAI模型时 54 | - **Google Gemini API**:当您选择使用Gemini模型时 55 | - **DeepSeek API**:当您选择使用DeepSeek模型时 56 | - **其他自定义API**:当您配置自定义API终端点时 57 | 58 | 请注意,这些第三方服务有自己的隐私政策和数据处理实践。我们建议您查阅这些服务的隐私政策,了解他们如何处理您的数据。由于本扩展是纯客户端应用,您的浏览器将直接与这些服务通信,不经过任何中间服务器。 59 | 60 | ## 数据安全 61 | 62 | 我们采取以下措施保护您的数据: 63 | - 纯客户端架构,没有自有服务器收集数据 64 | - 所有API密钥都使用浏览器的安全存储机制进行加密存储 65 | - 所有数据传输都通过HTTPS加密连接直接进行 66 | - 不在任何外部服务器上存储您的个人数据 67 | - 所有数据管理和存储在您的本地浏览器中完成 68 | 69 | ## 数据处理的明确说明 70 | 71 | 为了避免任何误解,我们特此明确说明数据处理流程: 72 | 73 | 1. **本地数据存储和管理**:所有设置、配置和历史记录都存储在您的本地浏览器中 74 | 2. **提示词优化过程**: 75 | - 当您请求优化提示词时,您的提示词内容将从您的浏览器直接发送到您选择的AI服务提供商(如OpenAI) 76 | - 这些AI服务提供商会处理您的提示词并返回优化结果 77 | - 整个过程不经过我们的服务器 78 | - 您的提示词内容和优化结果会受到相应AI服务提供商隐私政策的约束 79 | 80 | ## 儿童隐私 81 | 82 | 本扩展不针对13岁以下的儿童,我们也不会故意收集13岁以下儿童的个人信息。作为纯客户端应用,本扩展不会收集任何用户的个人信息。 83 | 84 | ## 政策变更 85 | 86 | 我们可能会不时更新本隐私政策。当我们进行重大变更时,我们将通过扩展更新通知您,并在本页面上发布更新后的隐私政策。 87 | 88 | ## 联系我们 89 | 90 | 如果您对本隐私政策有任何疑问或建议,请通过以下方式联系我们: 91 | - 通过GitHub Issues: https://github.com/linshenkx/prompt-optimizer/issues 92 | 93 | ## 同意 94 | 95 | 通过安装和使用本扩展,您确认您已阅读并同意本隐私政策的条款。您也确认理解本扩展是纯客户端应用程序,在没有自有服务器参与的情况下运行,所有数据存储和管理都在您的本地浏览器中进行,但优化提示词的功能需要将您的提示词内容发送到第三方AI服务提供商。 -------------------------------------------------------------------------------- /packages/extension/public/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Prompt Optimizer" 4 | }, 5 | "extDesc": { 6 | "message": "Large Language Model Prompt Optimization Tool: One-click prompt optimization with multi-round iterative improvements and version tracking. Supports major AI models (OpenAI/Gemini/DeepSeek etc.), fully open-source, client-side processing ensures data security." 7 | }, 8 | "extShortName": { 9 | "message": "Prompt Optimizer" 10 | } 11 | } -------------------------------------------------------------------------------- /packages/extension/public/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "提示词优化器" 4 | }, 5 | "extDesc": { 6 | "message": "大模型提示词优化工具:一键优化提示词并支持多轮迭代改进测试,支持版本回溯。支持主流AI模型(OpenAI/Gemini/DeepSeek等),完全开源,纯客户端处理保障数据安全。" 7 | }, 8 | "extShortName": { 9 | "message": "提示词优化器" 10 | } 11 | } -------------------------------------------------------------------------------- /packages/extension/public/background.js: -------------------------------------------------------------------------------- 1 | chrome.action.onClicked.addListener(() => { 2 | const url = chrome.runtime.getURL('index.html'); 3 | chrome.tabs.create({ url }); 4 | }); -------------------------------------------------------------------------------- /packages/extension/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/packages/extension/public/favicon.ico -------------------------------------------------------------------------------- /packages/extension/public/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/packages/extension/public/icons/icon128.png -------------------------------------------------------------------------------- /packages/extension/public/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/packages/extension/public/icons/icon16.png -------------------------------------------------------------------------------- /packages/extension/public/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/packages/extension/public/icons/icon48.png -------------------------------------------------------------------------------- /packages/extension/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "default_locale": "zh_CN", 4 | "name": "__MSG_extName__", 5 | "version": "1.0.3", 6 | "description": "__MSG_extDesc__", 7 | "icons": { 8 | "16": "icons/icon16.png", 9 | "48": "icons/icon48.png", 10 | "128": "icons/icon128.png" 11 | }, 12 | "action": { 13 | "default_icon": { 14 | "16": "icons/icon16.png", 15 | "48": "icons/icon48.png", 16 | "128": "icons/icon128.png" 17 | } 18 | }, 19 | "background": { 20 | "service_worker": "background.js", 21 | "type": "module" 22 | }, 23 | "permissions": [ 24 | ], 25 | "homepage_url": "https://github.com/linshenkx/prompt-optimizer", 26 | "author": "linshenkx", 27 | "content_security_policy": { 28 | "extension_pages": "script-src 'self'; object-src 'self'; style-src 'self';" 29 | }, 30 | "offline_enabled": true, 31 | "incognito": "split", 32 | "short_name": "__MSG_extShortName__", 33 | "minimum_chrome_version": "88" 34 | } -------------------------------------------------------------------------------- /packages/extension/publish-guide.md: -------------------------------------------------------------------------------- 1 | # Chrome扩展上架指南 2 | 3 | 本文档提供了将提示词优化器发布到Chrome网上应用商店的详细步骤。 4 | 5 | ## 准备工作 6 | 7 | ### 1. 开发者账号注册 8 | 9 | 在上传扩展前,需要先注册Chrome网上应用商店开发者账号: 10 | 11 | 1. 访问 [Chrome网上应用商店开发者控制台](https://chrome.google.com/webstore/devconsole) 12 | 2. 使用Google账号登录 13 | 3. 支付一次性的$5.00 USD开发者注册费 14 | 4. 完成开发者资料设置 15 | 16 | ### 2. 准备插件包 17 | 18 | 1. 确保已完成构建: 19 | ```bash 20 | pnpm build:ext 21 | ``` 22 | 23 | 2. 打包扩展: 24 | - 找到构建后的扩展目录(通常在`packages/extension/dist`) 25 | - 将整个目录打包为ZIP文件 26 | - 确保ZIP文件结构根目录直接包含manifest.json 27 | 28 | ### 3. 准备上架材料 29 | 30 | 根据`chrome.md`文件中列出的清单,确保准备好所有所需材料: 31 | 32 | - 所有尺寸的图标 33 | - 至少1张截图(1280x800或640x400像素) 34 | - 宣传图片(可选,1400x560像素) 35 | - 详细描述文本 36 | - 隐私政策页面 37 | 38 | ## 上传流程 39 | 40 | 1. 访问 [Chrome网上应用商店开发者控制台](https://chrome.google.com/webstore/devconsole) 41 | 2. 点击"添加新项目"按钮 42 | 3. 上传ZIP格式的扩展包 43 | 4. 填写商品详情页信息: 44 | - 语言:选择中文和英文 45 | - 商品详情:复制`chrome.md`中的详细描述 46 | - 类别:选择"生产力工具"和"写作工具" 47 | - 上传图标、截图和宣传图片 48 | - 填写隐私政策链接(可使用GitHub Pages或Vercel托管的隐私政策页面) 49 | 5. 提交审核前,检查所有内容是否完整 50 | 51 | ## 审核流程 52 | 53 | Chrome网上应用商店的审核通常需要几天到两周时间,请耐心等待。 54 | 55 | ### 常见审核问题及解决方案 56 | 57 | 1. **权限问题**: 58 | - 确保manifest.json中只请求必要的权限 59 | - 在描述中明确说明每个权限的用途 60 | 61 | 2. **隐私政策问题**: 62 | - 确保隐私政策完整详尽 63 | - 明确说明如何处理用户数据 64 | 65 | 3. **功能描述问题**: 66 | - 确保描述准确,不夸大功能 67 | - 所有截图需真实反映插件功能 68 | 69 | 4. **安全问题**: 70 | - 确保不存在恶意代码 71 | - 避免使用不安全的API 72 | 73 | ## 发布后维护 74 | 75 | ### 版本更新 76 | 77 | 1. 增加manifest.json中的版本号 78 | 2. 重新构建插件 79 | 3. 打包新版本 80 | 4. 在开发者控制台上传新版本 81 | 5. 填写更新说明 82 | 6. 提交审核 83 | 84 | ### 用户反馈处理 85 | 86 | 1. 定期检查用户评价和反馈 87 | 2. 及时回复用户问题 88 | 3. 根据反馈改进插件功能 89 | 4. 更新常见问题解答 90 | 91 | ## 宣传策略 92 | 93 | 1. **社交媒体宣传**: 94 | - 在技术社区分享(如掘金、知乎、V2EX等) 95 | - 制作简短演示视频 96 | - 编写使用教程和案例 97 | 98 | 2. **SEO优化**: 99 | - 优化Chrome商店描述关键词 100 | - 创建专门的落地页 101 | - 编写相关博客文章 102 | 103 | 3. **用户激励**: 104 | - 鼓励满意用户评价 105 | - 提供反馈奖励机制 106 | - 建立用户社区 107 | 108 | ## 财务管理 109 | 110 | 1. 设置Google商家账户(如适用) 111 | 2. 设置税务信息 112 | 3. 设置付款方式 113 | 4. 了解应用内购买政策(如适用) 114 | 115 | ## 资源链接 116 | 117 | - [Chrome开发者文档](https://developer.chrome.com/docs/webstore/) 118 | - [Chrome商店政策](https://developer.chrome.com/docs/webstore/program-policies/) 119 | - [Chrome扩展最佳实践](https://developer.chrome.com/docs/extensions/mv3/best_practices/) 120 | - [Google商家支持](https://support.google.com/chrome_webstore/) -------------------------------------------------------------------------------- /packages/extension/screenshots.md: -------------------------------------------------------------------------------- 1 | # 提示词优化器 - 截图准备指南 2 | 3 | 本文档提供了为Chrome应用商店准备截图的详细说明和建议。高质量的截图能够有效展示插件功能,吸引更多用户。 4 | 5 | ## 截图要求 6 | 7 | Chrome应用商店对截图有以下要求: 8 | - 分辨率:1280x800像素或640x400像素 9 | - 格式:PNG或JPEG 10 | - 数量:至少1张,最多5张 11 | - 文件大小:每张不超过2MB 12 | 13 | ## 推荐截图场景 14 | 15 | 为全面展示提示词优化器的功能,建议准备以下场景的截图: 16 | 17 | ### 1. 主界面截图 18 | 19 | **描述**:展示扩展的主弹出窗口,显示整洁的界面设计。 20 | 21 | **准备方法**: 22 | 1. 点击浏览器工具栏中的扩展图标 23 | 2. 确保界面干净,显示输入框和主要功能按钮 24 | 3. 截取整个弹出窗口 25 | 26 | **要点**: 27 | - 确保UI界面清晰可见 28 | - 保持简洁美观的设计 29 | - 避免显示任何敏感信息 30 | 31 | ### 2. 右键菜单使用场景 32 | 33 | **描述**:展示用户在网页上选中文本,通过右键菜单使用插件优化提示词。 34 | 35 | **准备方法**: 36 | 1. 在任意网页(如ChatGPT)选中一段文本 37 | 2. 右键点击,展开上下文菜单 38 | 3. 确保"提示词优化器"选项可见 39 | 4. 截取包含右键菜单的页面区域 40 | 41 | **要点**: 42 | - 选择有代表性的文本内容 43 | - 确保右键菜单清晰可见 44 | - 避免选择含个人隐私的内容 45 | 46 | ### 3. 优化结果展示 47 | 48 | **描述**:展示提示词优化前后的对比效果。 49 | 50 | **准备方法**: 51 | 1. 在输入框中输入一个简单的提示词 52 | 2. 点击优化按钮 53 | 3. 等待优化结果显示 54 | 4. 截取显示原始提示词和优化后提示词的界面 55 | 56 | **要点**: 57 | - 选择有代表性的提示词示例 58 | - 确保优化效果明显 59 | - 使用简洁的示例,避免过长的文本 60 | 61 | ### 4. 多模型设置界面 62 | 63 | **描述**:展示插件支持多种AI模型的设置界面。 64 | 65 | **准备方法**: 66 | 1. 进入插件设置页面 67 | 2. 导航到模型设置选项卡 68 | 3. 确保界面显示多个支持的模型选项 69 | 4. 截取设置界面 70 | 71 | **要点**: 72 | - 不要显示真实的API密钥 73 | - 确保界面整洁有序 74 | - 突出多模型支持特性 75 | 76 | ### 5. 使用场景示例 77 | 78 | **描述**:展示插件在实际应用场景中的使用效果。 79 | 80 | **准备方法**: 81 | 1. 在实际应用场景(如AI绘图网站)中使用插件 82 | 2. 展示优化前后的提示词及生成效果 83 | 3. 截取包含上下文的界面 84 | 85 | **要点**: 86 | - 选择有吸引力的应用场景 87 | - 确保效果对比明显 88 | - 避免显示个人账号信息 89 | 90 | ## 截图处理建议 91 | 92 | 1. **尺寸调整**: 93 | - 确保最终图片符合1280x800或640x400像素 94 | - 保持16:10的宽高比 95 | - 如需调整尺寸,使用专业图像处理软件避免失真 96 | 97 | 2. **视觉优化**: 98 | - 适当调整亮度和对比度,确保清晰可读 99 | - 考虑添加简单的标注或高亮重点功能区域 100 | - 保持一致的视觉风格 101 | 102 | 3. **文件命名**: 103 | - 使用描述性文件名(如`main-interface.png`、`right-click-menu.png`等) 104 | - 按顺序编号(如`01-main.png`、`02-context.png`等) 105 | 106 | ## 注意事项 107 | 108 | 1. 不要在截图中显示敏感信息(如API密钥、个人邮箱等) 109 | 2. 避免过度编辑或添加不实际存在的功能 110 | 3. 确保截图真实反映插件功能 111 | 4. 考虑不同屏幕尺寸和分辨率的用户体验 112 | 5. 避免使用过多文字说明,让界面直观地传达功能 113 | 114 | ## 截图示例 115 | 116 | 以下是推荐的截图内容安排: 117 | 118 | 1. **首张截图**:主界面 - 展示整体布局和设计风格 119 | 2. **第二张**:右键菜单使用场景 - 展示如何在任意网页使用 120 | 3. **第三张**:优化效果对比 - 展示核心功能效果 121 | 4. **第四张**:多模型支持 - 展示技术亮点 122 | 5. **第五张**:实际应用示例 - 展示实用价值 123 | 124 | 按照这个顺序,能够全面且有逻辑地向潜在用户展示插件的价值和功能。 -------------------------------------------------------------------------------- /packages/extension/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { installI18n } from '@prompt-optimizer/ui' 3 | import App from './App.vue' 4 | 5 | import './style.css' 6 | import '@prompt-optimizer/ui/dist/style.css' 7 | 8 | const app = createApp(App) 9 | installI18n(app) 10 | app.mount('#app') -------------------------------------------------------------------------------- /packages/extension/src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* 基础样式设置 */ 6 | :root { 7 | font-size: 16px; /* 确保基础字体大小 */ 8 | zoom: 1; /* 确保不被缩放 */ 9 | } 10 | 11 | /* 确保正确的字体大小继承 */ 12 | html { 13 | font-size: 100%; /* 使用百分比确保正确缩放 */ 14 | -webkit-text-size-adjust: 100%; /* 防止 Chrome 自动调整字体大小 */ 15 | } 16 | 17 | body { 18 | font-size: 1rem; /* 使用相对单位 */ 19 | text-rendering: optimizeLegibility; /* 优化文本渲染 */ 20 | -webkit-font-smoothing: antialiased; /* 字体平滑 */ 21 | -moz-osx-font-smoothing: grayscale; 22 | } -------------------------------------------------------------------------------- /packages/extension/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{vue,js,ts,jsx,tsx}", 6 | "../../node_modules/@prompt-optimizer/ui/src/**/*.{vue,js,ts,jsx,tsx}", 7 | "../ui/src/**/*.{vue,js,ts,jsx,tsx}" 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundColor: { 12 | 'input-bg': 'rgba(0, 0, 0, 0.2)', 13 | }, 14 | textColor: { 15 | 'input-text': 'rgba(255, 255, 255, 0.9)', 16 | }, 17 | }, 18 | }, 19 | plugins: [ 20 | require('@tailwindcss/forms')({ 21 | strategy: 'class', 22 | }), 23 | require('@tailwindcss/typography'), 24 | ], 25 | important: true, 26 | } -------------------------------------------------------------------------------- /packages/extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "jsx": "preserve", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./src/*"], 19 | "@prompt-optimizer/ui": ["../ui/src/index.ts"], 20 | "@prompt-optimizer/ui/*": ["../ui/src/*"] 21 | } 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [ 25 | { "path": "./tsconfig.node.json" } 26 | ] 27 | } -------------------------------------------------------------------------------- /packages/extension/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": ["./vite.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "module": "ESNext", 7 | "moduleResolution": "Node", 8 | "types": ["node"] 9 | } 10 | } -------------------------------------------------------------------------------- /packages/extension/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { resolve } from 'path' 4 | import basicSsl from '@vitejs/plugin-basic-ssl' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue(), basicSsl()], 9 | resolve: { 10 | alias: { 11 | '@': resolve(__dirname, 'src'), 12 | '@prompt-optimizer/ui': resolve(__dirname, '../ui') 13 | }, 14 | }, 15 | base: './', // 使用相对路径 16 | build: { 17 | outDir: 'dist', 18 | rollupOptions: { 19 | input: { 20 | popup: resolve(__dirname, 'index.html') 21 | }, 22 | output: { 23 | entryFileNames: `assets/[name].js`, 24 | chunkFileNames: `assets/[name].js`, 25 | assetFileNames: (assetInfo) => { 26 | if (assetInfo.name === 'background.js') { 27 | return 'background.js'; 28 | } 29 | return `assets/[name].[ext]`; 30 | } 31 | } 32 | }, 33 | copyPublicDir: true 34 | }, 35 | server: { 36 | port: 5174, 37 | https: {} 38 | } 39 | }) -------------------------------------------------------------------------------- /packages/ui/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@prompt-optimizer/ui", 3 | "version": "0.0.1", 4 | "private": true, 5 | "type": "module", 6 | "main": "./dist/index.cjs", 7 | "module": "./dist/index.js", 8 | "types": "./dist/index.d.ts", 9 | "style": "./dist/style.css", 10 | "exports": { 11 | ".": { 12 | "types": "./dist/index.d.ts", 13 | "import": "./dist/index.js", 14 | "require": "./dist/index.cjs" 15 | }, 16 | "./style.css": "./dist/style.css", 17 | "./dist/style.css": "./dist/style.css" 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "readme": "在Windows环境下,请使用分号(;)而不是&&作为命令分隔符,或使用build:win脚本", 23 | "scripts": { 24 | "dev": "vite", 25 | "build": "vite build", 26 | "build:win": "vite build", 27 | "test": "vitest run" 28 | }, 29 | "dependencies": { 30 | "@prompt-optimizer/core": "workspace:*", 31 | "dompurify": "^3.2.4", 32 | "element-plus": "^2.5.6", 33 | "highlight.js": "^11.11.1", 34 | "markdown-it": "^14.1.0", 35 | "uuid": "^11.0.5", 36 | "vue": "^3.3.4" 37 | }, 38 | "devDependencies": { 39 | "@types/node": "^22.13.10", 40 | "@types/uuid": "^10.0.0", 41 | "@vitejs/plugin-vue": "^5.2.1", 42 | "@vue/test-utils": "^2.4.5", 43 | "@vue/tsconfig": "^0.5.1", 44 | "jsdom": "^26.0.0", 45 | "typescript": "^5.8.2", 46 | "vite": "^6.2.0", 47 | "vite-plugin-dts": "^4.5.3", 48 | "vitest": "^3.0.7" 49 | } 50 | } -------------------------------------------------------------------------------- /packages/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /packages/ui/src/assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linshenkx/prompt-optimizer/d54806a281135f167beb9c6b9a36a101eda85740/packages/ui/src/assets/logo.jpg -------------------------------------------------------------------------------- /packages/ui/src/components.d.ts: -------------------------------------------------------------------------------- 1 | import type { DefineComponent } from 'vue' 2 | 3 | declare module 'vue' { 4 | export interface GlobalComponents { 5 | [key: string]: DefineComponent<{}, {}, any> 6 | } 7 | } 8 | 9 | declare module '*.vue' { 10 | import type { DefineComponent } from 'vue' 11 | const component: DefineComponent<{}, {}, any> 12 | export default component 13 | } -------------------------------------------------------------------------------- /packages/ui/src/components/ActionButton.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /packages/ui/src/components/ContentCard.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/src/components/FullscreenDialog.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 39 | 40 | -------------------------------------------------------------------------------- /packages/ui/src/components/InputPanel.vue: -------------------------------------------------------------------------------- 1 | 2 | 48 | 49 | -------------------------------------------------------------------------------- /packages/ui/src/components/LanguageSwitch.vue: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | -------------------------------------------------------------------------------- /packages/ui/src/components/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 37 | 38 | -------------------------------------------------------------------------------- /packages/ui/src/components/Modal.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | -------------------------------------------------------------------------------- /packages/ui/src/components/ModelSelect.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 148 | 149 | -------------------------------------------------------------------------------- /packages/ui/src/components/OptimizePanel.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | -------------------------------------------------------------------------------- /packages/ui/src/components/Panel.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/src/components/Toast.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | -------------------------------------------------------------------------------- /packages/ui/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | export * from './usePromptOptimizer' 2 | export * from './usePromptTester' 3 | export * from './useToast' 4 | export * from './usePromptHistory' 5 | export * from './useServiceInitializer' 6 | export * from './useTemplateManager' 7 | export * from './useModelManager' 8 | export * from './useHistoryManager' 9 | export * from './useModelSelectors' 10 | export * from './useModals' 11 | export * from './useAutoScroll' 12 | export * from './useClipboard' -------------------------------------------------------------------------------- /packages/ui/src/composables/useClipboard.ts: -------------------------------------------------------------------------------- 1 | import { useI18n } from 'vue-i18n' 2 | import { useToast } from './useToast' 3 | 4 | /** 5 | * 复制内容到剪贴板的组合式API 6 | * 7 | * @returns 复制相关的方法和状态 8 | */ 9 | export function useClipboard() { 10 | const { t } = useI18n() 11 | const toast = useToast() 12 | 13 | /** 14 | * 复制文本到剪贴板 15 | * 支持现代API和降级方案 16 | * 17 | * @param text 要复制的文本 18 | * @param showToast 是否显示提示,默认为true 19 | * @returns 是否复制成功 20 | */ 21 | const copyText = async (text: string, showToast = true): Promise => { 22 | if (!text) return false 23 | 24 | // 检查是否支持现代剪贴板API 25 | if (navigator.clipboard && window.isSecureContext) { 26 | try { 27 | await navigator.clipboard.writeText(text) 28 | if (showToast) toast.success(t('common.copySuccess')) 29 | return true 30 | } catch (e) { 31 | console.warn('现代剪贴板API失败,尝试备用方法', e) 32 | // 继续尝试备用方法 33 | } 34 | } 35 | 36 | // 备用方法:创建临时元素复制 37 | try { 38 | const textarea = document.createElement('textarea') 39 | textarea.value = text 40 | 41 | // 设置样式确保元素不可见 42 | textarea.style.position = 'fixed' 43 | textarea.style.opacity = '0' 44 | textarea.style.left = '-9999px' 45 | 46 | document.body.appendChild(textarea) 47 | textarea.select() 48 | 49 | const successful = document.execCommand('copy') 50 | document.body.removeChild(textarea) 51 | 52 | if (successful && showToast) { 53 | toast.success(t('common.copySuccess')) 54 | } else if (!successful && showToast) { 55 | toast.error(t('common.copyFailed')) 56 | } 57 | 58 | return successful 59 | } catch (e) { 60 | console.error('备用剪贴板方法失败', e) 61 | if (showToast) toast.error(t('common.copyFailed')) 62 | return false 63 | } 64 | } 65 | 66 | return { 67 | copyText 68 | } 69 | } -------------------------------------------------------------------------------- /packages/ui/src/composables/useHistoryManager.ts: -------------------------------------------------------------------------------- 1 | import { ref, type Ref } from 'vue' 2 | import type { HistoryManager } from '@prompt-optimizer/core' 3 | import { useToast } from './useToast' 4 | import { useI18n } from 'vue-i18n' 5 | 6 | export interface HistoryManagerHooks { 7 | showHistory: Ref 8 | handleSelectHistory: (historyItem: any) => void 9 | handleClearHistory: () => void 10 | handleDeleteChain: (chainId: string) => void 11 | } 12 | 13 | export function useHistoryManager( 14 | historyManager: HistoryManager, 15 | prompt: Ref, 16 | optimizedPrompt: Ref, 17 | currentChainId: Ref, 18 | currentVersions: Ref, 19 | currentVersionId: Ref, 20 | handleSelectHistoryBase: (historyItem: any) => void, 21 | handleClearHistoryBase: () => void, 22 | handleDeleteChainBase: (chainId: string) => void 23 | ): HistoryManagerHooks { 24 | const showHistory = ref(false) 25 | const toast = useToast() 26 | const { t } = useI18n() 27 | 28 | const handleSelectHistory = (historyItem: any) => { 29 | handleSelectHistoryBase(historyItem) 30 | showHistory.value = false 31 | } 32 | 33 | const handleClearHistory = () => { 34 | // 调用基础方法处理数据层面的清空 35 | handleClearHistoryBase() 36 | // 关闭历史记录抽屉 37 | showHistory.value = false 38 | } 39 | 40 | const handleDeleteChain = (chainId: string) => { 41 | // 调用基础方法处理数据层面的删除 42 | handleDeleteChainBase(chainId) 43 | // 不关闭历史记录抽屉,让用户继续查看其他记录 44 | } 45 | 46 | return { 47 | showHistory, 48 | handleSelectHistory, 49 | handleClearHistory, 50 | handleDeleteChain 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/ui/src/composables/useModals.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { useToast } from './useToast' 3 | import { useI18n } from 'vue-i18n' 4 | 5 | export function useModals( 6 | templateManager: any, 7 | optimizeModelSelect: any, 8 | testModelSelect: any, 9 | loadModels: () => Promise, 10 | initTemplateSelection: () => Promise 11 | ) { 12 | const toast = useToast() 13 | const { t } = useI18n() 14 | 15 | // 弹窗状态 16 | const showConfig = ref(false) 17 | const showHistory = ref(false) 18 | const showTemplates = ref(false) 19 | const currentType = ref('optimize') 20 | 21 | // 打开提示词管理器 22 | const openTemplateManager = (type = 'optimize') => { 23 | currentType.value = type 24 | showTemplates.value = true 25 | } 26 | 27 | // 加载提示词模板 28 | const loadTemplates = async () => { 29 | try { 30 | await templateManager.init() 31 | await initTemplateSelection() 32 | } catch (error) { 33 | console.error(t('toast.error.loadTemplatesFailed'), error) 34 | toast.error(t('toast.error.loadTemplatesFailed')) 35 | } 36 | } 37 | 38 | // 关闭提示词管理器 39 | const handleTemplateManagerClose = async () => { 40 | await loadTemplates() 41 | showTemplates.value = false 42 | } 43 | 44 | // 关闭模型管理器 45 | const handleModelManagerClose = async () => { 46 | await loadModels() 47 | optimizeModelSelect.value?.refresh() 48 | testModelSelect.value?.refresh() 49 | showConfig.value = false 50 | } 51 | 52 | return { 53 | showConfig, 54 | showHistory, 55 | showTemplates, 56 | currentType, 57 | openTemplateManager, 58 | handleTemplateManagerClose, 59 | handleModelManagerClose 60 | } 61 | } -------------------------------------------------------------------------------- /packages/ui/src/composables/useModelManager.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch, onMounted } from 'vue' 2 | import type { Ref } from 'vue' 3 | import type { ModelConfig, ModelManager } from '@prompt-optimizer/core' 4 | import { useToast } from './useToast' 5 | import { useI18n } from 'vue-i18n' 6 | 7 | export interface ModelManagerHooks { 8 | showConfig: Ref 9 | selectedOptimizeModel: Ref 10 | selectedTestModel: Ref 11 | handleModelManagerClose: () => void 12 | handleModelsUpdated: (modelKey: string) => void 13 | handleModelSelect: (model: ModelConfig & { key: string }) => void 14 | initModelSelection: () => void 15 | loadModels: () => void 16 | } 17 | 18 | export interface ModelManagerOptions { 19 | modelManager: ModelManager 20 | optimizeModelSelect: Ref 21 | testModelSelect: Ref 22 | } 23 | 24 | // Local storage keys 25 | const STORAGE_KEYS = { 26 | OPTIMIZE_MODEL: 'app:selected-optimize-model', 27 | TEST_MODEL: 'app:selected-test-model' 28 | } as const 29 | 30 | export function useModelManager(options: ModelManagerOptions): ModelManagerHooks { 31 | const toast = useToast() 32 | const { t } = useI18n() 33 | const showConfig = ref(false) 34 | const selectedOptimizeModel = ref('') 35 | const selectedTestModel = ref('') 36 | 37 | const { modelManager, optimizeModelSelect, testModelSelect } = options 38 | 39 | // Save model selection 40 | const saveModelSelection = (model: string, type: 'optimize' | 'test') => { 41 | if (model) { 42 | localStorage.setItem( 43 | type === 'optimize' ? STORAGE_KEYS.OPTIMIZE_MODEL : STORAGE_KEYS.TEST_MODEL, 44 | model 45 | ) 46 | } 47 | } 48 | 49 | // Initialize model selection 50 | const initModelSelection = () => { 51 | try { 52 | const enabledModels = modelManager.getAllModels().filter(m => m.enabled) 53 | const defaultModel = enabledModels[0]?.key 54 | 55 | if (defaultModel) { 56 | // Load optimization model selection 57 | const savedOptimizeModel = localStorage.getItem(STORAGE_KEYS.OPTIMIZE_MODEL) 58 | selectedOptimizeModel.value = (savedOptimizeModel && enabledModels.find(m => m.key === savedOptimizeModel)) 59 | ? savedOptimizeModel 60 | : defaultModel 61 | 62 | // Load test model selection 63 | const savedTestModel = localStorage.getItem(STORAGE_KEYS.TEST_MODEL) 64 | selectedTestModel.value = (savedTestModel && enabledModels.find(m => m.key === savedTestModel)) 65 | ? savedTestModel 66 | : defaultModel 67 | 68 | // Save initial selection 69 | saveModelSelection(selectedOptimizeModel.value, 'optimize') 70 | saveModelSelection(selectedTestModel.value, 'test') 71 | } 72 | } catch (error) { 73 | console.error(t('toast.error.initModelSelectFailed'), error) 74 | toast.error(t('toast.error.initModelSelectFailed')) 75 | } 76 | } 77 | 78 | // Handle model selection 79 | const handleModelSelect = (model: ModelConfig & { key: string }) => { 80 | if (model) { 81 | selectedOptimizeModel.value = model.key 82 | selectedTestModel.value = model.key 83 | 84 | saveModelSelection(model.key, 'optimize') 85 | saveModelSelection(model.key, 'test') 86 | 87 | toast.success(t('toast.success.modelSelected', { name: model.name })) 88 | } 89 | } 90 | 91 | // Load model data 92 | const loadModels = () => { 93 | try { 94 | // Get latest enabled models list 95 | const enabledModels = modelManager.getAllModels().filter(m => m.enabled) 96 | const defaultModel = enabledModels[0]?.key 97 | 98 | // Verify if current selected models are still available 99 | if (!enabledModels.find(m => m.key === selectedOptimizeModel.value)) { 100 | selectedOptimizeModel.value = defaultModel || '' 101 | } 102 | if (!enabledModels.find(m => m.key === selectedTestModel.value)) { 103 | selectedTestModel.value = defaultModel || '' 104 | } 105 | } catch (error: any) { 106 | console.error(t('toast.error.loadModelsFailed'), error) 107 | toast.error(t('toast.error.loadModelsFailed')) 108 | } 109 | } 110 | 111 | const handleModelManagerClose = () => { 112 | // Update data first 113 | loadModels() 114 | // Refresh model selection components 115 | optimizeModelSelect.value?.refresh() 116 | testModelSelect.value?.refresh() 117 | // Close interface 118 | showConfig.value = false 119 | } 120 | 121 | const handleModelsUpdated = (modelKey: string) => { 122 | // Handle other logic after model update if needed 123 | console.log(t('toast.info.modelUpdated'), modelKey) 124 | } 125 | 126 | // Watch model selection changes 127 | watch(selectedOptimizeModel, (newVal) => { 128 | if (newVal) { 129 | saveModelSelection(newVal, 'optimize') 130 | } 131 | }) 132 | 133 | watch(selectedTestModel, (newVal) => { 134 | if (newVal) { 135 | saveModelSelection(newVal, 'test') 136 | } 137 | }) 138 | 139 | // Auto initialize on mounted 140 | onMounted(() => { 141 | initModelSelection() 142 | }) 143 | 144 | return { 145 | showConfig, 146 | selectedOptimizeModel, 147 | selectedTestModel, 148 | handleModelManagerClose, 149 | handleModelsUpdated, 150 | handleModelSelect, 151 | initModelSelection, 152 | loadModels 153 | } 154 | } -------------------------------------------------------------------------------- /packages/ui/src/composables/useModelSelectors.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import type { Ref } from 'vue' 3 | 4 | export interface ModelSelectorsHooks { 5 | optimizeModelSelect: Ref 6 | testModelSelect: Ref 7 | } 8 | 9 | export function useModelSelectors(): ModelSelectorsHooks { 10 | const optimizeModelSelect = ref(null) 11 | const testModelSelect = ref(null) 12 | 13 | return { 14 | optimizeModelSelect, 15 | testModelSelect 16 | } 17 | } -------------------------------------------------------------------------------- /packages/ui/src/composables/usePromptHistory.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch, type Ref, onMounted } from 'vue' 2 | import { useToast } from './useToast' 3 | import { useI18n } from 'vue-i18n' 4 | import { v4 as uuidv4 } from 'uuid' 5 | import type { IHistoryManager, PromptRecordChain, PromptRecord } from '@prompt-optimizer/core' 6 | 7 | type PromptChain = PromptRecordChain 8 | 9 | export function usePromptHistory( 10 | historyManager: IHistoryManager, 11 | prompt: Ref, 12 | optimizedPrompt: Ref, 13 | currentChainId: Ref, 14 | currentVersions: Ref, 15 | currentVersionId: Ref 16 | ) { 17 | const toast = useToast() 18 | const { t } = useI18n() 19 | const history = ref([]) 20 | const showHistory = ref(false) 21 | 22 | const handleSelectHistory = (context: { record: any, chainId: string, rootPrompt: string }) => { 23 | const { record, rootPrompt } = context 24 | 25 | prompt.value = rootPrompt 26 | optimizedPrompt.value = record.optimizedPrompt 27 | 28 | const newRecord = historyManager.createNewChain({ 29 | id: uuidv4(), 30 | originalPrompt: rootPrompt, 31 | optimizedPrompt: record.optimizedPrompt, 32 | type: 'optimize', 33 | modelKey: record.modelKey, 34 | templateId: record.templateId, 35 | timestamp: Date.now(), 36 | metadata: {} 37 | }) 38 | 39 | currentChainId.value = newRecord.chainId 40 | currentVersions.value = newRecord.versions 41 | currentVersionId.value = newRecord.currentRecord.id 42 | 43 | refreshHistory() 44 | showHistory.value = false 45 | } 46 | 47 | const handleClearHistory = () => { 48 | try { 49 | historyManager.clearHistory() 50 | 51 | // 清空当前显示的内容 52 | prompt.value = ''; 53 | optimizedPrompt.value = ''; 54 | currentChainId.value = ''; 55 | currentVersions.value = []; 56 | currentVersionId.value = ''; 57 | 58 | // 立即更新历史记录,确保UI能够反映最新状态 59 | history.value = [] 60 | toast.success(t('toast.success.historyClear')) 61 | } catch (error) { 62 | console.error(t('toast.error.clearHistoryFailed'), error) 63 | toast.error(t('toast.error.clearHistoryFailed')) 64 | } 65 | } 66 | 67 | const handleDeleteChain = (chainId: string) => { 68 | try { 69 | // 获取链中的所有记录 70 | const allChains = historyManager.getAllChains() 71 | const chain = allChains.find(c => c.chainId === chainId) 72 | 73 | if (chain) { 74 | // 删除链中的所有记录 75 | chain.versions.forEach((record: PromptRecord) => { 76 | historyManager.deleteRecord(record.id) 77 | }) 78 | 79 | // 如果当前正在查看的是被删除的链,则清空当前显示 80 | if (currentChainId.value === chainId) { 81 | prompt.value = ''; 82 | optimizedPrompt.value = ''; 83 | currentChainId.value = ''; 84 | currentVersions.value = []; 85 | currentVersionId.value = ''; 86 | } 87 | 88 | // 立即更新历史记录,确保UI能够反映最新状态 89 | history.value = [...historyManager.getAllChains()] 90 | toast.success(t('toast.success.historyChainDeleted')) 91 | } 92 | } catch (error) { 93 | console.error(t('toast.error.historyChainDeleteFailed'), error) 94 | toast.error(t('toast.error.historyChainDeleteFailed')) 95 | } 96 | } 97 | 98 | const initHistory = () => { 99 | try { 100 | refreshHistory() 101 | } catch (error) { 102 | console.error(t('toast.error.loadHistoryFailed'), error) 103 | toast.error(t('toast.error.loadHistoryFailed')) 104 | } 105 | } 106 | 107 | // 添加一个刷新历史记录的函数 108 | const refreshHistory = () => { 109 | history.value = [...historyManager.getAllChains()] 110 | } 111 | 112 | // Watch history display state 113 | watch(showHistory, (newVal) => { 114 | if (newVal) { 115 | refreshHistory() 116 | } 117 | }) 118 | 119 | // Watch version changes, update history 120 | watch([currentVersions], () => { 121 | refreshHistory() 122 | }) 123 | 124 | // 初始化时加载历史记录 125 | onMounted(() => { 126 | refreshHistory() 127 | }) 128 | 129 | return { 130 | history, 131 | showHistory, 132 | handleSelectHistory, 133 | handleClearHistory, 134 | handleDeleteChain, 135 | initHistory 136 | } 137 | } -------------------------------------------------------------------------------- /packages/ui/src/composables/usePromptTester.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { useToast } from './useToast' 3 | import { useI18n } from 'vue-i18n' 4 | import type { Ref } from 'vue' 5 | import type { IPromptService } from '@prompt-optimizer/core' 6 | 7 | export function usePromptTester( 8 | promptService: Ref, 9 | selectedTestModel: Ref 10 | ) { 11 | const toast = useToast() 12 | const { t } = useI18n() 13 | 14 | // States 15 | const testContent = ref('') 16 | const testResult = ref('') 17 | const testError = ref('') 18 | const isTesting = ref(false) 19 | 20 | // Test prompt 21 | const handleTest = async (optimizedPrompt: string) => { 22 | if (!promptService.value) { 23 | toast.error(t('toast.error.serviceInit')) 24 | return 25 | } 26 | 27 | if (!selectedTestModel.value || !testContent.value || !optimizedPrompt) { 28 | toast.error(t('toast.error.incompleteTestInfo')) 29 | return 30 | } 31 | 32 | isTesting.value = true 33 | testError.value = '' 34 | testResult.value = '' 35 | 36 | try { 37 | await promptService.value.testPromptStream( 38 | optimizedPrompt, 39 | testContent.value, 40 | selectedTestModel.value, 41 | { 42 | onToken: (token: string) => { 43 | testResult.value += token 44 | }, 45 | onComplete: () => { 46 | isTesting.value = false 47 | }, 48 | onError: (error: Error) => { 49 | testError.value = error.message || t('toast.error.testFailed') 50 | isTesting.value = false 51 | } 52 | } 53 | ) 54 | } catch (error: any) { 55 | console.error(t('toast.error.testFailed'), error) 56 | testError.value = error.message || t('toast.error.testProcessError') 57 | } finally { 58 | isTesting.value = false 59 | } 60 | } 61 | 62 | return { 63 | // States 64 | testContent, 65 | testResult, 66 | testError, 67 | isTesting, 68 | 69 | // Methods 70 | handleTest 71 | } 72 | } -------------------------------------------------------------------------------- /packages/ui/src/composables/useServiceInitializer.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted } from 'vue' 2 | import { useToast } from './useToast' 3 | import { useI18n } from 'vue-i18n' 4 | import type { ModelManager, TemplateManager, HistoryManager, PromptService } from '@prompt-optimizer/core' 5 | import { createLLMService, createPromptService } from '@prompt-optimizer/core' 6 | 7 | export function useServiceInitializer( 8 | modelManager: ModelManager, 9 | templateManager: TemplateManager, 10 | historyManager: HistoryManager 11 | ) { 12 | const toast = useToast() 13 | const { t } = useI18n() 14 | const promptServiceRef = ref(null) 15 | const llmService = createLLMService(modelManager) 16 | 17 | // Initialize base services 18 | const initBaseServices = () => { 19 | try { 20 | console.log(t('log.info.initBaseServicesStart')) 21 | 22 | // Get and verify template list 23 | const templates = templateManager.listTemplates() 24 | console.log(t('log.info.templateList'), templates) 25 | 26 | // Create prompt service 27 | console.log(t('log.info.createPromptService')) 28 | promptServiceRef.value = createPromptService(modelManager, llmService, templateManager, historyManager) 29 | 30 | console.log(t('log.info.initComplete')) 31 | } catch (error) { 32 | console.error(t('log.error.initBaseServicesFailed'), error) 33 | toast.error(t('toast.error.initFailed', { error: error instanceof Error ? error.message : String(error) })) 34 | throw error 35 | } 36 | } 37 | 38 | // Auto initialize on mounted 39 | onMounted(() => { 40 | initBaseServices() 41 | }) 42 | 43 | return { 44 | promptServiceRef, 45 | initBaseServices 46 | } 47 | } -------------------------------------------------------------------------------- /packages/ui/src/composables/useTemplateManager.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted } from 'vue' 2 | import type { Ref } from 'vue' 3 | import { useToast } from './useToast' 4 | import { useI18n } from 'vue-i18n' 5 | import type { Template, TemplateManager } from '@prompt-optimizer/core' 6 | 7 | interface TemplateSelector extends Element { 8 | __vueParentComponent?: { 9 | ctx?: { 10 | refresh?: () => void 11 | } 12 | } 13 | } 14 | 15 | export interface TemplateManagerHooks { 16 | showTemplates: Ref 17 | currentType: Ref 18 | handleTemplateSelect: (template: Template | null, type: string) => void 19 | openTemplateManager: (type: string) => void 20 | handleTemplateManagerClose: () => void 21 | } 22 | 23 | export interface TemplateManagerOptions { 24 | selectedOptimizeTemplate: Ref