From b5f3b263f7000ce99800de0fc65199e0682444db Mon Sep 17 00:00:00 2001 From: linshen <32978552+linshenkx@users.noreply.github.com> Date: Sat, 5 Jul 2025 21:05:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=A1=8C=E9=9D=A2?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E6=89=93=E5=8C=85=E6=96=B9=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E6=94=B9=E4=B8=BAZIP=E6=A0=BC=E5=BC=8F=E4=BB=A5=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E5=AD=98=E5=82=A8=E8=B7=AF=E5=BE=84=E5=92=8C=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 18 +-- README.md | 19 ++- .../README.md | 150 ++++++++++++++++++ docs/archives/INDEX.md | 7 +- packages/desktop/main.js | 12 +- packages/desktop/package.json | 10 +- packages/desktop/preload.js | 2 + packages/ui/src/composables/index.ts | 3 +- .../ui/src/composables/useAppInitializer.ts | 34 +--- packages/ui/src/types/services.ts | 2 - ...sePromptOptimizer-model-validation.test.ts | 7 +- 11 files changed, 203 insertions(+), 61 deletions(-) create mode 100644 docs/archives/116-desktop-packaging-optimization/README.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44cccde6..6540b7a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: windows-build - path: packages/desktop/dist/*.exe + path: packages/desktop/dist/*.zip retention-days: 30 # 构建 macOS 版本 @@ -72,7 +72,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: macos-build - path: packages/desktop/dist/*.dmg + path: packages/desktop/dist/*.zip retention-days: 30 # 构建 Linux 版本 @@ -104,7 +104,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: linux-build - path: packages/desktop/dist/*.AppImage + path: packages/desktop/dist/*.zip retention-days: 30 # 创建 GitHub Release @@ -192,14 +192,14 @@ jobs: ## 🚀 Prompt Optimizer $CURRENT_TAG ### 📦 下载 - - **Windows**: \`Prompt Optimizer Setup *.exe\` - - **macOS**: \`Prompt Optimizer-*.dmg\` - - **Linux**: \`Prompt Optimizer-*.AppImage\` + - **Windows**: \`PromptOptimizer-*-win-x64.zip\` + - **macOS**: \`PromptOptimizer-*-darwin-x64.zip\` 或 \`PromptOptimizer-*-darwin-arm64.zip\` + - **Linux**: \`PromptOptimizer-*-linux-x64.zip\` ### 🔧 安装说明 - - **Windows**: 下载 \`.exe\` 文件直接安装 - - **macOS**: 下载 \`.dmg\` 文件,拖拽到应用程序文件夹 - - **Linux**: 下载 \`.AppImage\` 文件,添加执行权限后直接运行 + - **Windows**: 下载 \`.zip\` 文件,解压到任意目录,运行 \`PromptOptimizer.exe\` + - **macOS**: 下载 \`.zip\` 文件,解压到任意目录,运行 \`PromptOptimizer.app\` + - **Linux**: 下载 \`.zip\` 文件,解压到任意目录,运行 \`PromptOptimizer\` ### 📝 更新内容 $COMMITS_LIMITED diff --git a/README.md b/README.md index fb8b1de7..4f5d7bb7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ## 📖 项目简介 -Prompt Optimizer是一个强大的AI提示词优化工具,帮助你编写更好的AI提示词,提升AI输出质量。支持Web应用和Chrome插件两种使用方式。 +Prompt Optimizer是一个强大的AI提示词优化工具,帮助你编写更好的AI提示词,提升AI输出质量。支持Web应用、桌面应用和Chrome插件三种使用方式。 ### 🎥 功能演示 @@ -34,7 +34,7 @@ Prompt Optimizer是一个强大的AI提示词优化工具,帮助你编写更 - ⚙️ **高级参数配置**:支持为每个模型单独配置temperature、max_tokens等LLM参数 - 🔒 **安全架构**:纯客户端处理,数据直接与AI服务商交互,不经过中间服务器 - 💾 **隐私保护**:本地加密存储历史记录和API密钥,支持数据导入导出 -- 📱 **多端支持**:同时提供Web应用和Chrome插件两种使用方式 +- 📱 **多端支持**:同时提供Web应用、桌面应用和Chrome插件三种使用方式 - 🎨 **用户体验**:简洁直观的界面设计,响应式布局和流畅交互动效 - 🌐 **跨域支持**:Vercel部署时支持使用Edge Runtime代理解决跨域问题 - 🔐 **访问控制**:支持密码保护功能,保障部署安全 @@ -62,11 +62,22 @@ Prompt Optimizer是一个强大的AI提示词优化工具,帮助你编写更 更多详细的部署步骤和注意事项,请查看: - [Vercel部署指南](docs/vercel.md) -### 3. 安装Chrome插件 +### 3. 下载桌面应用 +1. 从 [GitHub Releases](https://github.com/linshenkx/prompt-optimizer/releases) 下载最新版本 +2. **Windows**: 下载 `PromptOptimizer-*-win-x64.zip`,解压到任意目录,运行 `PromptOptimizer.exe` +3. **macOS**: 下载 `PromptOptimizer-*-darwin-x64.zip` 或 `PromptOptimizer-*-darwin-arm64.zip`,解压到任意目录,运行 `PromptOptimizer.app` +4. **Linux**: 下载 `PromptOptimizer-*-linux-x64.zip`,解压到任意目录,运行 `PromptOptimizer` + +**桌面应用特点**: +- ✅ **便携模式**:数据存储在应用同级目录,便于备份和迁移 +- ✅ **离线使用**:无需浏览器,独立运行 +- ✅ **性能更好**:原生应用体验,响应更快 + +### 4. 安装Chrome插件 1. 从Chrome商店安装(由于审批较慢,可能不是最新的):[Chrome商店地址](https://chromewebstore.google.com/detail/prompt-optimizer/cakkkhboolfnadechdlgdcnjammejlna) 2. 点击图标即可打开提示词优化器 -### 4. Docker部署 +### 5. Docker部署 ```bash # 运行容器(默认配置) docker run -d -p 80:80 --restart unless-stopped --name prompt-optimizer linshen/prompt-optimizer diff --git a/docs/archives/116-desktop-packaging-optimization/README.md b/docs/archives/116-desktop-packaging-optimization/README.md new file mode 100644 index 00000000..f05e8954 --- /dev/null +++ b/docs/archives/116-desktop-packaging-optimization/README.md @@ -0,0 +1,150 @@ +# 116 - 桌面应用打包优化 + +## 概述 + +将桌面应用从单文件portable模式改为ZIP压缩包模式,解决了存储路径检测问题,简化了代码架构。 + +## 问题背景 + +### 原有问题 + +1. **存储路径问题**: + - portable模式下,`process.execPath` 指向临时解压目录 + - 数据保存在临时目录,应用关闭后被清理 + - 路径检测逻辑复杂,容易出错 + +2. **架构复杂性**: + - 需要复杂的路径检测和回退逻辑 + - 大量调试代码和日志输出 + - 主进程日志在生产环境难以查看 + +## 解决方案 + +### 1. 修改打包配置 + +**之前(不同格式)**: +```json +{ + "win": { "target": "portable" }, + "mac": { "target": "dmg" }, + "linux": { "target": "AppImage" } +} +``` + +**现在(统一ZIP格式)**: +```json +{ + "win": { + "target": "zip", + "artifactName": "${productName}-${version}-${os}-${arch}.${ext}" + }, + "mac": { + "target": "zip", + "artifactName": "${productName}-${version}-${os}-${arch}.${ext}" + }, + "linux": { + "target": "zip", + "artifactName": "${productName}-${version}-${os}-${arch}.${ext}" + } +} +``` + +### 2. 简化存储路径逻辑 + +**之前(复杂检测)**: +- 多种路径检测方法 +- 临时目录检查 +- 复杂的回退逻辑 +- 大量调试日志 + +**现在(简化逻辑)**: +```javascript +if (app.isPackaged) { + // ZIP包解压后的portable模式 + const exePath = app.getPath('exe'); + const execDir = path.dirname(exePath); + userDataPath = path.join(execDir, 'prompt-optimizer-data'); +} else { + // 开发环境 + userDataPath = path.join(__dirname, '..', '..', 'prompt-optimizer-data'); +} +``` + +### 3. 移除调试代码 + +- 删除 `debugLog` 函数 +- 移除文件日志输出 +- 删除调试API和IPC接口 +- 简化错误处理 + +## 实施步骤 + +### 1. 修改打包配置 +- 更新 `packages/desktop/package.json` +- 改为ZIP目标格式 + +### 2. 简化main.js +- 移除复杂的路径检测逻辑 +- 删除调试日志函数 +- 简化存储初始化代码 + +### 3. 清理preload.js +- 移除调试API接口 + +### 4. 更新文档和工作流 +- 修改GitHub Actions工作流 +- 更新README.md使用说明 +- 创建归档文档 + +## 优势 + +### 1. 技术优势 +- ✅ **路径可靠**:ZIP解压后路径确定,无临时目录问题 +- ✅ **代码简洁**:移除复杂检测逻辑,维护性更好 +- ✅ **性能更好**:无额外文件I/O操作 + +### 2. 用户体验 +- ✅ **真正portable**:解压到哪里,数据就在哪里 +- ✅ **便于管理**:整个文件夹包含应用+数据 +- ✅ **便于备份**:复制文件夹即可完整备份 + +### 3. 分发优势 +- ✅ **文件名清晰**:包含版本、系统、架构信息 +- ✅ **便于下载**:单个ZIP文件包含所有内容 +- ✅ **跨平台一致**:所有平台都使用相同的分发方式 + +## 使用方法 + +### 构建 +```bash +cd packages/desktop +pnpm run build +``` + +### 分发 +- **Windows**: `PromptOptimizer-1.2.0-win-x64.zip` +- **macOS**: `PromptOptimizer-1.2.0-darwin-x64.zip` / `PromptOptimizer-1.2.0-darwin-arm64.zip` +- **Linux**: `PromptOptimizer-1.2.0-linux-x64.zip` + +所有平台: +- 用户解压到任意目录 +- 运行对应的可执行文件 +- 数据保存在 `prompt-optimizer-data/` 目录 + +### 数据管理 +- **备份**:复制整个应用文件夹 +- **迁移**:移动整个文件夹到新位置 +- **升级**:替换exe文件,保留数据目录 + +## 经验总结 + +1. **简单即美**:复杂的路径检测不如简单的ZIP解压 +2. **用户友好**:便携模式更符合用户期望 +3. **维护性**:简化的代码更容易维护和调试 +4. **可靠性**:减少边界情况,提高稳定性 + +## 后续优化 + +1. **自动更新**:考虑添加应用内更新功能 +2. **安装包选项**:为需要的用户提供传统安装包 +3. **数据迁移**:提供从旧版本迁移数据的工具 diff --git a/docs/archives/INDEX.md b/docs/archives/INDEX.md index 97dbe82c..bb9b02f9 100644 --- a/docs/archives/INDEX.md +++ b/docs/archives/INDEX.md @@ -90,12 +90,17 @@ - 完整的数据持久化解决方案 - 高性能文件I/O和错误恢复机制 +- **[116-desktop-packaging-optimization](./116-desktop-packaging-optimization/)** - 桌面应用打包优化 📦 + - 从portable模式改为ZIP压缩包模式 + - 解决存储路径检测问题 + - 简化代码架构,提升用户体验 + ## 🔍 快速查找指南 ### 按问题类型查找 - **启动问题** → 102-web-architecture-refactor - **显示异常** → 112-desktop-ipc-fixes -- **存储问题** → 110-desktop-indexeddb-fix, 114-desktop-file-storage +- **存储问题** → 110-desktop-indexeddb-fix, 114-desktop-file-storage, 116-desktop-packaging-optimization - **序列化错误** → 112-desktop-ipc-fixes - **应用退出问题** → 114-desktop-file-storage - **语言设置问题** → 112-desktop-ipc-fixes diff --git a/packages/desktop/main.js b/packages/desktop/main.js index 793c77ad..54686399 100644 --- a/packages/desktop/main.js +++ b/packages/desktop/main.js @@ -181,15 +181,15 @@ async function initializeServices() { // 根据环境确定数据存储路径 let userDataPath; if (app.isPackaged) { - // 生产环境:使用可执行文件所在目录下的prompt-optimizer-data文件夹 - const execPath = process.execPath; - const execDir = path.dirname(execPath); + // 生产环境:ZIP包解压后的portable模式 + const exePath = app.getPath('exe'); + const execDir = path.dirname(exePath); userDataPath = path.join(execDir, 'prompt-optimizer-data'); - console.log('[DESKTOP] Production mode - Executable path:', execPath); + console.log('[DESKTOP] Production mode - portable storage in exe directory'); } else { // 开发环境:使用项目根目录下的prompt-optimizer-data文件夹 userDataPath = path.join(__dirname, '..', '..', 'prompt-optimizer-data'); - console.log('[DESKTOP] Development mode - Project root data folder'); + console.log('[DESKTOP] Development mode - project root storage'); } console.log('[DESKTOP] Data storage path:', userDataPath); @@ -724,6 +724,8 @@ function setupIPC() { } }); + + // 环境配置同步 - 主进程作为唯一配置源 ipcMain.handle('config-getEnvironmentVariables', async (event) => { try { diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 749ce378..c9292a67 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -33,14 +33,16 @@ "node_modules/**/*" ], "win": { - "target": "portable", - "artifactName": "${productName}-${version}.${ext}" + "target": "zip", + "artifactName": "${productName}-${version}-${os}-${arch}.${ext}" }, "mac": { - "target": "dmg" + "target": "zip", + "artifactName": "${productName}-${version}-${os}-${arch}.${ext}" }, "linux": { - "target": "AppImage" + "target": "zip", + "artifactName": "${productName}-${version}-${os}-${arch}.${ext}" } }, "keywords": [ diff --git a/packages/desktop/preload.js b/packages/desktop/preload.js index 21471a15..bafc940a 100644 --- a/packages/desktop/preload.js +++ b/packages/desktop/preload.js @@ -406,6 +406,8 @@ contextBridge.exposeInMainWorld('electronAPI', { return result.data; } }, + + // Data Manager interface data: { diff --git a/packages/ui/src/composables/index.ts b/packages/ui/src/composables/index.ts index 9c0191f5..8c314e92 100644 --- a/packages/ui/src/composables/index.ts +++ b/packages/ui/src/composables/index.ts @@ -11,5 +11,4 @@ export * from './useAutoScroll' export * from './useClipboard' export * from './useFullscreen' export * from 'vue-i18n' -export * from './useStorage' -export * from './useAppInitializer' \ No newline at end of file +export * from './useAppInitializer' \ No newline at end of file diff --git a/packages/ui/src/composables/useAppInitializer.ts b/packages/ui/src/composables/useAppInitializer.ts index 6e4617fc..6f75a41a 100644 --- a/packages/ui/src/composables/useAppInitializer.ts +++ b/packages/ui/src/composables/useAppInitializer.ts @@ -22,7 +22,7 @@ import { createPreferenceService, } from '../'; // 从UI包的index导入所有核心模块 import type { AppServices } from '../types/services'; -import type { IStorageProvider, IModelManager, ITemplateManager, IHistoryManager, ILLMService, IPromptService, IDataManager } from '@prompt-optimizer/core'; +import type { IModelManager, ITemplateManager, IHistoryManager, ILLMService, IPromptService, IDataManager } from '@prompt-optimizer/core'; import type { IPreferenceService } from '../types/services'; /** @@ -39,7 +39,7 @@ export function useAppInitializer() { try { console.log('[AppInitializer] 开始应用初始化...'); - let storageProvider: IStorageProvider; + let modelManager: IModelManager; let templateManager: ITemplateManager; let historyManager: IHistoryManager; @@ -58,29 +58,9 @@ export function useAppInitializer() { } console.log('[AppInitializer] Electron API 就绪,初始化代理服务...'); - // 在Electron渲染进程中,我们需要创建一个代理存储提供器 - // 这个代理会将所有存储操作转发到主进程 - storageProvider = { - async getItem(key: string): Promise { - console.warn('[ElectronStorageProxy] getItem called, but storage should be handled by main process'); - return null; - }, - async setItem(key: string, value: string): Promise { - console.warn('[ElectronStorageProxy] setItem called, but storage should be handled by main process'); - }, - async removeItem(key: string): Promise { - console.warn('[ElectronStorageProxy] removeItem called, but storage should be handled by main process'); - }, - async clearAll(): Promise { - console.warn('[ElectronStorageProxy] clear called, but storage should be handled by main process'); - }, - async updateData(key: string, modifier: (currentValue: T | null) => T): Promise { - console.warn('[ElectronStorageProxy] updateData called, but storage should be handled by main process'); - }, - async batchUpdate(operations: Array<{ key: string; operation: 'set' | 'remove'; value?: string }>): Promise { - console.warn('[ElectronStorageProxy] batchUpdate called, but storage should be handled by main process'); - } - }; + + // 在Electron环境中,不需要storageProvider + // 所有存储操作都通过各个manager的代理完成 // 在Electron环境中,我们实例化所有轻量级的代理类 modelManager = new ElectronModelManagerProxy(); @@ -97,7 +77,6 @@ export function useAppInitializer() { const templateLanguageService = new ElectronTemplateLanguageServiceProxy(); services.value = { - storageProvider, // 使用代理存储提供器 modelManager, templateManager, historyManager, @@ -112,7 +91,7 @@ export function useAppInitializer() { } else { console.log('[AppInitializer] 检测到Web环境,初始化完整服务...'); // 在Web环境中,我们创建一套完整的、真实的服务 - storageProvider = StorageFactory.create('dexie'); + const storageProvider = StorageFactory.create('dexie'); const languageService = createTemplateLanguageService(storageProvider); // Services with no dependencies or only storage @@ -201,7 +180,6 @@ export function useAppInitializer() { // 将所有服务实例赋值给 services.value services.value = { - storageProvider, modelManager: modelManagerAdapter, // 使用适配器 templateManager: templateManagerAdapter, // 使用适配器 historyManager: historyManagerAdapter, // 使用适配器 diff --git a/packages/ui/src/types/services.ts b/packages/ui/src/types/services.ts index 648e5ab9..6e3fd235 100644 --- a/packages/ui/src/types/services.ts +++ b/packages/ui/src/types/services.ts @@ -1,5 +1,4 @@ import type { - IStorageProvider, IModelManager, ITemplateManager, IHistoryManager, @@ -22,7 +21,6 @@ export interface IPreferenceService { * 统一的应用服务接口定义 */ export interface AppServices { - storageProvider: IStorageProvider; modelManager: IModelManager; templateManager: ITemplateManager; historyManager: IHistoryManager; diff --git a/packages/ui/tests/unit/usePromptOptimizer-model-validation.test.ts b/packages/ui/tests/unit/usePromptOptimizer-model-validation.test.ts index 1cf1d175..7230a555 100644 --- a/packages/ui/tests/unit/usePromptOptimizer-model-validation.test.ts +++ b/packages/ui/tests/unit/usePromptOptimizer-model-validation.test.ts @@ -10,12 +10,7 @@ vi.mock('../../src/composables/useToast', () => ({ }) })) -vi.mock('../../src/composables/useStorage', () => ({ - useStorage: () => ({ - getItem: vi.fn().mockResolvedValue(null), - setItem: vi.fn().mockResolvedValue(undefined) - }) -})) +// useStorage已被移除,不再需要mock vi.mock('vue-i18n', () => ({ useI18n: () => ({