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.

Files changed (85) hide show
  1. Supervertaler.py +47886 -0
  2. modules/__init__.py +10 -0
  3. modules/ai_actions.py +964 -0
  4. modules/ai_attachment_manager.py +343 -0
  5. modules/ai_file_viewer_dialog.py +210 -0
  6. modules/autofingers_engine.py +466 -0
  7. modules/cafetran_docx_handler.py +379 -0
  8. modules/config_manager.py +469 -0
  9. modules/database_manager.py +1878 -0
  10. modules/database_migrations.py +417 -0
  11. modules/dejavurtf_handler.py +779 -0
  12. modules/document_analyzer.py +427 -0
  13. modules/docx_handler.py +689 -0
  14. modules/encoding_repair.py +319 -0
  15. modules/encoding_repair_Qt.py +393 -0
  16. modules/encoding_repair_ui.py +481 -0
  17. modules/feature_manager.py +350 -0
  18. modules/figure_context_manager.py +340 -0
  19. modules/file_dialog_helper.py +148 -0
  20. modules/find_replace.py +164 -0
  21. modules/find_replace_qt.py +457 -0
  22. modules/glossary_manager.py +433 -0
  23. modules/image_extractor.py +188 -0
  24. modules/keyboard_shortcuts_widget.py +571 -0
  25. modules/llm_clients.py +1211 -0
  26. modules/llm_leaderboard.py +737 -0
  27. modules/llm_superbench_ui.py +1401 -0
  28. modules/local_llm_setup.py +1104 -0
  29. modules/model_update_dialog.py +381 -0
  30. modules/model_version_checker.py +373 -0
  31. modules/mqxliff_handler.py +638 -0
  32. modules/non_translatables_manager.py +743 -0
  33. modules/pdf_rescue_Qt.py +1822 -0
  34. modules/pdf_rescue_tkinter.py +909 -0
  35. modules/phrase_docx_handler.py +516 -0
  36. modules/project_home_panel.py +209 -0
  37. modules/prompt_assistant.py +357 -0
  38. modules/prompt_library.py +689 -0
  39. modules/prompt_library_migration.py +447 -0
  40. modules/quick_access_sidebar.py +282 -0
  41. modules/ribbon_widget.py +597 -0
  42. modules/sdlppx_handler.py +874 -0
  43. modules/setup_wizard.py +353 -0
  44. modules/shortcut_manager.py +932 -0
  45. modules/simple_segmenter.py +128 -0
  46. modules/spellcheck_manager.py +727 -0
  47. modules/statuses.py +207 -0
  48. modules/style_guide_manager.py +315 -0
  49. modules/superbench_ui.py +1319 -0
  50. modules/superbrowser.py +329 -0
  51. modules/supercleaner.py +600 -0
  52. modules/supercleaner_ui.py +444 -0
  53. modules/superdocs.py +19 -0
  54. modules/superdocs_viewer_qt.py +382 -0
  55. modules/superlookup.py +252 -0
  56. modules/tag_cleaner.py +260 -0
  57. modules/tag_manager.py +333 -0
  58. modules/term_extractor.py +270 -0
  59. modules/termbase_entry_editor.py +842 -0
  60. modules/termbase_import_export.py +488 -0
  61. modules/termbase_manager.py +1060 -0
  62. modules/termview_widget.py +1172 -0
  63. modules/theme_manager.py +499 -0
  64. modules/tm_editor_dialog.py +99 -0
  65. modules/tm_manager_qt.py +1280 -0
  66. modules/tm_metadata_manager.py +545 -0
  67. modules/tmx_editor.py +1461 -0
  68. modules/tmx_editor_qt.py +2784 -0
  69. modules/tmx_generator.py +284 -0
  70. modules/tracked_changes.py +900 -0
  71. modules/trados_docx_handler.py +430 -0
  72. modules/translation_memory.py +715 -0
  73. modules/translation_results_panel.py +2134 -0
  74. modules/translation_services.py +282 -0
  75. modules/unified_prompt_library.py +659 -0
  76. modules/unified_prompt_manager_qt.py +3951 -0
  77. modules/voice_commands.py +920 -0
  78. modules/voice_dictation.py +477 -0
  79. modules/voice_dictation_lite.py +249 -0
  80. supervertaler-1.9.153.dist-info/METADATA +896 -0
  81. supervertaler-1.9.153.dist-info/RECORD +85 -0
  82. supervertaler-1.9.153.dist-info/WHEEL +5 -0
  83. supervertaler-1.9.153.dist-info/entry_points.txt +2 -0
  84. supervertaler-1.9.153.dist-info/licenses/LICENSE +21 -0
  85. 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
+