diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index 3fa34131..93bf34e8 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -880,6 +880,12 @@ impl Coordinator { pack_name: style_pack.name.clone(), pack_prompt: style_pack.prompt.clone(), pack_prompt_chars: style_pack.prompt.chars().count(), + context_premise: single_turn.context_premise.clone(), + context_premise_chars: single_turn.context_premise.chars().count(), + hotword_block: single_turn.hotword_block.clone(), + hotword_block_chars: single_turn.hotword_block.chars().count(), + history_instruction: multi_turn.history_instruction.clone(), + history_instruction_chars: multi_turn.history_instruction.chars().count(), single_turn_prompt: single_turn.effective_system_prompt.clone(), single_turn_prompt_chars: single_turn.effective_system_prompt.chars().count(), multi_turn_prompt: multi_turn.effective_system_prompt.clone(), diff --git a/openless-all/app/src-tauri/src/polish.rs b/openless-all/app/src-tauri/src/polish.rs index 8c546067..6db6312a 100644 --- a/openless-all/app/src-tauri/src/polish.rs +++ b/openless-all/app/src-tauri/src/polish.rs @@ -272,6 +272,9 @@ pub struct OpenAICompatibleLLMProvider { #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct PolishSystemPromptAssembly { + pub context_premise: String, + pub hotword_block: String, + pub history_instruction: String, pub effective_system_prompt: String, pub includes_context_premise: bool, pub includes_hotword_block: bool, @@ -1667,15 +1670,25 @@ pub(crate) fn assemble_polish_system_prompt( front_app, has_prior_turns, ); - let includes_hotword_block = !hotwords.iter().all(|value| value.trim().is_empty()); - let includes_context_premise = context_premise( + let context_premise = context_premise( working_languages, chinese_script_preference, output_language_preference, front_app, ) - .is_some(); + .unwrap_or_default(); + let hotword_block = compose_hotword_block_preview(hotwords); + let history_instruction = if has_prior_turns { + prompts::polish_context_instruction().to_string() + } else { + String::new() + }; + let includes_hotword_block = !hotword_block.is_empty(); + let includes_context_premise = !context_premise.is_empty(); PolishSystemPromptAssembly { + context_premise, + hotword_block, + history_instruction, effective_system_prompt, includes_context_premise, includes_hotword_block, @@ -1743,6 +1756,26 @@ fn compose_system_prompt(style_system_prompt: &str, hotwords: &[String]) -> Stri ) } +fn compose_hotword_block_preview(hotwords: &[String]) -> String { + let cleaned: Vec = hotwords + .iter() + .map(|h| h.trim().to_string()) + .filter(|h| !h.is_empty()) + .collect(); + if cleaned.is_empty() { + return String::new(); + } + let bullets = cleaned + .iter() + .map(|h| format!("- {}", h)) + .collect::>() + .join("\n"); + format!( + "热词(用户希望以下写法在输出中保持准确;当转写中出现这些词的同音 / 近形词识别时,优先按上述写法输出,不做无关词的机械替换):\n{}", + bullets + ) +} + fn extract_assistant_content(body: &str) -> Result { let json: Value = serde_json::from_str(body) .map_err(|e| LLMError::ParseError(format!("not valid JSON: {}", e)))?; diff --git a/openless-all/app/src-tauri/src/types.rs b/openless-all/app/src-tauri/src/types.rs index d8fbf447..d07f1f02 100644 --- a/openless-all/app/src-tauri/src/types.rs +++ b/openless-all/app/src-tauri/src/types.rs @@ -276,6 +276,12 @@ pub struct StylePackRuntimeDiagnostics { pub pack_name: String, pub pack_prompt: String, pub pack_prompt_chars: usize, + pub context_premise: String, + pub context_premise_chars: usize, + pub hotword_block: String, + pub hotword_block_chars: usize, + pub history_instruction: String, + pub history_instruction_chars: usize, pub single_turn_prompt: String, pub single_turn_prompt_chars: usize, pub multi_turn_prompt: String, diff --git a/openless-all/app/src/lib/ipc.ts b/openless-all/app/src/lib/ipc.ts index b3e3d775..a3fd0c09 100644 --- a/openless-all/app/src/lib/ipc.ts +++ b/openless-all/app/src/lib/ipc.ts @@ -292,15 +292,14 @@ function cloneMockStylePacks(): StylePack[] { function composeMockStylePackRuntimeDiagnostics(stylePack: StylePack): StylePackRuntimeDiagnostics { const trimmedPrompt = stylePack.prompt.trimEnd(); - const contextLines = mockSettings.workingLanguages.length - ? [`# Context`, `Working languages: ${mockSettings.workingLanguages.join(', ')}`] - : []; + const contextPremise = mockSettings.workingLanguages.length + ? ['# Context', `Working languages: ${mockSettings.workingLanguages.join(', ')}`].join('\n') + : ''; const hotwordLines = [`GitHub`, `OpenLess`]; - const hotwordBlock = - hotwordLines.length > 0 - ? ['', 'Hotwords (keep the spelling below when they appear in the transcript):', ...hotwordLines.map(word => `- ${word}`)] - : []; - const singleTurnPrompt = [...contextLines, trimmedPrompt, ...hotwordBlock].filter(Boolean).join('\n\n'); + const hotwordBlock = hotwordLines.length > 0 + ? ['Hotwords (keep the spelling below when they appear in the transcript):', ...hotwordLines.map(word => `- ${word}`)].join('\n') + : ''; + const singleTurnPrompt = [contextPremise, trimmedPrompt, hotwordBlock].filter(Boolean).join('\n\n'); const historyInstruction = 'When prior turns exist, do not repeat previous assistant outputs. Only polish the current transcript.'; const multiTurnPrompt = `${singleTurnPrompt}\n\n${historyInstruction}`; return { @@ -308,6 +307,12 @@ function composeMockStylePackRuntimeDiagnostics(stylePack: StylePack): StylePack packName: stylePack.name, packPrompt: stylePack.prompt, packPromptChars: stylePack.prompt.length, + contextPremise, + contextPremiseChars: contextPremise.length, + hotwordBlock, + hotwordBlockChars: hotwordBlock.length, + historyInstruction, + historyInstructionChars: historyInstruction.length, singleTurnPrompt, singleTurnPromptChars: singleTurnPrompt.length, multiTurnPrompt, @@ -315,7 +320,7 @@ function composeMockStylePackRuntimeDiagnostics(stylePack: StylePack): StylePack workingLanguages: [...mockSettings.workingLanguages], hotwords: [...hotwordLines], contextWindowMinutes: mockSettings.polishContextWindowMinutes, - includesContextPremise: contextLines.length > 0, + includesContextPremise: Boolean(contextPremise), includesHotwordBlock: hotwordLines.length > 0, includesHistoryInstruction: true, previewOmitsFrontApp: true, diff --git a/openless-all/app/src/lib/types.ts b/openless-all/app/src/lib/types.ts index 115839b8..441e3646 100644 --- a/openless-all/app/src/lib/types.ts +++ b/openless-all/app/src/lib/types.ts @@ -181,6 +181,12 @@ export interface StylePackRuntimeDiagnostics { packName: string; packPrompt: string; packPromptChars: number; + contextPremise: string; + contextPremiseChars: number; + hotwordBlock: string; + hotwordBlockChars: number; + historyInstruction: string; + historyInstructionChars: number; singleTurnPrompt: string; singleTurnPromptChars: number; multiTurnPrompt: string; diff --git a/openless-all/app/src/pages/Style.tsx b/openless-all/app/src/pages/Style.tsx index ea1328c0..476e2f96 100644 --- a/openless-all/app/src/pages/Style.tsx +++ b/openless-all/app/src/pages/Style.tsx @@ -77,45 +77,45 @@ export function Style() { kicker: 'STYLE PACKS', title: isEnglish ? 'Style Packs' : '风格包', desc: isEnglish - ? 'Manage output styles as pluggable Style Packs. Built-in and imported packs share the same runtime prompt, ZIP exchange format, and repolish path.' - : '把输出风格作为可插拔的 Style Pack 来管理。内置风格和导入风格共用同一套运行时 prompt、ZIP 交换格式和 repolish 链路。', + ? 'Manage local style packs.' + : '管理本地风格包。', loadFailed: (message: string) => (isEnglish ? `Failed to load style packs: ${message}` : `加载风格包失败:${message}`), importZip: isEnglish ? 'Import ZIP' : '导入 ZIP', exportZip: isEnglish ? 'Export ZIP' : '导出 ZIP', exportShort: isEnglish ? 'Export' : '导出', builtin: isEnglish ? 'Built-in' : '内置', imported: isEnglish ? 'Imported' : '导入', - active: isEnglish ? 'Active' : '当前启用', - enabled: isEnglish ? 'Enabled' : '已启用', - disabled: isEnglish ? 'Disabled' : '已停用', - activate: isEnglish ? 'Activate' : '启用', - enable: isEnglish ? 'Enable' : '启用', - disable: isEnglish ? 'Disable' : '停用', + 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' : '未保存', listTitle: isEnglish ? 'Local Packs' : '本地风格包', listDesc: isEnglish - ? 'Browse and manage packs like a local storefront. Keep this page focused on cards, then open a dedicated editor only when you need to change prompt details.' - : '像本地商店一样浏览和管理风格包。主页面只保留卡片浏览,真正修改 prompt 和示例时再打开独立编辑面板。', + ? 'Browse and switch packs.' + : '浏览和切换风格包。', listCount: (count: number) => (isEnglish ? `${count} packs` : `${count} 个风格包`), - save: isEnglish ? 'Save Pack' : '保存风格包', - revert: isEnglish ? 'Discard Changes' : '放弃修改', + 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 ? `Activated "${name}".` : `已启用“${name}”`), - activateFailed: (message: string) => (isEnglish ? `Failed to activate style pack: ${message}` : `启用风格包失败:${message}`), - enableSuccess: (name: string) => (isEnglish ? `Enabled "${name}".` : `已启用“${name}”`), - disableSuccess: (name: string) => (isEnglish ? `Disabled "${name}".` : `已停用“${name}”`), - toggleFailed: (message: string) => (isEnglish ? `Failed to change pack status: ${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}`), exportFailed: (message: string) => (isEnglish ? `Failed to export ZIP: ${message}` : `导出 ZIP 失败:${message}`), - resetBuiltin: isEnglish ? 'Reset Built-in Pack' : '重置内置风格', + resetBuiltin: isEnglish ? 'Reset' : '重置', resetSuccess: (name: string) => (isEnglish ? `Reset "${name}".` : `已重置“${name}”`), resetFailed: (message: string) => (isEnglish ? `Failed to reset pack: ${message}` : `重置风格包失败:${message}`), - deleteImported: isEnglish ? 'Delete Imported Pack' : '删除导入风格', + deleteImported: isEnglish ? 'Delete' : '删除', deleteConfirm: (name: string) => (isEnglish ? `Delete "${name}"? This cannot be undone.` : `确定删除“${name}”吗?删除后无法恢复。`), @@ -125,18 +125,17 @@ export function Style() { summaryBuiltinHint: isEnglish ? 'Default product semantics with one-click reset.' : '跟随产品默认语义,可一键重置到官方基线。', summaryImported: isEnglish ? 'Imported Packs' : '导入风格', summaryImportedHint: isEnglish ? 'Installed from ZIP and fully portable.' : '来自 ZIP 包,可启用、编辑、导出和删除。', - summaryEnabled: isEnglish ? 'Rotation Ready' : '当前可轮换', + summaryEnabled: isEnglish ? 'In Rotation' : '已加入轮换', summaryCurrent: (name: string) => (isEnglish ? `Current: ${name}` : `当前启用:${name}`), summaryCurrentEmpty: isEnglish ? 'No pack selected yet' : '还没有选中风格包', - summaryFocused: (name: string) => (isEnglish ? `Focused: ${name}` : `当前浏览:${name}`), - editorTitle: isEnglish ? 'Style Pack Editor' : '风格包编辑面板', + editorTitle: isEnglish ? 'Edit Pack' : '编辑风格', editorDesc: isEnglish - ? 'Edit the full runtime prompt, examples, tags, export, reset, and metadata here without crowding the main store view.' - : '在这里集中编辑完整运行时 prompt、示例、标签、导入导出和重置操作,不再占满主列表空间。', + ? 'Edit this pack.' + : '编辑当前风格包。', metaTitle: isEnglish ? 'Installation Info' : '安装信息', metaSource: isEnglish ? 'Source' : '来源', metaBaseMode: isEnglish ? 'Base Mode' : '基础模式', - metaStatus: isEnglish ? 'Status' : '状态', + metaStatus: isEnglish ? 'Rotation' : '轮换状态', metaUpdatedAt: isEnglish ? 'Updated' : '更新时间', fieldName: isEnglish ? 'Name' : '名称', fieldAuthor: isEnglish ? 'Author' : '作者', @@ -148,53 +147,39 @@ export function Style() { fieldModel: isEnglish ? 'Recommended Model (Metadata)' : '推荐模型(仅元数据)', fieldModelPlaceholder: isEnglish ? 'Optional, e.g. gpt-4.1 / deepseek-v3' : '可选,例如 gpt-4.1 / deepseek-v3', fieldModelHint: isEnglish - ? 'Advisory metadata only. It is exported in the pack manifest for humans, but it never switches the actual LLM provider or model.' - : '这是给人看的建议元数据,会写入风格包 manifest,方便分享时说明适配模型;它不会自动切换实际使用的 LLM provider 或 model。', + ? 'Metadata only. Does not switch model.' + : '仅作说明,不会切换实际模型。', fieldCompatibility: isEnglish ? 'Compatible App Version' : '兼容版本', fieldCompatibilityPlaceholder: isEnglish ? 'Optional, e.g. >=1.3.0' : '可选,例如 >=1.3.0', - fullPromptTitle: isEnglish ? 'Style Pack Prompt Body' : 'Style Pack Prompt 主体', + fullPromptTitle: isEnglish ? 'System Prompt' : 'System Prompt', fullPromptHint: isEnglish - ? 'This textarea owns the pack’s full style definition. OpenLess no longer swaps in a hidden built-in mode prompt and then appends your text.' - : '这里编辑的是这套风格包自己拥有的完整风格定义。OpenLess 不再走“隐藏内置 prompt + 末尾追加文本”的旧语义。', - runtimeTitle: isEnglish ? 'Runtime Assembly' : '运行时组装说明', + ? 'The prompt owned by this pack.' + : '这就是这套风格包自己的 Prompt。', + runtimeTitle: isEnglish ? 'OpenLess Runtime Directives' : 'OpenLess 运行时附加指令', runtimeDesc: isEnglish - ? 'At runtime the pack prompt stays as the style body. OpenLess may only prepend context premises and append helper guardrails for the current session.' - : '运行时仍以这段 pack prompt 作为风格主体;OpenLess 只会按当前会话额外注入上下文前提与辅助约束,不会再替换成另一套隐藏风格 prompt。', - runtimePackStageTitle: isEnglish ? 'Pack-owned prompt body' : 'Pack 自有 prompt 主体', - runtimePackStageDesc: isEnglish - ? 'This editable text is the style definition that both polish and repolish share.' - : '你在上面编辑的文本就是这套风格包拥有的风格主体,polish 和 repolish 共用它。', - runtimeContextStageTitle: isEnglish ? 'Context premise' : '上下文前提', - runtimeContextStageDesc: isEnglish - ? 'Prepended when working-language or output-language preferences are available. This is session context, not another style prompt.' - : '当工作语言、字形偏好或输出语言偏好存在时,会前置注入这一段。这是会话上下文,不是另一套风格 prompt。', - runtimeHotwordStageTitle: isEnglish ? 'Hotword spelling hints' : '热词拼写提示', - runtimeHotwordStageDesc: isEnglish - ? 'Appended when enabled vocabulary entries exist so names and terms keep the expected spelling.' - : '当用户词库里存在已启用热词时,会追加这一段,帮助专名和术语保持预期写法。', - runtimeHistoryStageTitle: isEnglish ? 'Multi-turn history guardrail' : '多轮历史保护段', - runtimeHistoryStageDesc: isEnglish - ? 'Only added for live polish when prior turns are present. Repolish does not use this segment.' - : '只有实时 polish 在存在 prior turns 时才会追加这段;History 里的 repolish 不会带它。', - runtimePreviewTitle: isEnglish ? 'Current Runtime Preview' : '当前运行时预览', - runtimePreviewDesc: isEnglish - ? 'Preview generated from the current app preferences and enabled hotwords. It intentionally omits the frontmost-app label so the editor window itself does not pollute the result.' - : '这个预览按当前应用设置和已启用热词生成。为了避免把编辑器窗口本身误当成真实输入目标,预览会刻意省略前台 app 名称。', - runtimePreviewSingle: isEnglish ? 'Single-turn polish / repolish' : '单轮 polish / repolish', - runtimePreviewMulti: isEnglish ? 'Live polish with prior turns' : '带上下文的实时 polish', + ? 'Read-only runtime helpers.' + : '只读的运行时辅助项。', + runtimeDirectiveContextTitle: isEnglish ? 'Context premise' : '上下文前提', + runtimeDirectiveContextDesc: isEnglish ? 'From language and app context' : '来自语言与应用上下文', + runtimeDirectiveContextEmpty: isEnglish ? 'Not added in the current preview.' : '当前不会附加', + runtimeDirectiveHotwordTitle: isEnglish ? 'Hotword block' : '热词提示段', + runtimeDirectiveHotwordDesc: isEnglish ? 'From enabled hotwords' : '来自已启用热词', + runtimeDirectiveHotwordEmpty: isEnglish ? 'Not added in the current preview.' : '当前不会附加', + runtimeDirectiveHistoryTitle: isEnglish ? 'Multi-turn history guardrail' : '多轮历史保护段', + runtimeDirectiveHistoryDesc: isEnglish ? 'Only for live multi-turn polish' : '仅用于实时多轮 polish', + runtimeDirectiveHistoryEmpty: isEnglish ? 'Only added when prior turns exist.' : '只有存在 prior turns 时才会附加', + runtimeDirectiveActive: isEnglish ? 'Active' : '当前生效', + runtimeDirectiveInactive: isEnglish ? 'Inactive' : '当前未生效', runtimePreviewFailed: (message: string) => (isEnglish ? `Failed to build runtime preview: ${message}` : `生成运行时预览失败:${message}`), - runtimePreviewOmittedFrontApp: isEnglish ? 'Front-app label omitted in preview.' : '预览已省略前台 app 标签。', - runtimePromptChars: (count: number) => (isEnglish ? `${count} chars` : `${count} 字符`), - runtimeHotwordCount: (count: number) => (isEnglish ? `${count} hotwords` : `${count} 个热词`), - runtimeContextWindow: (minutes: number) => (isEnglish ? `${minutes} min context window` : `${minutes} 分钟上下文窗口`), + runtimePreviewOmittedFrontApp: isEnglish ? 'Preview omits the front-app label.' : '预览已省略前台 app 标签。', examplesTitle: isEnglish ? 'Effect Examples' : '效果示例', examplesDesc: isEnglish - ? 'Present input/output pairs like a product detail page. They will be exported into examples.json.' - : '像商品详情页一样展示输入和输出。导出 ZIP 时会一起写入 examples.json。', + ? 'Exported with the pack.' + : '会随风格包一起导出。', addExample: isEnglish ? 'Add Example' : '新增示例', examplesEmpty: isEnglish - ? 'This pack has no examples yet. Add at least one input/output pair before sharing it.' - : '这个风格包还没有示例。建议至少补一组输入/输出,方便导出后被别人理解和复用。', + ? 'No examples yet.' + : '还没有示例。', exampleTitlePlaceholder: (index: number) => (isEnglish ? `Example ${index} title` : `示例 ${index} 标题`), exampleInput: isEnglish ? 'Input' : '输入', exampleOutput: isEnglish ? 'Output' : '输出', @@ -579,7 +564,6 @@ export function Style() {
{copy.listDesc}
- {selectedPack && {copy.summaryFocused(selectedPack.name)}} {copy.listCount(packs.length)}
@@ -592,6 +576,8 @@ export function Style() {
-
+
-
+
{pack.name}
{pack.kind === 'builtin' ? copy.builtin : copy.imported} - {pack.active && {copy.active}} - {!pack.enabled && {copy.disabled}} +
+ {pack.active ? ( + {copy.active} + ) : !pack.enabled ? ( + {copy.disabled} + ) : null} +
{pack.description} @@ -655,24 +649,14 @@ export function Style() {
-
+
{t(`style.modes.${pack.baseMode}.name`)} - {copy.examplesCount(pack.examples.length)} - {pack.tags.slice(0, 2).map(tag => ( + {pack.tags.slice(0, 1).map(tag => ( {tag} ))}
-
- - {copy.metaSource}: {pack.kind === 'builtin' ? copy.builtin : copy.imported} - - - {copy.metaStatus}: {pack.enabled ? copy.enabled : copy.disabled} - -
- -
+
void handleExportZip()} disabled={busy === 'exporting'}> {copy.exportZip} + void handleToggleEnabled(draft)} + > + {draft.enabled ? copy.disable : copy.enable} +
{copy.fullPromptTitle} - {copy.runtimePromptChars(draft.prompt.length)} + {isEnglish ? `${draft.prompt.length} chars` : `${draft.prompt.length} 字符`}
{copy.fullPromptHint}