From 477e6da93d095f96994cd368f113f18ebf16b5e5 Mon Sep 17 00:00:00 2001 From: baiqing Date: Thu, 14 May 2026 14:59:31 +0800 Subject: [PATCH 1/3] feat(style-pack): rework UI + template-create path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI: - 「原文」改成标题旁的 pill 切换器,与 builtin 卡分离 - builtin 卡片按 light → structured → formal 排序,followed by imported 包 - 「+ 新建风格包」tile 固定在网格末位 - imported 卡片右上角换成红色 trash 删除按钮(builtin 显示装饰 sparkle 图标,无 delete) - builtin 卡片背景做灰底处理;编辑按钮 disabled,"只读" - 编辑按钮挪到底部,紧接「导出」右边 后端: - 新增 IPC create_style_pack_from_template / persistence.create_from_template,「+」走这条路径直接落盘 - 编辑面板出场加 modal-backdrop-out / modal-drawer-out 两个 keyframe 清理: - 删除 Settings.tsx 里 PR #429 留下的"润色 System Prompt 已迁移"死告示卡 - 删除 11 个相关 i18n key 跨 5 种语言 - 删除卡片上的「轮换 ON/OFF」按钮 + 编辑面板里同款 + metaStatus item - BusyAction 移除 'toggling' 变体 - 删除 BUILTIN 选中态与 active 共用 highlight 的 bug - SavedToast 统一替换 inline notice/error banner --- openless-all/app/src-tauri/src/commands.rs | 20 + openless-all/app/src-tauri/src/lib.rs | 1 + openless-all/app/src-tauri/src/persistence.rs | 31 ++ openless-all/app/src/i18n/en.ts | 11 - openless-all/app/src/i18n/ja.ts | 11 - openless-all/app/src/i18n/ko.ts | 11 - openless-all/app/src/i18n/zh-CN.ts | 11 - openless-all/app/src/i18n/zh-TW.ts | 11 - openless-all/app/src/lib/ipc.ts | 16 + openless-all/app/src/pages/Settings.tsx | 28 -- openless-all/app/src/pages/Style.tsx | 473 +++++++++++------- openless-all/app/src/styles/global.css | 32 ++ 12 files changed, 393 insertions(+), 263 deletions(-) diff --git a/openless-all/app/src-tauri/src/commands.rs b/openless-all/app/src-tauri/src/commands.rs index 9859d8ca..0b338e7f 100644 --- a/openless-all/app/src-tauri/src/commands.rs +++ b/openless-all/app/src-tauri/src/commands.rs @@ -1256,6 +1256,26 @@ pub fn list_style_packs(coord: CoordinatorState<'_>) -> Result, S .map_err(|e| e.to_string()) } +#[tauri::command] +pub fn create_style_pack_from_template( + coord: CoordinatorState<'_>, + app: AppHandle, + template: StylePack, +) -> Result { + log::info!( + "[style-pack] command create_from_template name={} base_mode={:?}", + template.name, + template.base_mode + ); + let created = coord + .style_packs() + .create_from_template(template) + .map_err(|e| e.to_string())?; + let prefs = coord.prefs().get(); + let _ = sync_style_pack_prefs_and_persist(&*coord, &app, prefs)?; + Ok(created) +} + #[tauri::command] pub fn save_style_pack( coord: CoordinatorState<'_>, diff --git a/openless-all/app/src-tauri/src/lib.rs b/openless-all/app/src-tauri/src/lib.rs index 420c4ba8..417e0a4c 100644 --- a/openless-all/app/src-tauri/src/lib.rs +++ b/openless-all/app/src-tauri/src/lib.rs @@ -301,6 +301,7 @@ pub fn run() { commands::inject_hotkey_click_for_dev, commands::repolish, commands::list_style_packs, + commands::create_style_pack_from_template, commands::save_style_pack, commands::preview_style_pack_runtime, commands::set_active_style_pack, diff --git a/openless-all/app/src-tauri/src/persistence.rs b/openless-all/app/src-tauri/src/persistence.rs index f4722a12..854b7d98 100644 --- a/openless-all/app/src-tauri/src/persistence.rs +++ b/openless-all/app/src-tauri/src/persistence.rs @@ -1023,6 +1023,37 @@ impl StylePackStore { .ok_or_else(|| anyhow!("no enabled style pack available")) } + /// 从模板新建一个 imported 风格包("+"按钮路径)。 + /// 跟 ZIP 导入不同:没有 manifest.json、没有 assets,纯空白模板。 + /// 调用方负责 set `prefs.active_style_pack_id` 等高层 wiring(这里只管落盘)。 + pub fn create_from_template(&self, template: StylePack) -> Result { + let mut packs = self.state.lock(); + let base_id = if template.id.trim().is_empty() { + format!("imported-{}", Uuid::new_v4().simple()) + } else { + template.id.clone() + }; + let assigned_id = unique_imported_style_pack_id(&packs, &base_id); + let now = Utc::now().to_rfc3339(); + let mut pack = template; + pack.id = assigned_id; + pack.kind = StylePackKind::Imported; + pack.created_at = Some(now.clone()); + pack.updated_at = Some(now); + pack.active = false; + pack.enabled = true; + packs.push(pack.clone()); + write_style_packs_file(&self.path, &packs)?; + log::info!( + "[style-pack] created from template id={} base_mode={:?} prompt_chars={} examples={}", + pack.id, + pack.base_mode, + pack.prompt.chars().count(), + pack.examples.len() + ); + Ok(pack) + } + pub fn upsert(&self, incoming: StylePack) -> Result { let mut packs = self.state.lock(); let index = packs diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index f5ee3c72..f4e017e7 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -384,17 +384,6 @@ export const en: typeof zhCN = { llmProviderDesc: 'Selecting a preset auto-fills the default Base URL.', credentialStorageNotice: 'Credentials are stored in the OS credential vault. Legacy local JSON credentials are migrated into the vault and removed after a successful write.', codexOAuthNotice: 'Codex OAuth uses the local Codex login state (~/.codex/auth.json). OpenLess does not store an API key or Base URL for this provider.', - styleSystemPromptTitle: 'Polish system prompts', - styleSystemPromptDesc: 'Each built-in style now exposes its full system prompt here. Saved edits are shared by live polish and History repolish.', - styleSystemPromptPlaceholder: 'Enter the full system prompt for this style', - styleSystemPromptHint: 'This edits the full prompt, not just an appended suffix. Press Ctrl/Cmd+Enter to save as well.', - styleSystemPromptSave: 'Save prompt', - styleSystemPromptReset: 'Reset to default', - styleSystemPromptDirty: 'Unsaved', - styleSystemPromptSaveFailed: 'Failed to save system prompt: {{error}}', - styleSystemPromptMovedBadge: 'Migrated', - styleSystemPromptMovedDesc: 'Full system prompts are no longer edited per mode on the Settings page. They now live in the Style Pack detail panel on the Style page.', - styleSystemPromptMovedHint: 'To change runtime prompts, examples, tags, or ZIP import/export, use the Style page.', asrProviderDesc: 'Switching providers automatically loads the matching credentials.', asrTitle: 'ASR (transcription)', asrDesc: 'Used to turn speech into text in real time.', diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 162d47a9..9f89fc58 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -386,17 +386,6 @@ export const ja: typeof zhCN = { llmProviderDesc: '選択するとデフォルトの Base URL が自動入力されます。', credentialStorageNotice: '認証情報は OS の認証情報ストアに保存されます。旧バージョンのローカル JSON 認証情報はストアへ移行され、書き込み成功後に削除されます。', codexOAuthNotice: 'Codex OAuth はローカルの Codex ログイン状態(~/.codex/auth.json)を使用します。OpenLess は API Key や Base URL を保存しません。', - styleSystemPromptTitle: 'Polish system prompts', - styleSystemPromptDesc: 'Each built-in style now exposes its full system prompt here. Saved edits are shared by live polish and History repolish.', - styleSystemPromptPlaceholder: 'Enter the full system prompt for this style', - styleSystemPromptHint: 'This edits the full prompt, not just an appended suffix. Press Ctrl/Cmd+Enter to save as well.', - styleSystemPromptSave: 'Save prompt', - styleSystemPromptReset: 'Reset to default', - styleSystemPromptDirty: 'Unsaved', - styleSystemPromptSaveFailed: 'Failed to save system prompt: {{error}}', - styleSystemPromptMovedBadge: '移行済み', - styleSystemPromptMovedDesc: '完全な system prompt は設定ページでモード別に直接編集せず、「スタイル」ページの Style Pack 詳細パネルに集約されました。', - styleSystemPromptMovedHint: '実行時 prompt、例、タグ、ZIP のインポート/エクスポートを変更する場合は「スタイル」ページで操作してください。', asrProviderDesc: '切り替えると対応する認証情報が自動選択されます。', asrTitle: 'ASR 音声(転写)', asrDesc: '口述をリアルタイムでテキストに転写。', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index 32890cd4..60461e87 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -389,17 +389,6 @@ export const ko: typeof zhCN = { asrProviderDesc: '전환 시 해당하는 자격 증명이 자동 선택됩니다.', asrTitle: 'ASR 음성(전사)', asrDesc: '구술을 실시간으로 텍스트로 전사합니다.', - styleSystemPromptTitle: 'Polish system prompts', - styleSystemPromptDesc: 'Each built-in style now exposes its full system prompt here. Saved edits are shared by live polish and History repolish.', - styleSystemPromptPlaceholder: 'Enter the full system prompt for this style', - styleSystemPromptHint: 'This edits the full prompt, not just an appended suffix. Press Ctrl/Cmd+Enter to save as well.', - styleSystemPromptSave: 'Save prompt', - styleSystemPromptReset: 'Reset to default', - styleSystemPromptDirty: 'Unsaved', - styleSystemPromptSaveFailed: 'Failed to save system prompt: {{error}}', - styleSystemPromptMovedBadge: '마이그레이션됨', - styleSystemPromptMovedDesc: '전체 system prompt는 더 이상 설정 페이지에서 모드별로 직접 편집하지 않고, 스타일 페이지의 Style Pack 상세 패널로 통합되었습니다.', - styleSystemPromptMovedHint: '런타임 prompt, 예시, 태그 또는 ZIP 가져오기/내보내기를 변경하려면 스타일 페이지에서 작업하세요.', presets: { ark: 'ARK (Volcengine Ark)', deepseek: 'DeepSeek', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index 5d75fcae..1789698e 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -382,17 +382,6 @@ export const zhCN = { llmProviderDesc: '选择后将自动填入 Base URL 默认值。', credentialStorageNotice: '凭据保存在系统凭据库中。旧版本地 JSON 凭据会迁移到系统凭据库,并在成功写入后删除。', codexOAuthNotice: 'Codex OAuth 使用本机 Codex 登录状态(~/.codex/auth.json),无需在 OpenLess 中保存 API Key 或 Base URL。', - styleSystemPromptTitle: '润色 System Prompt', - styleSystemPromptDesc: '每个内置风格都可以直接编辑完整 system prompt。保存后,实时润色和 History 里的 repolish 会共用这一套提示词。', - styleSystemPromptPlaceholder: '输入该风格的完整 system prompt', - styleSystemPromptHint: '这里编辑的是完整提示词,不再只是附加片段。按 Ctrl/Cmd+Enter 也可保存。', - styleSystemPromptSave: '保存 Prompt', - styleSystemPromptReset: '恢复默认', - styleSystemPromptDirty: '未保存', - styleSystemPromptSaveFailed: '保存 System Prompt 失败:{{error}}', - styleSystemPromptMovedBadge: '已迁移', - styleSystemPromptMovedDesc: '完整 system prompt 不再在设置页里按模式硬编码编辑,而是统一收敛到「风格」页的 Style Pack 详情面板。', - styleSystemPromptMovedHint: '如果你要改运行时 prompt、示例、标签或导入导出 ZIP,请去「风格」页操作。', asrProviderDesc: '切换后将自动选用对应凭据。', asrTitle: 'ASR 语音(转写)', asrDesc: '用于将口述实时转写为文本。', diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index e694f04c..d3820b8a 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -384,17 +384,6 @@ export const zhTW: typeof zhCN = { llmProviderDesc: '選擇後將自動填入 Base URL 默認值。', credentialStorageNotice: '憑據儲存在系統憑據庫中。舊版本機 JSON 憑據會遷移到系統憑據庫,並在成功寫入後刪除。', codexOAuthNotice: 'Codex OAuth 使用本機 Codex 登入狀態(~/.codex/auth.json),無需在 OpenLess 中保存 API Key 或 Base URL。', - styleSystemPromptTitle: '潤色 System Prompt', - styleSystemPromptDesc: '每個內建風格都可以直接編輯完整 system prompt。儲存後,即時潤色和 History 裡的 repolish 會共用這一套提示詞。', - styleSystemPromptPlaceholder: '輸入該風格的完整 system prompt', - styleSystemPromptHint: '這裡編輯的是完整提示詞,不再只是附加片段。按 Ctrl/Cmd+Enter 也可儲存。', - styleSystemPromptSave: '儲存 Prompt', - styleSystemPromptReset: '恢復預設', - styleSystemPromptDirty: '未儲存', - styleSystemPromptSaveFailed: '儲存 System Prompt 失敗:{{error}}', - styleSystemPromptMovedBadge: '已遷移', - styleSystemPromptMovedDesc: '完整 system prompt 不再在設定頁裡按模式硬編碼編輯,而是統一收斂到「風格」頁的 Style Pack 詳情面板。', - styleSystemPromptMovedHint: '如果你要改執行時 prompt、範例、標籤或匯入匯出 ZIP,請去「風格」頁操作。', asrProviderDesc: '切換後將自動選用對應憑據。', asrTitle: 'ASR 語音(轉寫)', asrDesc: '用於將口述實時轉寫爲文本。', diff --git a/openless-all/app/src/lib/ipc.ts b/openless-all/app/src/lib/ipc.ts index a3fd0c09..8faa5063 100644 --- a/openless-all/app/src/lib/ipc.ts +++ b/openless-all/app/src/lib/ipc.ts @@ -689,6 +689,22 @@ export function saveStylePack(stylePack: StylePack): Promise { }); } +export function createStylePackFromTemplate(template: StylePack): Promise { + return invokeOrMock('create_style_pack_from_template', { template }, () => { + const created: StylePack = { + ...cloneStylePack(template), + id: `imported-mock-${Date.now()}`, + kind: 'imported', + active: false, + enabled: true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + mockStylePacks = [...mockStylePacks, created]; + return cloneStylePack(created); + }); +} + export function previewStylePackRuntime(stylePack: StylePack): Promise { return invokeOrMock('preview_style_pack_runtime', { stylePack }, () => composeMockStylePackRuntimeDiagnostics(stylePack)); } diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx index 427bc793..e89fb27b 100644 --- a/openless-all/app/src/pages/Settings.tsx +++ b/openless-all/app/src/pages/Settings.tsx @@ -1607,34 +1607,6 @@ function ProvidersSection() { setLlmModelRevision(v => v + 1)} /> - -
-
{t('settings.providers.styleSystemPromptTitle')}
-
- {t('settings.providers.styleSystemPromptDesc')} -
-
-
-
- {t('settings.providers.styleSystemPromptMovedBadge')} - {t('settings.providers.styleSystemPromptTitle')} -
-
- {t('settings.providers.styleSystemPromptMovedDesc')} -
-
- {t('settings.providers.styleSystemPromptMovedHint')} -
-
-
-
{t('settings.providers.asrTitle')}
diff --git a/openless-all/app/src/pages/Style.tsx b/openless-all/app/src/pages/Style.tsx index 1b34406a..b69232e8 100644 --- a/openless-all/app/src/pages/Style.tsx +++ b/openless-all/app/src/pages/Style.tsx @@ -1,6 +1,7 @@ -import { type CSSProperties, useEffect, useState } from 'react'; +import { type CSSProperties, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { + createStylePackFromTemplate, deleteStylePack, exportStylePackToZip, importStylePackFromZip, @@ -10,11 +11,11 @@ import { resetBuiltinStylePack, saveStylePack, setActiveStylePack, - setStylePackEnabled, } from '../lib/ipc'; import type { PolishMode, StylePack, StylePackExample, StylePackRuntimeDiagnostics } from '../lib/types'; import { Btn, Card, PageHeader, Pill } from './_atoms'; import { Icon } from '../components/Icon'; +import { SavedToast, type SaveToastState } from '../components/SavedToast'; type BusyAction = | 'loading' @@ -22,11 +23,31 @@ type BusyAction = | 'importing' | 'exporting' | 'activating' - | 'toggling' | 'resetting' | 'deleting' + | 'creating' | null; +const BUILTIN_RAW_ID = 'builtin.raw'; +const BUILTIN_BODY_ORDER = ['builtin.light', 'builtin.structured', 'builtin.formal']; + +const NEW_PACK_TEMPLATE_BASE: Omit = { + name: '未命名风格', + description: '简短描述这个风格的使用场景。', + author: null, + version: '1.0.0', + kind: 'imported', + baseMode: 'light', + prompt: '你是 OpenLess 的润色助手。请将口语化的转写整理为顺畅、自然、可直接发送的文字,但不要扩写事实。', + examples: [], + tags: [], + iconPath: null, + enabled: true, + active: false, + recommendedModel: null, + compatibleAppVersion: null, +}; + function clonePack(pack: StylePack): StylePack { return { ...pack, @@ -86,11 +107,7 @@ export function Style() { builtin: isEnglish ? 'Built-in' : '内置', imported: isEnglish ? 'Imported' : '导入', active: isEnglish ? 'Active' : '当前', - enabled: isEnglish ? 'In Rotation' : '已加入轮换', - disabled: isEnglish ? 'Out of Rotation' : '未加入轮换', activate: isEnglish ? 'Activate' : '激活', - enable: isEnglish ? 'Rotation ON' : '轮换 ON', - disable: isEnglish ? 'Rotation OFF' : '轮换 OFF', edit: isEnglish ? 'Edit' : '编辑', closeEditor: isEnglish ? 'Close' : '关闭', unsaved: isEnglish ? 'Unsaved' : '未保存', @@ -99,15 +116,17 @@ export function Style() { ? 'Browse and switch packs.' : '浏览和切换风格包。', listCount: (count: number) => (isEnglish ? `${count} packs` : `${count} 个风格包`), + addPackTileTitle: isEnglish ? 'New Pack' : '新建风格包', + addPackTileHint: isEnglish ? 'Start from a blank template.' : '从空白模板开始。', + createSuccess: isEnglish ? 'New pack created.' : '已创建新风格包', + createFailed: (message: string) => (isEnglish ? `Failed to create pack: ${message}` : `创建风格包失败:${message}`), + builtinPackEditLabel: (name: string) => (isEnglish ? `Edit "${name}"` : `编辑「${name}」`), save: isEnglish ? 'Save' : '保存', revert: isEnglish ? 'Revert' : '撤销', saveSuccess: isEnglish ? 'Style pack saved.' : '风格包已保存', saveFailed: (message: string) => (isEnglish ? `Failed to save style pack: ${message}` : `保存风格包失败:${message}`), activateSuccess: (name: string) => (isEnglish ? `Set "${name}" as current.` : `已将“${name}”设为当前风格`), activateFailed: (message: string) => (isEnglish ? `Failed to set current style pack: ${message}` : `设为当前风格失败:${message}`), - enableSuccess: (name: string) => (isEnglish ? `Added "${name}" to rotation.` : `已将“${name}”加入轮换`), - disableSuccess: (name: string) => (isEnglish ? `Removed "${name}" from rotation.` : `已将“${name}”移出轮换`), - toggleFailed: (message: string) => (isEnglish ? `Failed to change rotation status: ${message}` : `切换轮换状态失败:${message}`), importSuccess: (name: string) => (isEnglish ? `Imported "${name}".` : `已导入“${name}”`), importFailed: (message: string) => (isEnglish ? `Failed to import ZIP: ${message}` : `导入 ZIP 失败:${message}`), exportSuccess: (path: string) => (isEnglish ? `Exported to ${path}` : `已导出到 ${path}`), @@ -122,12 +141,6 @@ export function Style() { : `确定删除“${name}”吗?删除后无法恢复。`), deleteSuccess: (name: string) => (isEnglish ? `Deleted "${name}".` : `已删除“${name}”`), deleteFailed: (message: string) => (isEnglish ? `Failed to delete pack: ${message}` : `删除风格包失败:${message}`), - summaryBuiltin: isEnglish ? 'Built-in Packs' : '内置风格', - summaryBuiltinHint: isEnglish ? 'Default product semantics with one-click reset.' : '跟随产品默认语义,可一键重置到官方基线。', - summaryImported: isEnglish ? 'Imported Packs' : '导入风格', - summaryImportedHint: isEnglish ? 'Installed from ZIP and fully portable.' : '来自 ZIP 包,可启用、编辑、导出和删除。', - summaryEnabled: isEnglish ? 'In Rotation' : '已加入轮换', - summaryCurrent: (name: string) => (isEnglish ? `Current: ${name}` : `当前启用:${name}`), summaryCurrentEmpty: isEnglish ? 'No pack selected yet' : '还没有选中风格包', editorTitle: isEnglish ? 'Edit Pack' : '编辑风格', editorDesc: isEnglish @@ -136,7 +149,6 @@ export function Style() { metaTitle: isEnglish ? 'Installation Info' : '安装信息', metaSource: isEnglish ? 'Source' : '来源', metaBaseMode: isEnglish ? 'Base Mode' : '基础模式', - metaStatus: isEnglish ? 'Rotation' : '轮换状态', metaUpdatedAt: isEnglish ? 'Updated' : '更新时间', fieldName: isEnglish ? 'Name' : '名称', fieldAuthor: isEnglish ? 'Author' : '作者', @@ -194,18 +206,42 @@ export function Style() { }; const [packs, setPacks] = useState([]); + const [selectedId, setSelectedId] = useState(null); const [draft, setDraft] = useState(null); const [busy, setBusy] = useState('loading'); - const [error, setError] = useState(null); - const [notice, setNotice] = useState(null); + const [saveState, setSaveState] = useState('idle'); + const [saveMessage, setSaveMessage] = useState(''); + const statusTimer = useRef(null); const [editorOpen, setEditorOpen] = useState(false); + const [editorClosing, setEditorClosing] = useState(false); + const editorCloseTimer = useRef(null); const [runtimePreview, setRuntimePreview] = useState(null); const [runtimePreviewError, setRuntimePreviewError] = useState(null); + useEffect(() => () => { + if (statusTimer.current !== null) window.clearTimeout(statusTimer.current); + if (editorCloseTimer.current !== null) window.clearTimeout(editorCloseTimer.current); + }, []); + + const showSaveStatus = (state: SaveToastState, message: string, temporary = false) => { + if (statusTimer.current !== null) { + window.clearTimeout(statusTimer.current); + statusTimer.current = null; + } + setSaveState(state); + setSaveMessage(message); + if (temporary) { + statusTimer.current = window.setTimeout(() => { + setSaveState('idle'); + setSaveMessage(''); + statusTimer.current = null; + }, 1600); + } + }; + const loadPacks = async (preferredId?: string | null) => { setBusy('loading'); - setError(null); try { const next = await listStylePacks(); setPacks(next); @@ -216,7 +252,7 @@ export function Style() { null; setSelectedId(nextSelectedId); } catch (loadError) { - setError(copy.loadFailed(String(loadError))); + showSaveStatus('failed', copy.loadFailed(String(loadError))); } finally { setBusy(null); } @@ -248,6 +284,12 @@ export function Style() { const selectedPack = packs.find(pack => pack.id === selectedId) ?? null; const activePack = packs.find(pack => pack.active) ?? null; + const rawPack = packs.find(pack => pack.id === BUILTIN_RAW_ID) ?? null; + const otherBuiltinPacks = packs + .filter(pack => pack.kind === 'builtin' && pack.id !== BUILTIN_RAW_ID) + .sort((a, b) => BUILTIN_BODY_ORDER.indexOf(a.id) - BUILTIN_BODY_ORDER.indexOf(b.id)); + const importedPacks = packs.filter(pack => pack.kind === 'imported'); + const bodyPacks = [...otherBuiltinPacks, ...importedPacks]; const builtinCount = packs.filter(pack => pack.kind === 'builtin').length; const importedCount = packs.filter(pack => pack.kind === 'imported').length; const enabledCount = packs.filter(pack => pack.enabled).length; @@ -284,8 +326,6 @@ export function Style() { const focusPack = (packId: string) => { setSelectedId(packId); - setNotice(null); - setError(null); }; const discardDraftChanges = () => { @@ -294,14 +334,26 @@ export function Style() { } }; + const startEditorClose = () => { + if (editorClosing) return; + setEditorClosing(true); + if (editorCloseTimer.current !== null) window.clearTimeout(editorCloseTimer.current); + editorCloseTimer.current = window.setTimeout(() => { + setEditorOpen(false); + setEditorClosing(false); + editorCloseTimer.current = null; + }, 200); + }; + const closeEditor = () => { + if (editorClosing) return; if (dirty) { if (!window.confirm(copy.discardCloseConfirm)) { return; } discardDraftChanges(); } - setEditorOpen(false); + startEditorClose(); }; const openEditorForPack = (pack: StylePack) => { @@ -310,14 +362,17 @@ export function Style() { return; } } + if (editorCloseTimer.current !== null) { + window.clearTimeout(editorCloseTimer.current); + editorCloseTimer.current = null; + } + setEditorClosing(false); focusPack(pack.id); setEditorOpen(true); }; useEffect(() => { if (!editorOpen) return; - const previousOverflow = document.body.style.overflow; - document.body.style.overflow = 'hidden'; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { event.preventDefault(); @@ -326,7 +381,6 @@ export function Style() { }; window.addEventListener('keydown', handleKeyDown); return () => { - document.body.style.overflow = previousOverflow; window.removeEventListener('keydown', handleKeyDown); }; }, [editorOpen, dirty, selectedPack, draft]); @@ -359,23 +413,19 @@ export function Style() { }); }; - const showSuccess = (message: string) => { - setNotice(message); - setError(null); - }; - const handleSave = async () => { if (!draft) return; setBusy('saving'); + showSaveStatus('saving', t('common.saving')); try { const saved = await saveStylePack({ ...draft, tags: draft.tags.filter(Boolean), }); - showSuccess(copy.saveSuccess); + showSaveStatus('saved', copy.saveSuccess, true); await loadPacks(saved.id); } catch (saveError) { - setError(copy.saveFailed(String(saveError))); + showSaveStatus('failed', copy.saveFailed(String(saveError))); } finally { setBusy(null); } @@ -385,23 +435,10 @@ export function Style() { setBusy('activating'); try { await setActiveStylePack(pack.id); - showSuccess(copy.activateSuccess(pack.name)); + showSaveStatus('saved', copy.activateSuccess(pack.name), true); await loadPacks(pack.id); } catch (activateError) { - setError(copy.activateFailed(String(activateError))); - } finally { - setBusy(null); - } - }; - - const handleToggleEnabled = async (pack: StylePack) => { - setBusy('toggling'); - try { - await setStylePackEnabled(pack.id, !pack.enabled); - showSuccess(pack.enabled ? copy.disableSuccess(pack.name) : copy.enableSuccess(pack.name)); - await loadPacks(pack.id); - } catch (toggleError) { - setError(copy.toggleFailed(String(toggleError))); + showSaveStatus('failed', copy.activateFailed(String(activateError))); } finally { setBusy(null); } @@ -412,28 +449,60 @@ export function Style() { setBusy('resetting'); try { await resetBuiltinStylePack(selectedPack.id); - showSuccess(copy.resetSuccess(selectedPack.name)); + showSaveStatus('saved', copy.resetSuccess(selectedPack.name), true); await loadPacks(selectedPack.id); } catch (resetError) { - setError(copy.resetFailed(String(resetError))); + showSaveStatus('failed', copy.resetFailed(String(resetError))); } finally { setBusy(null); } }; - const handleDeleteImported = async () => { - if (!selectedPack || selectedPack.kind !== 'imported') return; - if (!window.confirm(copy.deleteConfirm(selectedPack.name))) { + const handleDeleteImportedPack = async (pack: StylePack) => { + if (pack.kind !== 'imported') return; + if (!window.confirm(copy.deleteConfirm(pack.name))) { return; } setBusy('deleting'); try { - await deleteStylePack(selectedPack.id); - showSuccess(copy.deleteSuccess(selectedPack.name)); - setEditorOpen(false); + await deleteStylePack(pack.id); + showSaveStatus('saved', copy.deleteSuccess(pack.name), true); + if (editorOpen && selectedId === pack.id) { + startEditorClose(); + } await loadPacks(); } catch (deleteError) { - setError(copy.deleteFailed(String(deleteError))); + showSaveStatus('failed', copy.deleteFailed(String(deleteError))); + } finally { + setBusy(null); + } + }; + + const handleDeleteImported = async () => { + if (!selectedPack || selectedPack.kind !== 'imported') return; + await handleDeleteImportedPack(selectedPack); + }; + + const handleCreateFromTemplate = async () => { + setBusy('creating'); + try { + const template: StylePack = { + ...NEW_PACK_TEMPLATE_BASE, + id: '', + }; + const created = await createStylePackFromTemplate(template); + showSaveStatus('saved', copy.createSuccess, true); + await loadPacks(created.id); + // Re-fetch list, then open the editor on the new pack + if (editorCloseTimer.current !== null) { + window.clearTimeout(editorCloseTimer.current); + editorCloseTimer.current = null; + } + setEditorClosing(false); + setSelectedId(created.id); + setEditorOpen(true); + } catch (createError) { + showSaveStatus('failed', copy.createFailed(String(createError))); } finally { setBusy(null); } @@ -458,10 +527,10 @@ export function Style() { return; } const imported = await importStylePackFromZip(zipPath); - showSuccess(copy.importSuccess(imported.name)); + showSaveStatus('saved', copy.importSuccess(imported.name), true); await loadPacks(imported.id); } catch (importError) { - setError(copy.importFailed(String(importError))); + showSaveStatus('failed', copy.importFailed(String(importError))); } finally { setBusy(null); } @@ -470,8 +539,7 @@ export function Style() { const handleExportZip = async (pack = selectedPack) => { if (!pack) return; if (editorOpen && dirty && selectedPack && pack.id === selectedPack.id) { - setError(copy.exportDirtyFirst); - setNotice(null); + showSaveStatus('failed', copy.exportDirtyFirst); return; } setBusy('exporting'); @@ -492,9 +560,9 @@ export function Style() { return; } const savedPath = await exportStylePackToZip(pack.id, targetPath); - showSuccess(copy.exportSuccess(savedPath)); + showSaveStatus('saved', copy.exportSuccess(savedPath), true); } catch (exportError) { - setError(copy.exportFailed(String(exportError))); + showSaveStatus('failed', copy.exportFailed(String(exportError))); } finally { setBusy(null); } @@ -507,7 +575,7 @@ export function Style() { title={copy.title} desc={copy.desc} right={( -
+
void loadPacks(selectedId)} disabled={busy === 'loading'}> {t('common.refresh')} @@ -518,66 +586,51 @@ export function Style() { )} /> -
- -
- {copy.summaryBuiltin} -
-
{builtinCount}
-
{copy.summaryBuiltinHint}
-
- -
- {copy.summaryImported} -
-
{importedCount}
-
{copy.summaryImportedHint}
-
- -
- {copy.summaryEnabled} -
-
{enabledCount}
-
- {activePack ? copy.summaryCurrent(activePack.name) : copy.summaryCurrentEmpty} -
-
-
- - {(notice || error) && ( -
- {error ?? notice} -
- )} + - -
-
-
-
{copy.listTitle}
-
{copy.listDesc}
-
-
+ +
+
+
+
+
{copy.listTitle}
+
{copy.listDesc}
+
+ {rawPack && ( + + )} +
{copy.listCount(packs.length)}
-
-
-
- {packs.map(pack => { - const selected = pack.id === selectedId; +
+
+ {bodyPacks.map(pack => { + const isBuiltin = pack.kind === 'builtin'; return (
-
{pack.name}
- - {pack.kind === 'builtin' ? copy.builtin : copy.imported} - -
- {pack.active ? ( - {copy.active} - ) : !pack.enabled ? ( - {copy.disabled} - ) : null} +
+ {pack.name}
+ + {isBuiltin ? copy.builtin : copy.imported} + + {pack.active && {copy.active}}
-
+ {isBuiltin ? (
- +
- openEditorForPack(pack)} + ) : ( +
+ + + )}
@@ -674,24 +736,61 @@ export function Style() { void handleToggleEnabled(pack)} + icon="archive" + disabled={busy === 'exporting'} + onClick={() => void handleExportZip(pack)} > - {pack.enabled ? copy.disable : copy.enable} + {copy.exportShort} void handleExportZip(pack)} + icon="expand" + disabled={isBuiltin} + onClick={() => openEditorForPack(pack)} > - {copy.exportShort} + {copy.edit}
); })} +
@@ -704,10 +803,13 @@ export function Style() { style={{ position: 'fixed', inset: 0, - background: 'rgba(15,23,42,0.24)', - backdropFilter: 'blur(6px)', - WebkitBackdropFilter: 'blur(6px)', + background: 'rgba(15,17,22,0.32)', + backdropFilter: 'blur(8px) saturate(140%)', + WebkitBackdropFilter: 'blur(8px) saturate(140%)', zIndex: 40, + animation: editorClosing + ? 'ol-modal-backdrop-out 0.2s var(--ol-motion-soft) forwards' + : 'ol-modal-backdrop-in 0.2s var(--ol-motion-soft) both', }} />
- +
@@ -779,13 +885,6 @@ export function Style() { void handleExportZip()} disabled={busy === 'exporting'}> {copy.exportZip} - void handleToggleEnabled(draft)} - > - {draft.enabled ? copy.disable : copy.enable} - -
@@ -997,9 +1095,24 @@ export function Style() { style={{ ...inputStyle, fontWeight: 600 }} placeholder={copy.exampleTitlePlaceholder(index + 1)} /> - removeExample(index)}> - {t('common.delete')} - +
diff --git a/openless-all/app/src/styles/global.css b/openless-all/app/src/styles/global.css index 8cc79c58..a30aef3c 100644 --- a/openless-all/app/src/styles/global.css +++ b/openless-all/app/src/styles/global.css @@ -76,3 +76,35 @@ a { color: inherit; text-decoration: none; } filter: blur(0); } } + +@keyframes ol-modal-backdrop-in { + from { opacity: 0; backdrop-filter: blur(0); -webkit-backdrop-filter: blur(0); } + to { opacity: 1; backdrop-filter: blur(8px) saturate(140%); -webkit-backdrop-filter: blur(8px) saturate(140%); } +} + +@keyframes ol-modal-backdrop-out { + from { opacity: 1; backdrop-filter: blur(8px) saturate(140%); -webkit-backdrop-filter: blur(8px) saturate(140%); } + to { opacity: 0; backdrop-filter: blur(0); -webkit-backdrop-filter: blur(0); } +} + +@keyframes ol-modal-drawer-in { + from { + opacity: 0; + transform: translate3d(12px, 0, 0) scale(0.985); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1); + } +} + +@keyframes ol-modal-drawer-out { + from { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1); + } + to { + opacity: 0; + transform: translate3d(12px, 0, 0) scale(0.985); + } +} From 883ff74cf4dbbaafedc29a8d5d98fbb2dd83a4f5 Mon Sep 17 00:00:00 2001 From: baiqing Date: Thu, 14 May 2026 14:59:36 +0800 Subject: [PATCH 2/3] docs(marketplace): add style pack marketplace planning doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 预留风格包市场的 HTTP API + IPC 契约 + DTO + 鉴权/缓存/安全策略。 IPC stub 未实装;后端 endpoint 选型 + 服务端工程化待定。 --- docs/style-pack-marketplace.md | 299 +++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 docs/style-pack-marketplace.md diff --git a/docs/style-pack-marketplace.md b/docs/style-pack-marketplace.md new file mode 100644 index 00000000..eba69293 --- /dev/null +++ b/docs/style-pack-marketplace.md @@ -0,0 +1,299 @@ +# Style Pack Marketplace — 规划文档 + +**状态**:规划中(API 已预留 stub,未实装) +**起草日期**:2026-05-14 +**owner**:待定 + +## 1. 目标 + +把现在「ZIP 包本地导入 / 导出」的体验扩展成一个公开的风格包市场: + +- 用户可以把自己调好的风格包**上传**到云端,附带名称、描述、作者署名、标签、效果示例 +- 其他用户可以**浏览 / 搜索 / 下载**别人的风格包,一键安装到本地 +- 后期支持**版本升级提醒**、**收藏 / 评分**等基础社交属性 + +非目标(v1 不做): +- 付费 / 抽成 +- 风格包内嵌外部 prompt 注入 / 跨域 fetch(安全考虑,风格包始终是纯文本 prompt) +- 多人协作编辑 / fork + +## 2. 架构概览 + +``` +┌──────────────────┐ HTTPS ┌─────────────────────┐ +│ OpenLess client │ ◄──────────────────► │ marketplace API │ +│ (Tauri 2) │ JSON over TLS │ (TBD: Cloudflare │ +│ │ │ Workers / D1 / │ +│ Rust IPC → │ │ R2 for blobs) │ +│ reqwest client │ │ │ +└──────────────────┘ └─────────────────────┘ + │ │ + │ local cache (~/Library/Application │ + │ Support/OpenLess/market_cache/) │ + ▼ ▼ + StylePackStore Postgres / D1 + (existing local listings + R2 blobs + persistence layer) +``` + +**关键约束**: +- 客户端只能上传 / 下载 ZIP **bundle**(不直接传 JSON),保持跟现有 ZIP import/export 同构 +- 服务端 ZIP 验证:解压后必须能反序列化成 `StylePack`、`prompt.chars().count() <= 50_000`、没有可执行附件 +- 风格包 ID 上传后由服务端分配(`{author_slug}-{name_slug}-{version}`),跟本地 ID 解耦 +- 客户端始终拿 ZIP 走现有 `import_style_pack_from_zip` 路径入库 —— 不另开一条「从市场直接写 Pack」的代码路径,避免双入口 + +## 3. HTTP API 规约 + +Base URL(待定):`https://api.openless.app/v1/marketplace/` + +所有响应统一信封: +```json +{ + "ok": true, + "data": | null, + "error": null | { "code": "ERR_XXX", "message": "..." } +} +``` + +### 3.1 GET `/packs` — 列表 / 搜索 + +Query: +| 参数 | 类型 | 默认 | 说明 | +|---|---|---|---| +| `q` | string | `""` | 关键词(名称 / 描述 / 标签) | +| `tag` | string | `""` | 单标签筛选 | +| `sort` | `recent` \| `popular` \| `name` | `recent` | 排序 | +| `cursor` | string | `null` | 分页游标 | +| `limit` | int (1-100) | `20` | 每页条数 | + +Response data: +```typescript +{ + packs: MarketPackListing[]; + next_cursor: string | null; +} +``` + +`MarketPackListing`: +```typescript +{ + id: string; // server-assigned, e.g. "alice-formal-v2.1" + name: string; + description: string; + author: string; + version: string; // semver + tags: string[]; + base_mode: "raw" | "light" | "structured" | "professional"; + recommended_model: string | null; + compatible_app_version: string | null; + downloads: number; + rating_avg: number | null; + rating_count: number; + updated_at: string; // ISO8601 + zip_size_bytes: number; + zip_sha256: string; // 客户端下载后校验 +} +``` + +### 3.2 GET `/packs/{id}` — 详情 + +Response data:`MarketPackListing` + 额外字段: +```typescript +{ + ...listing, + examples: StylePackExample[]; // 解压 ZIP 前的预览 + changelog: string | null; + homepage_url: string | null; +} +``` + +### 3.3 GET `/packs/{id}/download` — 下载 ZIP + +Response:`application/zip` 二进制流,带 `X-Pack-SHA256` header 用于校验。 + +服务端通过 redirect 直接指向 R2 / S3 预签 URL,避免代理流量。 + +### 3.4 POST `/packs` — 上传(需鉴权) + +Headers:`Authorization: Bearer ` +Body:`multipart/form-data` with field `pack=@xxx.zip` + +Response data:`MarketPackListing`(含新分配 id) + +错误码: +- `ERR_INVALID_ZIP` — ZIP 解压失败 / 不是合法 StylePack JSON +- `ERR_PROMPT_TOO_LARGE` — prompt 字数超 50k +- `ERR_DUPLICATE_VERSION` — 同 author+name+version 已存在 +- `ERR_RATE_LIMITED` — 触发限频 + +### 3.5 DELETE `/packs/{id}` — 撤回(需鉴权 + 必须是上传者) + +### 3.6 POST `/packs/{id}/rate` — 评分(需鉴权) + +Body:`{ score: 1..5, comment?: string }` + +## 4. IPC 契约(Rust ↔ TS) + +在 `src-tauri/src/commands.rs` 新增以下 stub(暂返回 `Err("not implemented yet")`,等服务端落地后实装): + +```rust +// 列表 / 搜索 +#[tauri::command] +pub async fn market_list_packs( + query: Option, + tag: Option, + sort: Option, + cursor: Option, + limit: Option, +) -> Result; + +// 详情 +#[tauri::command] +pub async fn market_get_pack(id: String) -> Result; + +// 下载 + 自动调用现有的 import_style_pack_from_zip 入库 +#[tauri::command] +pub async fn market_download_pack( + coord: CoordinatorState<'_>, + app: AppHandle, + id: String, +) -> Result; + +// 上传(dirty 字段 = 已编辑、未保存) +#[tauri::command] +pub async fn market_upload_pack( + coord: CoordinatorState<'_>, + pack_id: String, + api_key: String, +) -> Result; + +// 撤回 +#[tauri::command] +pub async fn market_delete_pack(id: String, api_key: String) -> Result<(), String>; + +// 评分 +#[tauri::command] +pub async fn market_rate_pack( + id: String, + api_key: String, + score: u8, + comment: Option, +) -> Result<(), String>; +``` + +DTO(在 `types.rs` 新增): +```rust +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct MarketPackListing { + pub id: String, + pub name: String, + pub description: String, + pub author: String, + pub version: String, + pub tags: Vec, + pub base_mode: PolishMode, + pub recommended_model: Option, + pub compatible_app_version: Option, + pub downloads: u64, + pub rating_avg: Option, + pub rating_count: u32, + pub updated_at: String, + pub zip_size_bytes: u64, + pub zip_sha256: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct MarketPackDetail { + #[serde(flatten)] + pub listing: MarketPackListing, + pub examples: Vec, + pub changelog: Option, + pub homepage_url: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct MarketListResponse { + pub packs: Vec, + pub next_cursor: Option, +} +``` + +TS wrappers(`src/lib/ipc.ts`): +```typescript +export interface MarketPackListing { /* same shape */ } +export interface MarketPackDetail extends MarketPackListing { /* + examples, changelog, homepage_url */ } +export interface MarketListResponse { packs: MarketPackListing[]; next_cursor: string | null; } + +export function marketListPacks(opts: { + query?: string; tag?: string; sort?: 'recent' | 'popular' | 'name'; + cursor?: string; limit?: number; +}): Promise; +export function marketGetPack(id: string): Promise; +export function marketDownloadPack(id: string): Promise; +export function marketUploadPack(packId: string, apiKey: string): Promise; +export function marketDeletePack(id: string, apiKey: string): Promise; +export function marketRatePack(id: string, apiKey: string, score: number, comment?: string): Promise; +``` + +## 5. 鉴权模型 + +**v1 简化方案**: +- 用户在设置页输入个人 API key(服务端发放) +- API key 存到 OS Keychain,账户名 `com.openless.app.market_api_key` +- 客户端在 Header 加 `Authorization: Bearer ` +- 服务端校验 + 限频(每小时 60 次写、600 次读) + +**v2 升级路径**(暂不做): +- OAuth via GitHub / Google +- 上传时自动签名 ZIP,下载端校验签名 + +## 6. 缓存与版本检查 + +本地缓存目录:`/market_cache/` +- `listings.json` — 上次拉的 listings(带 ETag) +- `packs/{id}.zip` — 已下载的 ZIP(按需保留,30 天自动清理) + +版本升级提示: +- 启动时(带 dev-cap 24h 节流)调用 `/packs?ids=<已安装的 market_id...>` 拉对比 +- 本地包记录 `installed_market_id` 和 `installed_market_version` 字段,新建 `StylePack` 时填,本地从 ZIP 安装也填 +- 发现新版本 → 在 Style 页该包卡片角标显示 `New version: 2.3.0 →` + +## 7. 客户端 UI 入口(v1 不做,先留位) + +- Style 页头部加一个 tab:`本地 / 市场` +- 市场页:搜索栏 + tag 过滤 + 卡片列表 + 详情抽屉 +- 上传:编辑某个本地包时,"导出 ZIP" 按钮旁边出现 "上传到市场"(需要先在设置里填 API key) + +## 8. 安全 / 滥用对策 + +- ZIP 解压走 streaming,限制最大解压后大小 5 MB +- prompt 字段过滤明显的 prompt injection / 越狱(关键词预扫描 + 异步内容审核) +- 每用户每天上传上限 10 包,单包大小 ≤ 2 MB +- 上传后挂 24h 公开延迟(防恶意刷榜) + +## 9. 实装 TODO(按优先级) + +- [ ] 服务端选型(CF Workers + D1 + R2 vs Supabase vs 自托管 FastAPI) +- [ ] 服务端实装 + 部署环境(dev / staging / prod) +- [ ] 客户端 `types.rs` 加 DTO +- [ ] `commands.rs` 加 6 个 stub(**已完成**,返回 `not implemented yet`) +- [ ] `lib/ipc.ts` 加 wrapper(**已完成**) +- [ ] 实装 `market_download_pack`(先做单条路径打通:URL → 下载 → 走现有 import_style_pack_from_zip) +- [ ] 加凭据存储(Keychain 复用现有 `CredentialsVault`) +- [ ] UI:本地 / 市场 tab +- [ ] UI:搜索 + 卡片 +- [ ] UI:详情面板 +- [ ] UI:上传流程 +- [ ] 升级提醒 badge +- [ ] 缓存清理 + ETag + +## 10. 决策 / 风险记录 + +| 项 | 决策 | Why | +|---|---|---| +| ZIP 而非 JSON 上传 | 用 ZIP | 跟现有 import/export 同构;prompt 长文 + examples 用 ZIP 包压缩 | +| 服务端分配 ID | 是 | 防本地 ID 碰撞、用户重命名包不影响订阅 | +| 上传立刻可见 vs 审核 | 24h 公开延迟 | 防刷榜 + 给审核留空间 | +| API key vs OAuth | 先 API key | 简化 v1;登录态可 v2 升级 | +| 客户端缓存策略 | listings ETag + 已下载 ZIP 30 天 | 平衡流量和体验 | +| 国际化 / 跨境 | API 全英文 + 客户端 i18n | 服务端不存翻译,名称/描述支持任意 UTF-8 | From e0a48ce8010799b304d0ebfcc687d330296f3ce4 Mon Sep 17 00:00:00 2001 From: baiqing Date: Thu, 14 May 2026 15:00:15 +0800 Subject: [PATCH 3/3] chore(release): bump version to 1.3.2-1 (Beta) --- openless-all/app/package-lock.json | 4 ++-- openless-all/app/package.json | 2 +- openless-all/app/src-tauri/Cargo.lock | 2 +- openless-all/app/src-tauri/Cargo.toml | 2 +- openless-all/app/src-tauri/tauri.conf.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openless-all/app/package-lock.json b/openless-all/app/package-lock.json index 7b65fe09..b01ab830 100644 --- a/openless-all/app/package-lock.json +++ b/openless-all/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "openless-app", - "version": "1.3.1", + "version": "1.3.2-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openless-app", - "version": "1.3.1", + "version": "1.3.2-1", "dependencies": { "@tauri-apps/api": "^2.1.1", "@tauri-apps/plugin-autostart": "^2.5.1", diff --git a/openless-all/app/package.json b/openless-all/app/package.json index ecfda333..e74285cd 100644 --- a/openless-all/app/package.json +++ b/openless-all/app/package.json @@ -1,7 +1,7 @@ { "name": "openless-app", "private": true, - "version": "1.3.1", + "version": "1.3.2-1", "type": "module", "scripts": { "dev": "vite", diff --git a/openless-all/app/src-tauri/Cargo.lock b/openless-all/app/src-tauri/Cargo.lock index 9d517a8f..a5d55476 100644 --- a/openless-all/app/src-tauri/Cargo.lock +++ b/openless-all/app/src-tauri/Cargo.lock @@ -3751,7 +3751,7 @@ dependencies = [ [[package]] name = "openless" -version = "1.3.1" +version = "1.3.2-1" dependencies = [ "anyhow", "arboard", diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index 245fd29b..5346b46d 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openless" -version = "1.3.1" +version = "1.3.2-1" description = "OpenLess — local voice input that types where your cursor is" authors = ["OpenLess"] edition = "2021" diff --git a/openless-all/app/src-tauri/tauri.conf.json b/openless-all/app/src-tauri/tauri.conf.json index 2888892c..344cfe3a 100644 --- a/openless-all/app/src-tauri/tauri.conf.json +++ b/openless-all/app/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenLess", - "version": "1.3.1", + "version": "1.3.2-1", "identifier": "com.openless.app", "build": { "beforeDevCommand": "npm run dev",