supervertaler 1.9.153__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of supervertaler might be problematic. Click here for more details.
- Supervertaler.py +47886 -0
- modules/__init__.py +10 -0
- modules/ai_actions.py +964 -0
- modules/ai_attachment_manager.py +343 -0
- modules/ai_file_viewer_dialog.py +210 -0
- modules/autofingers_engine.py +466 -0
- modules/cafetran_docx_handler.py +379 -0
- modules/config_manager.py +469 -0
- modules/database_manager.py +1878 -0
- modules/database_migrations.py +417 -0
- modules/dejavurtf_handler.py +779 -0
- modules/document_analyzer.py +427 -0
- modules/docx_handler.py +689 -0
- modules/encoding_repair.py +319 -0
- modules/encoding_repair_Qt.py +393 -0
- modules/encoding_repair_ui.py +481 -0
- modules/feature_manager.py +350 -0
- modules/figure_context_manager.py +340 -0
- modules/file_dialog_helper.py +148 -0
- modules/find_replace.py +164 -0
- modules/find_replace_qt.py +457 -0
- modules/glossary_manager.py +433 -0
- modules/image_extractor.py +188 -0
- modules/keyboard_shortcuts_widget.py +571 -0
- modules/llm_clients.py +1211 -0
- modules/llm_leaderboard.py +737 -0
- modules/llm_superbench_ui.py +1401 -0
- modules/local_llm_setup.py +1104 -0
- modules/model_update_dialog.py +381 -0
- modules/model_version_checker.py +373 -0
- modules/mqxliff_handler.py +638 -0
- modules/non_translatables_manager.py +743 -0
- modules/pdf_rescue_Qt.py +1822 -0
- modules/pdf_rescue_tkinter.py +909 -0
- modules/phrase_docx_handler.py +516 -0
- modules/project_home_panel.py +209 -0
- modules/prompt_assistant.py +357 -0
- modules/prompt_library.py +689 -0
- modules/prompt_library_migration.py +447 -0
- modules/quick_access_sidebar.py +282 -0
- modules/ribbon_widget.py +597 -0
- modules/sdlppx_handler.py +874 -0
- modules/setup_wizard.py +353 -0
- modules/shortcut_manager.py +932 -0
- modules/simple_segmenter.py +128 -0
- modules/spellcheck_manager.py +727 -0
- modules/statuses.py +207 -0
- modules/style_guide_manager.py +315 -0
- modules/superbench_ui.py +1319 -0
- modules/superbrowser.py +329 -0
- modules/supercleaner.py +600 -0
- modules/supercleaner_ui.py +444 -0
- modules/superdocs.py +19 -0
- modules/superdocs_viewer_qt.py +382 -0
- modules/superlookup.py +252 -0
- modules/tag_cleaner.py +260 -0
- modules/tag_manager.py +333 -0
- modules/term_extractor.py +270 -0
- modules/termbase_entry_editor.py +842 -0
- modules/termbase_import_export.py +488 -0
- modules/termbase_manager.py +1060 -0
- modules/termview_widget.py +1172 -0
- modules/theme_manager.py +499 -0
- modules/tm_editor_dialog.py +99 -0
- modules/tm_manager_qt.py +1280 -0
- modules/tm_metadata_manager.py +545 -0
- modules/tmx_editor.py +1461 -0
- modules/tmx_editor_qt.py +2784 -0
- modules/tmx_generator.py +284 -0
- modules/tracked_changes.py +900 -0
- modules/trados_docx_handler.py +430 -0
- modules/translation_memory.py +715 -0
- modules/translation_results_panel.py +2134 -0
- modules/translation_services.py +282 -0
- modules/unified_prompt_library.py +659 -0
- modules/unified_prompt_manager_qt.py +3951 -0
- modules/voice_commands.py +920 -0
- modules/voice_dictation.py +477 -0
- modules/voice_dictation_lite.py +249 -0
- supervertaler-1.9.153.dist-info/METADATA +896 -0
- supervertaler-1.9.153.dist-info/RECORD +85 -0
- supervertaler-1.9.153.dist-info/WHEEL +5 -0
- supervertaler-1.9.153.dist-info/entry_points.txt +2 -0
- supervertaler-1.9.153.dist-info/licenses/LICENSE +21 -0
- supervertaler-1.9.153.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prompt Library Manager Module
|
|
3
|
+
|
|
4
|
+
Manages translation prompts with domain-specific expertise.
|
|
5
|
+
Supports two types:
|
|
6
|
+
- System Prompts: Define AI role and expertise
|
|
7
|
+
- Custom Instructions: Additional context and preferences
|
|
8
|
+
|
|
9
|
+
Supports both JSON (legacy) and Markdown (recommended) formats.
|
|
10
|
+
Markdown files use YAML frontmatter for metadata.
|
|
11
|
+
|
|
12
|
+
Extracted from main Supervertaler file for better modularity.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import json
|
|
17
|
+
import shutil
|
|
18
|
+
import re
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from tkinter import messagebox
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_user_data_path(subfolder):
|
|
24
|
+
"""
|
|
25
|
+
Get path to user_data folder, handling DEV_MODE.
|
|
26
|
+
This is imported from the main module's implementation.
|
|
27
|
+
"""
|
|
28
|
+
# Import from parent if needed, or accept as parameter
|
|
29
|
+
# For now, we'll make this module require the path to be passed in __init__
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PromptLibrary:
|
|
34
|
+
"""
|
|
35
|
+
Manages translation prompts with domain-specific expertise.
|
|
36
|
+
Supports two types:
|
|
37
|
+
- System Prompts: Define AI role and expertise
|
|
38
|
+
- Custom Instructions: Additional context and preferences
|
|
39
|
+
|
|
40
|
+
Loads prompt files from appropriate folders based on dev mode.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# File extensions
|
|
44
|
+
FILE_EXTENSION = ".svprompt" # New Supervertaler prompt format
|
|
45
|
+
LEGACY_EXTENSIONS = [".md", ".json"] # Backward compatibility
|
|
46
|
+
|
|
47
|
+
def __init__(self, system_prompts_dir=None, custom_instructions_dir=None, log_callback=None, domain_prompts_dir=None, project_prompts_dir=None):
|
|
48
|
+
"""
|
|
49
|
+
Initialize the Prompt Library.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
domain_prompts_dir: Path to domain prompts directory (Layer 2 - preferred parameter name)
|
|
53
|
+
project_prompts_dir: Path to project prompts directory (Layer 3 - preferred parameter name)
|
|
54
|
+
system_prompts_dir: (Deprecated) Alias for domain_prompts_dir for backward compatibility
|
|
55
|
+
custom_instructions_dir: (Deprecated) Alias for project_prompts_dir for backward compatibility
|
|
56
|
+
log_callback: Function to call for logging messages
|
|
57
|
+
"""
|
|
58
|
+
# Support both old and new parameter names
|
|
59
|
+
self.domain_prompts_dir = domain_prompts_dir or system_prompts_dir
|
|
60
|
+
self.project_prompts_dir = project_prompts_dir or custom_instructions_dir
|
|
61
|
+
# Keep old names for backward compatibility
|
|
62
|
+
self.system_prompts_dir = self.domain_prompts_dir
|
|
63
|
+
self.custom_instructions_dir = self.project_prompts_dir
|
|
64
|
+
|
|
65
|
+
self.log = log_callback if log_callback else print
|
|
66
|
+
|
|
67
|
+
# Create directories if they don't exist and paths are provided
|
|
68
|
+
if self.system_prompts_dir:
|
|
69
|
+
os.makedirs(self.system_prompts_dir, exist_ok=True)
|
|
70
|
+
if self.custom_instructions_dir:
|
|
71
|
+
os.makedirs(self.custom_instructions_dir, exist_ok=True)
|
|
72
|
+
|
|
73
|
+
# Available prompts: {filename: prompt_data}
|
|
74
|
+
self.prompts = {}
|
|
75
|
+
self.active_prompt = None # Currently selected prompt
|
|
76
|
+
self.active_prompt_name = None
|
|
77
|
+
|
|
78
|
+
def set_directories(self, domain_prompts_dir=None, project_prompts_dir=None, system_prompts_dir=None, custom_instructions_dir=None):
|
|
79
|
+
"""Set the directories after initialization
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
domain_prompts_dir: Path to domain prompts directory (Layer 2 - preferred)
|
|
83
|
+
project_prompts_dir: Path to project prompts directory (Layer 3 - preferred)
|
|
84
|
+
system_prompts_dir: (Deprecated) Alias for domain_prompts_dir
|
|
85
|
+
custom_instructions_dir: (Deprecated) Alias for project_prompts_dir
|
|
86
|
+
"""
|
|
87
|
+
# Support both old and new parameter names
|
|
88
|
+
self.domain_prompts_dir = domain_prompts_dir or system_prompts_dir
|
|
89
|
+
self.project_prompts_dir = project_prompts_dir or custom_instructions_dir
|
|
90
|
+
# Keep old names for backward compatibility
|
|
91
|
+
self.system_prompts_dir = self.domain_prompts_dir
|
|
92
|
+
self.custom_instructions_dir = self.project_prompts_dir
|
|
93
|
+
|
|
94
|
+
os.makedirs(self.domain_prompts_dir, exist_ok=True)
|
|
95
|
+
os.makedirs(self.project_prompts_dir, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
def load_all_prompts(self):
|
|
98
|
+
"""Load all prompts (system prompts and custom instructions) from appropriate directories"""
|
|
99
|
+
self.prompts = {}
|
|
100
|
+
|
|
101
|
+
# Load from the appropriate directories based on dev mode
|
|
102
|
+
sys_count = self._load_from_directory(self.system_prompts_dir, prompt_type="system_prompt")
|
|
103
|
+
inst_count = self._load_from_directory(self.custom_instructions_dir, prompt_type="custom_instruction")
|
|
104
|
+
|
|
105
|
+
total = sys_count + inst_count
|
|
106
|
+
self.log(f"✓ Loaded {total} prompts ({sys_count} system prompts, {inst_count} custom instructions)")
|
|
107
|
+
return total
|
|
108
|
+
|
|
109
|
+
def _load_from_directory(self, directory, prompt_type="system_prompt"):
|
|
110
|
+
"""Load prompts from a specific directory (.svprompt, .md and .json files)
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
directory: Path to directory
|
|
114
|
+
prompt_type: Either 'system_prompt' or 'custom_instruction'
|
|
115
|
+
"""
|
|
116
|
+
count = 0
|
|
117
|
+
|
|
118
|
+
if not directory or not os.path.exists(directory):
|
|
119
|
+
return count
|
|
120
|
+
|
|
121
|
+
for filename in os.listdir(directory):
|
|
122
|
+
# Skip format_examples folder
|
|
123
|
+
if filename == 'format_examples':
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
filepath = os.path.join(directory, filename)
|
|
127
|
+
|
|
128
|
+
# Skip directories
|
|
129
|
+
if os.path.isdir(filepath):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
prompt_data = None
|
|
133
|
+
|
|
134
|
+
# Try .svprompt first (new format - uses markdown internally)
|
|
135
|
+
if filename.endswith('.svprompt'):
|
|
136
|
+
prompt_data = self.parse_markdown(filepath)
|
|
137
|
+
|
|
138
|
+
# Then try Markdown (legacy)
|
|
139
|
+
elif filename.endswith('.md'):
|
|
140
|
+
prompt_data = self.parse_markdown(filepath)
|
|
141
|
+
|
|
142
|
+
# Fall back to JSON (legacy format)
|
|
143
|
+
elif filename.endswith('.json'):
|
|
144
|
+
try:
|
|
145
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
146
|
+
prompt_data = json.load(f)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
self.log(f"⚠ Failed to load JSON {filename}: {e}")
|
|
149
|
+
continue
|
|
150
|
+
else:
|
|
151
|
+
# Skip unsupported file types
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
# Process loaded data
|
|
155
|
+
if prompt_data:
|
|
156
|
+
try:
|
|
157
|
+
# Add metadata
|
|
158
|
+
prompt_data['_filename'] = filename
|
|
159
|
+
prompt_data['_filepath'] = filepath
|
|
160
|
+
prompt_data['_type'] = prompt_type
|
|
161
|
+
|
|
162
|
+
# Add task_type with backward compatibility
|
|
163
|
+
if 'task_type' not in prompt_data:
|
|
164
|
+
prompt_data['task_type'] = self._infer_task_type(prompt_data.get('name', ''))
|
|
165
|
+
|
|
166
|
+
# Validate required fields
|
|
167
|
+
if 'name' not in prompt_data or 'translate_prompt' not in prompt_data:
|
|
168
|
+
self.log(f"⚠ Skipping {filename}: missing required fields")
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
self.prompts[filename] = prompt_data
|
|
172
|
+
count += 1
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
self.log(f"⚠ Error processing {filename}: {e}")
|
|
176
|
+
|
|
177
|
+
return count
|
|
178
|
+
|
|
179
|
+
def _infer_task_type(self, title):
|
|
180
|
+
"""Infer task type from prompt title for backward compatibility
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
title: Prompt title/name
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
str: Inferred task type
|
|
187
|
+
"""
|
|
188
|
+
title_lower = title.lower()
|
|
189
|
+
|
|
190
|
+
if 'localization' in title_lower or 'localisation' in title_lower:
|
|
191
|
+
return 'Localization'
|
|
192
|
+
elif 'proofread' in title_lower:
|
|
193
|
+
return 'Proofreading'
|
|
194
|
+
elif 'qa' in title_lower or 'quality' in title_lower:
|
|
195
|
+
return 'QA'
|
|
196
|
+
elif 'copyedit' in title_lower or 'copy-edit' in title_lower:
|
|
197
|
+
return 'Copyediting'
|
|
198
|
+
elif 'post-edit' in title_lower or 'postedit' in title_lower:
|
|
199
|
+
return 'Post-editing'
|
|
200
|
+
elif 'transcreation' in title_lower:
|
|
201
|
+
return 'Transcreation'
|
|
202
|
+
else:
|
|
203
|
+
return 'Translation' # Default
|
|
204
|
+
|
|
205
|
+
def parse_markdown(self, filepath):
|
|
206
|
+
"""Parse Markdown file with YAML frontmatter into prompt data.
|
|
207
|
+
|
|
208
|
+
Format:
|
|
209
|
+
---
|
|
210
|
+
name: "Prompt Name"
|
|
211
|
+
description: "Description"
|
|
212
|
+
domain: "Domain"
|
|
213
|
+
version: "1.0"
|
|
214
|
+
task_type: "Translation"
|
|
215
|
+
created: "2025-10-19"
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
# Content
|
|
219
|
+
Actual prompt content here...
|
|
220
|
+
"""
|
|
221
|
+
try:
|
|
222
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
223
|
+
content = f.read()
|
|
224
|
+
|
|
225
|
+
# Split frontmatter from content
|
|
226
|
+
if content.startswith('---'):
|
|
227
|
+
# Remove opening ---
|
|
228
|
+
content = content[3:].lstrip('\n')
|
|
229
|
+
|
|
230
|
+
# Find closing ---
|
|
231
|
+
if '---' in content:
|
|
232
|
+
frontmatter_str, prompt_content = content.split('---', 1)
|
|
233
|
+
prompt_content = prompt_content.lstrip('\n')
|
|
234
|
+
else:
|
|
235
|
+
self.log(f"[WARNING] Invalid Markdown format in {filepath}: closing --- not found")
|
|
236
|
+
return None
|
|
237
|
+
else:
|
|
238
|
+
self.log(f"[WARNING] Invalid Markdown format in {filepath}: no opening ---")
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
# Parse YAML frontmatter
|
|
242
|
+
prompt_data = self._parse_yaml(frontmatter_str)
|
|
243
|
+
|
|
244
|
+
# Store content as translate_prompt (main prompt content)
|
|
245
|
+
prompt_data['translate_prompt'] = prompt_content.strip()
|
|
246
|
+
|
|
247
|
+
# Proofread prompt defaults to translate prompt if not specified
|
|
248
|
+
if 'proofread_prompt' not in prompt_data:
|
|
249
|
+
prompt_data['proofread_prompt'] = prompt_content.strip()
|
|
250
|
+
|
|
251
|
+
# Validate required fields
|
|
252
|
+
if 'name' not in prompt_data or 'translate_prompt' not in prompt_data:
|
|
253
|
+
self.log(f"[WARNING] Missing required fields in {filepath}")
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
return prompt_data
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
self.log(f"⚠ Failed to parse Markdown {filepath}: {e}")
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
def _parse_yaml(self, yaml_str):
|
|
263
|
+
"""Simple YAML parser for frontmatter (handles basic key: value pairs).
|
|
264
|
+
|
|
265
|
+
Supports:
|
|
266
|
+
- Simple strings: key: "value" or key: value
|
|
267
|
+
- Numbers: key: 1.0
|
|
268
|
+
- Arrays: key: [item1, item2]
|
|
269
|
+
"""
|
|
270
|
+
data = {}
|
|
271
|
+
|
|
272
|
+
for line in yaml_str.strip().split('\n'):
|
|
273
|
+
line = line.strip()
|
|
274
|
+
if not line or line.startswith('#'):
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
if ':' not in line:
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
key, value = line.split(':', 1)
|
|
281
|
+
key = key.strip()
|
|
282
|
+
value = value.strip()
|
|
283
|
+
|
|
284
|
+
# Remove quotes
|
|
285
|
+
if value.startswith('"') and value.endswith('"'):
|
|
286
|
+
value = value[1:-1]
|
|
287
|
+
elif value.startswith("'") and value.endswith("'"):
|
|
288
|
+
value = value[1:-1]
|
|
289
|
+
|
|
290
|
+
# Handle numbers
|
|
291
|
+
if value.replace('.', '', 1).isdigit():
|
|
292
|
+
try:
|
|
293
|
+
value = float(value) if '.' in value else int(value)
|
|
294
|
+
except:
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
data[key] = value
|
|
298
|
+
|
|
299
|
+
return data
|
|
300
|
+
|
|
301
|
+
def markdown_to_dict(self, filepath):
|
|
302
|
+
"""Convert Markdown file to dictionary (alias for parse_markdown)"""
|
|
303
|
+
return self.parse_markdown(filepath)
|
|
304
|
+
|
|
305
|
+
def dict_to_markdown(self, prompt_data, filepath):
|
|
306
|
+
"""Save prompt data as Markdown file with YAML frontmatter.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
prompt_data: Dictionary with prompt info
|
|
310
|
+
filepath: Where to save the .md file
|
|
311
|
+
"""
|
|
312
|
+
try:
|
|
313
|
+
# Prepare frontmatter fields
|
|
314
|
+
frontmatter = []
|
|
315
|
+
frontmatter.append('---')
|
|
316
|
+
|
|
317
|
+
# Fields to include in frontmatter (in order)
|
|
318
|
+
frontmatter_fields = ['name', 'description', 'domain', 'version', 'task_type', 'created', 'modified']
|
|
319
|
+
|
|
320
|
+
for field in frontmatter_fields:
|
|
321
|
+
if field in prompt_data:
|
|
322
|
+
value = prompt_data[field]
|
|
323
|
+
# Quote strings, don't quote numbers
|
|
324
|
+
if isinstance(value, str):
|
|
325
|
+
frontmatter.append(f'{field}: "{value}"')
|
|
326
|
+
else:
|
|
327
|
+
frontmatter.append(f'{field}: {value}')
|
|
328
|
+
|
|
329
|
+
frontmatter.append('---')
|
|
330
|
+
|
|
331
|
+
# Get content (use translate_prompt if proofread_prompt is the same)
|
|
332
|
+
content = prompt_data.get('translate_prompt', '')
|
|
333
|
+
|
|
334
|
+
# Build final content
|
|
335
|
+
markdown_content = '\n'.join(frontmatter) + '\n\n' + content.strip()
|
|
336
|
+
|
|
337
|
+
# Write file
|
|
338
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
339
|
+
f.write(markdown_content)
|
|
340
|
+
|
|
341
|
+
return True
|
|
342
|
+
|
|
343
|
+
except Exception as e:
|
|
344
|
+
self.log(f"⚠ Failed to save Markdown {filepath}: {e}")
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
def get_prompt_list(self):
|
|
348
|
+
"""Get list of available prompts with metadata"""
|
|
349
|
+
prompt_list = []
|
|
350
|
+
for filename, data in sorted(self.prompts.items()):
|
|
351
|
+
prompt_list.append({
|
|
352
|
+
'filename': filename,
|
|
353
|
+
'name': data.get('name', 'Unnamed'),
|
|
354
|
+
'description': data.get('description', ''),
|
|
355
|
+
'domain': data.get('domain', 'General'),
|
|
356
|
+
'version': data.get('version', '1.0'),
|
|
357
|
+
'task_type': data.get('task_type', 'Translation'), # NEW: Include task type
|
|
358
|
+
'filepath': data.get('_filepath', ''),
|
|
359
|
+
'_type': data.get('_type', 'system_prompt') # Include type for filtering
|
|
360
|
+
})
|
|
361
|
+
return prompt_list
|
|
362
|
+
|
|
363
|
+
def get_prompt(self, filename):
|
|
364
|
+
"""Get full prompt data by filename"""
|
|
365
|
+
return self.prompts.get(filename)
|
|
366
|
+
|
|
367
|
+
def set_active_prompt(self, filename):
|
|
368
|
+
"""Set the active custom prompt"""
|
|
369
|
+
if filename not in self.prompts:
|
|
370
|
+
self.log(f"✗ Prompt not found: {filename}")
|
|
371
|
+
return False
|
|
372
|
+
|
|
373
|
+
self.active_prompt = self.prompts[filename]
|
|
374
|
+
self.active_prompt_name = self.active_prompt.get('name', filename)
|
|
375
|
+
self.log(f"✓ Active prompt: {self.active_prompt_name}")
|
|
376
|
+
return True
|
|
377
|
+
|
|
378
|
+
def clear_active_prompt(self):
|
|
379
|
+
"""Clear active prompt (use default)"""
|
|
380
|
+
self.active_prompt = None
|
|
381
|
+
self.active_prompt_name = None
|
|
382
|
+
self.log("✓ Using default translation prompt")
|
|
383
|
+
|
|
384
|
+
def get_translate_prompt(self):
|
|
385
|
+
"""Get the translate_prompt from active prompt, or None if using default"""
|
|
386
|
+
if self.active_prompt:
|
|
387
|
+
return self.active_prompt.get('translate_prompt')
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
def get_proofread_prompt(self):
|
|
391
|
+
"""Get the proofread_prompt from active prompt, or None if using default"""
|
|
392
|
+
if self.active_prompt:
|
|
393
|
+
return self.active_prompt.get('proofread_prompt')
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
def search_prompts(self, search_text):
|
|
397
|
+
"""Search prompts by name, description, or domain"""
|
|
398
|
+
if not search_text:
|
|
399
|
+
return self.get_prompt_list()
|
|
400
|
+
|
|
401
|
+
search_lower = search_text.lower()
|
|
402
|
+
results = []
|
|
403
|
+
|
|
404
|
+
for filename, data in sorted(self.prompts.items()):
|
|
405
|
+
name = data.get('name', '').lower()
|
|
406
|
+
desc = data.get('description', '').lower()
|
|
407
|
+
domain = data.get('domain', '').lower()
|
|
408
|
+
|
|
409
|
+
if search_lower in name or search_lower in desc or search_lower in domain:
|
|
410
|
+
results.append({
|
|
411
|
+
'filename': filename,
|
|
412
|
+
'name': data.get('name', 'Unnamed'),
|
|
413
|
+
'description': data.get('description', ''),
|
|
414
|
+
'domain': data.get('domain', 'General'),
|
|
415
|
+
'version': data.get('version', '1.0'),
|
|
416
|
+
'filepath': data.get('_filepath', '')
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
return results
|
|
420
|
+
|
|
421
|
+
def create_new_prompt(self, name, description, domain, translate_prompt, proofread_prompt="",
|
|
422
|
+
version="1.0", task_type="Translation", prompt_type="system_prompt"):
|
|
423
|
+
"""Create a new prompt and save as .svprompt
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
prompt_type: Either 'system_prompt' or 'custom_instruction'
|
|
427
|
+
task_type: Type of translation task
|
|
428
|
+
"""
|
|
429
|
+
# Create filename from name with .svprompt extension
|
|
430
|
+
filename = name.replace(' ', '_').replace('/', '_') + '.svprompt'
|
|
431
|
+
|
|
432
|
+
# Choose directory based on type
|
|
433
|
+
if prompt_type == "custom_instruction":
|
|
434
|
+
directory = self.custom_instructions_dir
|
|
435
|
+
else: # system_prompt
|
|
436
|
+
directory = self.system_prompts_dir
|
|
437
|
+
|
|
438
|
+
filepath = os.path.join(directory, filename)
|
|
439
|
+
|
|
440
|
+
# Create prompt data
|
|
441
|
+
prompt_data = {
|
|
442
|
+
'name': name,
|
|
443
|
+
'description': description,
|
|
444
|
+
'domain': domain,
|
|
445
|
+
'version': version,
|
|
446
|
+
'task_type': task_type,
|
|
447
|
+
'created': datetime.now().strftime('%Y-%m-%d'),
|
|
448
|
+
'translate_prompt': translate_prompt,
|
|
449
|
+
'proofread_prompt': proofread_prompt
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
# Save to file as Markdown
|
|
453
|
+
try:
|
|
454
|
+
success = self.dict_to_markdown(prompt_data, filepath)
|
|
455
|
+
if not success:
|
|
456
|
+
return False
|
|
457
|
+
|
|
458
|
+
# Add to loaded prompts
|
|
459
|
+
prompt_data['_filename'] = filename
|
|
460
|
+
prompt_data['_filepath'] = filepath
|
|
461
|
+
prompt_data['_type'] = prompt_type
|
|
462
|
+
self.prompts[filename] = prompt_data
|
|
463
|
+
|
|
464
|
+
self.log(f"✓ Created new prompt: {name}")
|
|
465
|
+
return True
|
|
466
|
+
|
|
467
|
+
except Exception as e:
|
|
468
|
+
self.log(f"✗ Failed to create prompt: {e}")
|
|
469
|
+
messagebox.showerror("Save Error", f"Failed to save prompt:\n{e}")
|
|
470
|
+
return False
|
|
471
|
+
|
|
472
|
+
def update_prompt(self, filename, name, description, domain, translate_prompt,
|
|
473
|
+
proofread_prompt="", version="1.0", task_type="Translation"):
|
|
474
|
+
"""Update an existing prompt"""
|
|
475
|
+
if filename not in self.prompts:
|
|
476
|
+
self.log(f"✗ Prompt not found: {filename}")
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
filepath = self.prompts[filename]['_filepath']
|
|
480
|
+
|
|
481
|
+
# Update prompt data
|
|
482
|
+
prompt_data = {
|
|
483
|
+
'name': name,
|
|
484
|
+
'description': description,
|
|
485
|
+
'domain': domain,
|
|
486
|
+
'version': version,
|
|
487
|
+
'task_type': task_type,
|
|
488
|
+
'created': self.prompts[filename].get('created', datetime.now().strftime('%Y-%m-%d')),
|
|
489
|
+
'modified': datetime.now().strftime('%Y-%m-%d'),
|
|
490
|
+
'translate_prompt': translate_prompt,
|
|
491
|
+
'proofread_prompt': proofread_prompt
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
# Save to file as Markdown
|
|
495
|
+
try:
|
|
496
|
+
success = self.dict_to_markdown(prompt_data, filepath)
|
|
497
|
+
if not success:
|
|
498
|
+
return False
|
|
499
|
+
|
|
500
|
+
# Update loaded prompts
|
|
501
|
+
prompt_data['_filename'] = filename
|
|
502
|
+
prompt_data['_filepath'] = filepath
|
|
503
|
+
prompt_data['_type'] = self.prompts[filename].get('_type', 'system_prompt')
|
|
504
|
+
self.prompts[filename] = prompt_data
|
|
505
|
+
|
|
506
|
+
self.log(f"✓ Updated prompt: {name}")
|
|
507
|
+
return True
|
|
508
|
+
|
|
509
|
+
except Exception as e:
|
|
510
|
+
self.log(f"✗ Failed to update prompt: {e}")
|
|
511
|
+
messagebox.showerror("Save Error", f"Failed to update prompt:\n{e}")
|
|
512
|
+
return False
|
|
513
|
+
|
|
514
|
+
def delete_prompt(self, filename):
|
|
515
|
+
"""Delete a custom prompt"""
|
|
516
|
+
if filename not in self.prompts:
|
|
517
|
+
return False
|
|
518
|
+
|
|
519
|
+
filepath = self.prompts[filename]['_filepath']
|
|
520
|
+
prompt_name = self.prompts[filename].get('name', filename)
|
|
521
|
+
|
|
522
|
+
try:
|
|
523
|
+
os.remove(filepath)
|
|
524
|
+
del self.prompts[filename]
|
|
525
|
+
|
|
526
|
+
# Clear active if this was active
|
|
527
|
+
if self.active_prompt and self.active_prompt.get('_filename') == filename:
|
|
528
|
+
self.clear_active_prompt()
|
|
529
|
+
|
|
530
|
+
self.log(f"✓ Deleted prompt: {prompt_name}")
|
|
531
|
+
return True
|
|
532
|
+
|
|
533
|
+
except Exception as e:
|
|
534
|
+
self.log(f"✗ Failed to delete prompt: {e}")
|
|
535
|
+
messagebox.showerror("Delete Error", f"Failed to delete prompt:\n{e}")
|
|
536
|
+
return False
|
|
537
|
+
|
|
538
|
+
def export_prompt(self, filename, export_path):
|
|
539
|
+
"""Export a prompt to a specific location"""
|
|
540
|
+
if filename not in self.prompts:
|
|
541
|
+
return False
|
|
542
|
+
|
|
543
|
+
try:
|
|
544
|
+
source = self.prompts[filename]['_filepath']
|
|
545
|
+
shutil.copy2(source, export_path)
|
|
546
|
+
self.log(f"✓ Exported prompt to: {export_path}")
|
|
547
|
+
return True
|
|
548
|
+
except Exception as e:
|
|
549
|
+
self.log(f"✗ Export failed: {e}")
|
|
550
|
+
return False
|
|
551
|
+
|
|
552
|
+
def import_prompt(self, import_path, prompt_type="system_prompt"):
|
|
553
|
+
"""Import a prompt from an external file
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
import_path: Path to JSON file to import
|
|
557
|
+
prompt_type: Either 'system_prompt' or 'custom_instruction'
|
|
558
|
+
"""
|
|
559
|
+
try:
|
|
560
|
+
with open(import_path, 'r', encoding='utf-8') as f:
|
|
561
|
+
prompt_data = json.load(f)
|
|
562
|
+
|
|
563
|
+
# Validate
|
|
564
|
+
if 'name' not in prompt_data or 'translate_prompt' not in prompt_data:
|
|
565
|
+
messagebox.showerror("Invalid Prompt", "Missing required fields: name, translate_prompt")
|
|
566
|
+
return False
|
|
567
|
+
|
|
568
|
+
# Copy to appropriate directory based on type
|
|
569
|
+
filename = os.path.basename(import_path)
|
|
570
|
+
if prompt_type == "custom_instruction":
|
|
571
|
+
directory = self.custom_instructions_dir
|
|
572
|
+
else: # system_prompt
|
|
573
|
+
directory = self.system_prompts_dir
|
|
574
|
+
dest_path = os.path.join(directory, filename)
|
|
575
|
+
|
|
576
|
+
shutil.copy2(import_path, dest_path)
|
|
577
|
+
|
|
578
|
+
# Add metadata and load
|
|
579
|
+
prompt_data['_filename'] = filename
|
|
580
|
+
prompt_data['_filepath'] = dest_path
|
|
581
|
+
prompt_data['_type'] = prompt_type
|
|
582
|
+
self.prompts[filename] = prompt_data
|
|
583
|
+
|
|
584
|
+
self.log(f"✓ Imported prompt: {prompt_data['name']}")
|
|
585
|
+
return True
|
|
586
|
+
|
|
587
|
+
except Exception as e:
|
|
588
|
+
self.log(f"✗ Import failed: {e}")
|
|
589
|
+
messagebox.showerror("Import Error", f"Failed to import prompt:\n{e}")
|
|
590
|
+
return False
|
|
591
|
+
|
|
592
|
+
def convert_json_to_markdown(self, directory, prompt_type="system_prompt"):
|
|
593
|
+
"""Convert all JSON files in directory to Markdown format.
|
|
594
|
+
|
|
595
|
+
Args:
|
|
596
|
+
directory: Path to directory containing .json files
|
|
597
|
+
prompt_type: Either 'system_prompt' or 'custom_instruction'
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
tuple: (converted_count, failed_count)
|
|
601
|
+
"""
|
|
602
|
+
converted = 0
|
|
603
|
+
failed = 0
|
|
604
|
+
|
|
605
|
+
if not directory or not os.path.exists(directory):
|
|
606
|
+
self.log(f"⚠ Directory not found: {directory}")
|
|
607
|
+
return (0, 0)
|
|
608
|
+
|
|
609
|
+
for filename in os.listdir(directory):
|
|
610
|
+
# Skip non-JSON files
|
|
611
|
+
if not filename.endswith('.json'):
|
|
612
|
+
continue
|
|
613
|
+
|
|
614
|
+
filepath = os.path.join(directory, filename)
|
|
615
|
+
|
|
616
|
+
# Skip directories
|
|
617
|
+
if os.path.isdir(filepath):
|
|
618
|
+
continue
|
|
619
|
+
|
|
620
|
+
try:
|
|
621
|
+
# Load JSON
|
|
622
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
623
|
+
prompt_data = json.load(f)
|
|
624
|
+
|
|
625
|
+
# Validate
|
|
626
|
+
if 'name' not in prompt_data or 'translate_prompt' not in prompt_data:
|
|
627
|
+
self.log(f"⚠ Skipping {filename}: missing required fields")
|
|
628
|
+
failed += 1
|
|
629
|
+
continue
|
|
630
|
+
|
|
631
|
+
# Create new filename with .md extension
|
|
632
|
+
name_without_ext = os.path.splitext(filename)[0]
|
|
633
|
+
md_filename = f"{name_without_ext}.md"
|
|
634
|
+
md_filepath = os.path.join(directory, md_filename)
|
|
635
|
+
|
|
636
|
+
# Save as Markdown
|
|
637
|
+
if self.dict_to_markdown(prompt_data, md_filepath):
|
|
638
|
+
self.log(f"✓ Converted {filename} → {md_filename}")
|
|
639
|
+
|
|
640
|
+
# Delete original JSON file
|
|
641
|
+
try:
|
|
642
|
+
os.remove(filepath)
|
|
643
|
+
self.log(f" Removed original: {filename}")
|
|
644
|
+
except Exception as e:
|
|
645
|
+
self.log(f"⚠ Could not delete {filename}: {e}")
|
|
646
|
+
|
|
647
|
+
converted += 1
|
|
648
|
+
else:
|
|
649
|
+
failed += 1
|
|
650
|
+
|
|
651
|
+
except Exception as e:
|
|
652
|
+
self.log(f"✗ Failed to convert {filename}: {e}")
|
|
653
|
+
failed += 1
|
|
654
|
+
|
|
655
|
+
return (converted, failed)
|
|
656
|
+
|
|
657
|
+
def convert_all_prompts_to_markdown(self):
|
|
658
|
+
"""Convert all JSON prompts to Markdown format in both directories.
|
|
659
|
+
|
|
660
|
+
Returns:
|
|
661
|
+
dict: {"system_prompts": (converted, failed), "custom_instructions": (converted, failed)}
|
|
662
|
+
"""
|
|
663
|
+
results = {}
|
|
664
|
+
|
|
665
|
+
self.log("🔄 Converting prompts to Markdown format...")
|
|
666
|
+
|
|
667
|
+
# Convert system prompts
|
|
668
|
+
if self.system_prompts_dir:
|
|
669
|
+
self.log(f" Converting System Prompts from {self.system_prompts_dir}")
|
|
670
|
+
results['system_prompts'] = self.convert_json_to_markdown(
|
|
671
|
+
self.system_prompts_dir,
|
|
672
|
+
prompt_type="system_prompt"
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
# Convert custom instructions
|
|
676
|
+
if self.custom_instructions_dir:
|
|
677
|
+
self.log(f" Converting Custom Instructions from {self.custom_instructions_dir}")
|
|
678
|
+
results['custom_instructions'] = self.convert_json_to_markdown(
|
|
679
|
+
self.custom_instructions_dir,
|
|
680
|
+
prompt_type="custom_instruction"
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
# Summary
|
|
684
|
+
total_converted = sum(r[0] for r in results.values())
|
|
685
|
+
total_failed = sum(r[1] for r in results.values())
|
|
686
|
+
self.log(f"✓ Conversion complete: {total_converted} prompts converted, {total_failed} failed")
|
|
687
|
+
|
|
688
|
+
return results
|
|
689
|
+
|