supervertaler 1.9.163__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.
- Supervertaler.py +48473 -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 +1911 -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 +351 -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 +1176 -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.163.dist-info/METADATA +906 -0
- supervertaler-1.9.163.dist-info/RECORD +85 -0
- supervertaler-1.9.163.dist-info/WHEEL +5 -0
- supervertaler-1.9.163.dist-info/entry_points.txt +2 -0
- supervertaler-1.9.163.dist-info/licenses/LICENSE +21 -0
- supervertaler-1.9.163.dist-info/top_level.txt +2 -0
modules/ai_actions.py
ADDED
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI Actions Module
|
|
3
|
+
|
|
4
|
+
Provides structured action interface for AI Assistant to interact with Supervertaler
|
|
5
|
+
resources (Prompt Library, Translation Memories, Termbases).
|
|
6
|
+
|
|
7
|
+
Phase 2 of AI Assistant Enhancement Plan:
|
|
8
|
+
- Parses ACTION markers from AI responses
|
|
9
|
+
- Executes actions on prompt library
|
|
10
|
+
- Returns structured results for display
|
|
11
|
+
|
|
12
|
+
Action Format:
|
|
13
|
+
ACTION:function_name
|
|
14
|
+
PARAMS:{"param1": "value1", "param2": "value2"}
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
ACTION:list_prompts
|
|
18
|
+
PARAMS:{"folder": "Domain Expertise"}
|
|
19
|
+
|
|
20
|
+
ACTION:create_prompt
|
|
21
|
+
PARAMS:{"name": "Medical Translator", "content": "...", "folder": "Domain Expertise"}
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import re
|
|
26
|
+
from datetime import datetime
|
|
27
|
+
from typing import Dict, List, Optional, Tuple, Any
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AIActionSystem:
|
|
31
|
+
"""
|
|
32
|
+
Handles parsing and execution of AI actions on Supervertaler resources.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, prompt_library, parent_app=None, log_callback=None):
|
|
36
|
+
"""
|
|
37
|
+
Initialize AI Action System.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
prompt_library: UnifiedPromptLibrary instance
|
|
41
|
+
parent_app: Reference to main application (for segment access)
|
|
42
|
+
log_callback: Function to call for logging messages
|
|
43
|
+
"""
|
|
44
|
+
self.prompt_library = prompt_library
|
|
45
|
+
self.parent_app = parent_app
|
|
46
|
+
self.log = log_callback if log_callback else print
|
|
47
|
+
|
|
48
|
+
# Map action names to handler methods
|
|
49
|
+
self.action_handlers = {
|
|
50
|
+
'list_prompts': self._action_list_prompts,
|
|
51
|
+
'get_prompt': self._action_get_prompt,
|
|
52
|
+
'create_prompt': self._action_create_prompt,
|
|
53
|
+
'update_prompt': self._action_update_prompt,
|
|
54
|
+
'delete_prompt': self._action_delete_prompt,
|
|
55
|
+
'search_prompts': self._action_search_prompts,
|
|
56
|
+
'create_folder': self._action_create_folder,
|
|
57
|
+
'toggle_favorite': self._action_toggle_favorite,
|
|
58
|
+
'toggle_quick_run': self._action_toggle_quick_run,
|
|
59
|
+
'get_favorites': self._action_get_favorites,
|
|
60
|
+
'get_quick_run': self._action_get_quick_run,
|
|
61
|
+
'get_folder_structure': self._action_get_folder_structure,
|
|
62
|
+
'get_segment_count': self._action_get_segment_count,
|
|
63
|
+
'get_segment_info': self._action_get_segment_info,
|
|
64
|
+
'activate_prompt': self._action_activate_prompt,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def parse_and_execute(self, ai_response: str) -> Tuple[str, List[Dict]]:
|
|
68
|
+
"""
|
|
69
|
+
Parse AI response for ACTION markers and execute actions.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
ai_response: Full response text from AI
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Tuple of (cleaned_response, list of action results)
|
|
76
|
+
cleaned_response: AI response with ACTION blocks removed
|
|
77
|
+
action_results: List of {action, params, success, result/error}
|
|
78
|
+
"""
|
|
79
|
+
action_results = []
|
|
80
|
+
|
|
81
|
+
# Strip markdown code fences if present (Claude often wraps in ```yaml or ```)
|
|
82
|
+
# Handle both block-level and inline code fences
|
|
83
|
+
self.log(f"[DEBUG] Original response length: {len(ai_response)} chars")
|
|
84
|
+
self.log(f"[DEBUG] First 200 chars: {ai_response[:200]}")
|
|
85
|
+
|
|
86
|
+
# Remove opening fence: ```yaml, ```json, or just ``` (at start or after newline)
|
|
87
|
+
ai_response = re.sub(r'(^|\n)```(?:yaml|json|)?\s*\n?', r'\1', ai_response)
|
|
88
|
+
# Remove closing fence: ``` (at end or before newline)
|
|
89
|
+
ai_response = re.sub(r'\n?```\s*($|\n)', r'\1', ai_response)
|
|
90
|
+
# Remove any remaining standalone backticks or language markers
|
|
91
|
+
ai_response = re.sub(r'^`(?:yaml|json|)\s*$', '', ai_response, flags=re.MULTILINE)
|
|
92
|
+
|
|
93
|
+
self.log(f"[DEBUG] After fence stripping length: {len(ai_response)} chars")
|
|
94
|
+
self.log(f"[DEBUG] After fence stripping first 200 chars: {ai_response[:200]}")
|
|
95
|
+
|
|
96
|
+
cleaned_response = ai_response
|
|
97
|
+
|
|
98
|
+
# Find all ACTION blocks
|
|
99
|
+
# Pattern: ACTION:name PARAMS:... (with optional newline between)
|
|
100
|
+
# Handles both "ACTION:name\nPARAMS:" and "ACTION:name PARAMS:"
|
|
101
|
+
action_pattern = r'ACTION:(\w+)\s+PARAMS:\s*'
|
|
102
|
+
matches = list(re.finditer(action_pattern, ai_response))
|
|
103
|
+
|
|
104
|
+
self.log(f"[DEBUG] Found {len(matches)} ACTION blocks")
|
|
105
|
+
|
|
106
|
+
# Process each ACTION block
|
|
107
|
+
for i, match in enumerate(matches):
|
|
108
|
+
action_name = match.group(1)
|
|
109
|
+
start_pos = match.end()
|
|
110
|
+
|
|
111
|
+
# Find where this action's params end (next ACTION: or end of string)
|
|
112
|
+
if i + 1 < len(matches):
|
|
113
|
+
end_pos = matches[i + 1].start()
|
|
114
|
+
else:
|
|
115
|
+
end_pos = len(ai_response)
|
|
116
|
+
|
|
117
|
+
params_str = ai_response[start_pos:end_pos].strip()
|
|
118
|
+
|
|
119
|
+
# Extract just the JSON part (up to first newline followed by non-JSON char)
|
|
120
|
+
# Look for the closing brace of the JSON object
|
|
121
|
+
brace_count = 0
|
|
122
|
+
json_end = 0
|
|
123
|
+
in_string = False
|
|
124
|
+
escape_next = False
|
|
125
|
+
|
|
126
|
+
for idx, char in enumerate(params_str):
|
|
127
|
+
if escape_next:
|
|
128
|
+
escape_next = False
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
if char == '\\':
|
|
132
|
+
escape_next = True
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
if char == '"' and not escape_next:
|
|
136
|
+
in_string = not in_string
|
|
137
|
+
|
|
138
|
+
if not in_string:
|
|
139
|
+
if char == '{':
|
|
140
|
+
brace_count += 1
|
|
141
|
+
elif char == '}':
|
|
142
|
+
brace_count -= 1
|
|
143
|
+
if brace_count == 0:
|
|
144
|
+
json_end = idx + 1
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
if json_end > 0:
|
|
148
|
+
params_str = params_str[:json_end]
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
# Parse parameters
|
|
152
|
+
params = json.loads(params_str)
|
|
153
|
+
|
|
154
|
+
# Execute action
|
|
155
|
+
result = self.execute_action(action_name, params)
|
|
156
|
+
action_results.append(result)
|
|
157
|
+
|
|
158
|
+
# Reload prompt library immediately if prompt was created/updated/deleted
|
|
159
|
+
# This ensures subsequent actions (like activate_prompt) see the new prompt
|
|
160
|
+
if result['success'] and action_name in ['create_prompt', 'update_prompt', 'delete_prompt']:
|
|
161
|
+
self.prompt_library.load_all_prompts()
|
|
162
|
+
self.log(f"✓ Reloaded prompt library after {action_name}")
|
|
163
|
+
|
|
164
|
+
# Remove ACTION block from response
|
|
165
|
+
# Include the full ACTION block: from start of match to end of JSON
|
|
166
|
+
full_block = ai_response[match.start():start_pos + json_end]
|
|
167
|
+
cleaned_response = cleaned_response.replace(full_block, '')
|
|
168
|
+
|
|
169
|
+
except json.JSONDecodeError as e:
|
|
170
|
+
self.log(f"✗ Failed to parse action parameters: {e}")
|
|
171
|
+
action_results.append({
|
|
172
|
+
'action': action_name,
|
|
173
|
+
'params': params_str,
|
|
174
|
+
'success': False,
|
|
175
|
+
'error': f"Invalid JSON parameters: {e}"
|
|
176
|
+
})
|
|
177
|
+
except Exception as e:
|
|
178
|
+
self.log(f"✗ Action execution error: {e}")
|
|
179
|
+
action_results.append({
|
|
180
|
+
'action': action_name,
|
|
181
|
+
'params': params_str,
|
|
182
|
+
'success': False,
|
|
183
|
+
'error': str(e)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
return cleaned_response.strip(), action_results
|
|
187
|
+
|
|
188
|
+
def execute_action(self, action_name: str, params: Dict) -> Dict:
|
|
189
|
+
"""
|
|
190
|
+
Execute a single action with given parameters.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
action_name: Name of the action
|
|
194
|
+
params: Dictionary of parameters
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Dictionary with action result: {action, params, success, result/error}
|
|
198
|
+
"""
|
|
199
|
+
if action_name not in self.action_handlers:
|
|
200
|
+
return {
|
|
201
|
+
'action': action_name,
|
|
202
|
+
'params': params,
|
|
203
|
+
'success': False,
|
|
204
|
+
'error': f"Unknown action: {action_name}"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
handler = self.action_handlers[action_name]
|
|
209
|
+
result = handler(params)
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
'action': action_name,
|
|
213
|
+
'params': params,
|
|
214
|
+
'success': True,
|
|
215
|
+
'result': result
|
|
216
|
+
}
|
|
217
|
+
except Exception as e:
|
|
218
|
+
self.log(f"✗ Error executing {action_name}: {e}")
|
|
219
|
+
return {
|
|
220
|
+
'action': action_name,
|
|
221
|
+
'params': params,
|
|
222
|
+
'success': False,
|
|
223
|
+
'error': str(e)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# ========================================================================
|
|
227
|
+
# ACTION HANDLERS
|
|
228
|
+
# ========================================================================
|
|
229
|
+
|
|
230
|
+
def _action_list_prompts(self, params: Dict) -> Dict:
|
|
231
|
+
"""
|
|
232
|
+
List prompts in library, optionally filtered by folder.
|
|
233
|
+
|
|
234
|
+
Params:
|
|
235
|
+
folder (optional): Folder path to filter by
|
|
236
|
+
include_content (optional): Include full content (default: False)
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
{count: int, prompts: [{name, path, folder, favorite, quick_run, ...}]}
|
|
240
|
+
"""
|
|
241
|
+
folder_filter = params.get('folder')
|
|
242
|
+
include_content = params.get('include_content', False)
|
|
243
|
+
|
|
244
|
+
prompts_list = []
|
|
245
|
+
for path, prompt_data in self.prompt_library.prompts.items():
|
|
246
|
+
# Apply folder filter if specified
|
|
247
|
+
if folder_filter:
|
|
248
|
+
if prompt_data.get('_folder') != folder_filter:
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
# Build prompt info
|
|
252
|
+
prompt_info = {
|
|
253
|
+
'name': prompt_data.get('name', path),
|
|
254
|
+
'path': path,
|
|
255
|
+
'folder': prompt_data.get('_folder', ''),
|
|
256
|
+
'description': prompt_data.get('description', ''),
|
|
257
|
+
'favorite': prompt_data.get('favorite', False),
|
|
258
|
+
'quick_run': prompt_data.get('quick_run', False),
|
|
259
|
+
'tags': prompt_data.get('tags', [])
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if include_content:
|
|
263
|
+
prompt_info['content'] = prompt_data.get('content', '')
|
|
264
|
+
|
|
265
|
+
prompts_list.append(prompt_info)
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
'count': len(prompts_list),
|
|
269
|
+
'prompts': prompts_list
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
def _action_get_prompt(self, params: Dict) -> Dict:
|
|
273
|
+
"""
|
|
274
|
+
Get full details of a specific prompt.
|
|
275
|
+
|
|
276
|
+
Params:
|
|
277
|
+
path (required): Relative path to prompt
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
{name, path, folder, content, metadata...}
|
|
281
|
+
"""
|
|
282
|
+
path = params.get('path')
|
|
283
|
+
if not path:
|
|
284
|
+
raise ValueError("Missing required parameter: path")
|
|
285
|
+
|
|
286
|
+
if path not in self.prompt_library.prompts:
|
|
287
|
+
raise ValueError(f"Prompt not found: {path}")
|
|
288
|
+
|
|
289
|
+
prompt_data = self.prompt_library.prompts[path]
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
'name': prompt_data.get('name', path),
|
|
293
|
+
'path': path,
|
|
294
|
+
'folder': prompt_data.get('_folder', ''),
|
|
295
|
+
'description': prompt_data.get('description', ''),
|
|
296
|
+
'content': prompt_data.get('content', ''),
|
|
297
|
+
'favorite': prompt_data.get('favorite', False),
|
|
298
|
+
'quick_run': prompt_data.get('quick_run', False),
|
|
299
|
+
'tags': prompt_data.get('tags', []),
|
|
300
|
+
'domain': prompt_data.get('domain', ''),
|
|
301
|
+
'task_type': prompt_data.get('task_type', ''),
|
|
302
|
+
'version': prompt_data.get('version', '1.0'),
|
|
303
|
+
'created': prompt_data.get('created', ''),
|
|
304
|
+
'modified': prompt_data.get('modified', '')
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
def _action_create_prompt(self, params: Dict) -> Dict:
|
|
308
|
+
"""
|
|
309
|
+
Create a new prompt in the library.
|
|
310
|
+
|
|
311
|
+
Params:
|
|
312
|
+
name (required): Prompt name
|
|
313
|
+
content (required): Prompt content
|
|
314
|
+
folder (optional): Folder to create in (default: root)
|
|
315
|
+
description (optional): Prompt description
|
|
316
|
+
tags (optional): List of tags
|
|
317
|
+
domain (optional): Domain category
|
|
318
|
+
task_type (optional): Task type
|
|
319
|
+
activate (optional): If True, activate as primary after creating
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
{success: bool, path: str, message: str}
|
|
323
|
+
"""
|
|
324
|
+
name = params.get('name')
|
|
325
|
+
content = params.get('content')
|
|
326
|
+
|
|
327
|
+
if not name or not content:
|
|
328
|
+
raise ValueError("Missing required parameters: name and content")
|
|
329
|
+
|
|
330
|
+
# Build relative path
|
|
331
|
+
folder = params.get('folder', '')
|
|
332
|
+
# Sanitize filename - remove/replace invalid Windows filename characters
|
|
333
|
+
# Invalid chars: < > : " / \ | ? * and also → (arrow) which AI likes to use
|
|
334
|
+
filename = name
|
|
335
|
+
invalid_chars = ['/', '\\', '<', '>', ':', '"', '|', '?', '*', '→', '←', '↔']
|
|
336
|
+
for char in invalid_chars:
|
|
337
|
+
filename = filename.replace(char, '-')
|
|
338
|
+
# Also clean up multiple dashes and trim
|
|
339
|
+
import re
|
|
340
|
+
filename = re.sub(r'-+', '-', filename).strip('-')
|
|
341
|
+
filename = filename + '.svprompt'
|
|
342
|
+
relative_path = f"{folder}/{filename}" if folder else filename
|
|
343
|
+
|
|
344
|
+
# Build prompt data
|
|
345
|
+
prompt_data = {
|
|
346
|
+
'name': name,
|
|
347
|
+
'content': content,
|
|
348
|
+
'description': params.get('description', ''),
|
|
349
|
+
'domain': params.get('domain', ''),
|
|
350
|
+
'task_type': params.get('task_type', 'Translation'),
|
|
351
|
+
'version': '1.0',
|
|
352
|
+
'favorite': False,
|
|
353
|
+
'quick_run': False,
|
|
354
|
+
'folder': folder,
|
|
355
|
+
'tags': params.get('tags', []),
|
|
356
|
+
'created': datetime.now().strftime('%Y-%m-%d'),
|
|
357
|
+
'modified': datetime.now().strftime('%Y-%m-%d')
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
# Save prompt
|
|
361
|
+
success = self.prompt_library.save_prompt(relative_path, prompt_data)
|
|
362
|
+
|
|
363
|
+
if success:
|
|
364
|
+
message = f"Created prompt: {name}"
|
|
365
|
+
|
|
366
|
+
# Auto-activate if requested
|
|
367
|
+
if params.get('activate', False):
|
|
368
|
+
self.prompt_library.set_primary_prompt(relative_path)
|
|
369
|
+
message += f" and activated as primary"
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
'success': True,
|
|
373
|
+
'path': relative_path,
|
|
374
|
+
'message': message
|
|
375
|
+
}
|
|
376
|
+
else:
|
|
377
|
+
raise Exception("Failed to save prompt")
|
|
378
|
+
|
|
379
|
+
def _action_update_prompt(self, params: Dict) -> Dict:
|
|
380
|
+
"""
|
|
381
|
+
Update an existing prompt.
|
|
382
|
+
|
|
383
|
+
Params:
|
|
384
|
+
path (required): Relative path to prompt
|
|
385
|
+
content (optional): New content
|
|
386
|
+
name (optional): New name
|
|
387
|
+
description (optional): New description
|
|
388
|
+
tags (optional): New tags
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
{success: bool, path: str, message: str}
|
|
392
|
+
"""
|
|
393
|
+
path = params.get('path')
|
|
394
|
+
if not path:
|
|
395
|
+
raise ValueError("Missing required parameter: path")
|
|
396
|
+
|
|
397
|
+
if path not in self.prompt_library.prompts:
|
|
398
|
+
raise ValueError(f"Prompt not found: {path}")
|
|
399
|
+
|
|
400
|
+
# Get existing prompt data
|
|
401
|
+
prompt_data = self.prompt_library.prompts[path].copy()
|
|
402
|
+
|
|
403
|
+
# Update fields
|
|
404
|
+
if 'name' in params:
|
|
405
|
+
prompt_data['name'] = params['name']
|
|
406
|
+
if 'content' in params:
|
|
407
|
+
prompt_data['content'] = params['content']
|
|
408
|
+
if 'description' in params:
|
|
409
|
+
prompt_data['description'] = params['description']
|
|
410
|
+
if 'tags' in params:
|
|
411
|
+
prompt_data['tags'] = params['tags']
|
|
412
|
+
|
|
413
|
+
prompt_data['modified'] = datetime.now().strftime('%Y-%m-%d')
|
|
414
|
+
|
|
415
|
+
# Save updated prompt
|
|
416
|
+
success = self.prompt_library.save_prompt(path, prompt_data)
|
|
417
|
+
|
|
418
|
+
if success:
|
|
419
|
+
return {
|
|
420
|
+
'success': True,
|
|
421
|
+
'path': path,
|
|
422
|
+
'message': f"Updated prompt: {prompt_data['name']}"
|
|
423
|
+
}
|
|
424
|
+
else:
|
|
425
|
+
raise Exception("Failed to update prompt")
|
|
426
|
+
|
|
427
|
+
def _action_delete_prompt(self, params: Dict) -> Dict:
|
|
428
|
+
"""
|
|
429
|
+
Delete a prompt from the library.
|
|
430
|
+
|
|
431
|
+
Params:
|
|
432
|
+
path (required): Relative path to prompt
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
{success: bool, path: str, message: str}
|
|
436
|
+
"""
|
|
437
|
+
path = params.get('path')
|
|
438
|
+
if not path:
|
|
439
|
+
raise ValueError("Missing required parameter: path")
|
|
440
|
+
|
|
441
|
+
if path not in self.prompt_library.prompts:
|
|
442
|
+
raise ValueError(f"Prompt not found: {path}")
|
|
443
|
+
|
|
444
|
+
name = self.prompt_library.prompts[path].get('name', path)
|
|
445
|
+
success = self.prompt_library.delete_prompt(path)
|
|
446
|
+
|
|
447
|
+
if success:
|
|
448
|
+
return {
|
|
449
|
+
'success': True,
|
|
450
|
+
'path': path,
|
|
451
|
+
'message': f"Deleted prompt: {name}"
|
|
452
|
+
}
|
|
453
|
+
else:
|
|
454
|
+
raise Exception("Failed to delete prompt")
|
|
455
|
+
|
|
456
|
+
def _action_search_prompts(self, params: Dict) -> Dict:
|
|
457
|
+
"""
|
|
458
|
+
Search prompts by name, content, or tags.
|
|
459
|
+
|
|
460
|
+
Params:
|
|
461
|
+
query (required): Search query
|
|
462
|
+
search_in (optional): 'name', 'content', 'tags', or 'all' (default: all)
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
{count: int, results: [{name, path, folder, match_type, ...}]}
|
|
466
|
+
"""
|
|
467
|
+
query = params.get('query', '').lower()
|
|
468
|
+
search_in = params.get('search_in', 'all')
|
|
469
|
+
|
|
470
|
+
if not query:
|
|
471
|
+
raise ValueError("Missing required parameter: query")
|
|
472
|
+
|
|
473
|
+
results = []
|
|
474
|
+
for path, prompt_data in self.prompt_library.prompts.items():
|
|
475
|
+
match_type = None
|
|
476
|
+
|
|
477
|
+
# Search in name
|
|
478
|
+
if search_in in ['name', 'all']:
|
|
479
|
+
name = prompt_data.get('name', '').lower()
|
|
480
|
+
if query in name:
|
|
481
|
+
match_type = 'name'
|
|
482
|
+
|
|
483
|
+
# Search in content
|
|
484
|
+
if not match_type and search_in in ['content', 'all']:
|
|
485
|
+
content = prompt_data.get('content', '').lower()
|
|
486
|
+
if query in content:
|
|
487
|
+
match_type = 'content'
|
|
488
|
+
|
|
489
|
+
# Search in tags
|
|
490
|
+
if not match_type and search_in in ['tags', 'all']:
|
|
491
|
+
tags = prompt_data.get('tags', [])
|
|
492
|
+
if any(query in tag.lower() for tag in tags):
|
|
493
|
+
match_type = 'tags'
|
|
494
|
+
|
|
495
|
+
if match_type:
|
|
496
|
+
results.append({
|
|
497
|
+
'name': prompt_data.get('name', path),
|
|
498
|
+
'path': path,
|
|
499
|
+
'folder': prompt_data.get('_folder', ''),
|
|
500
|
+
'description': prompt_data.get('description', ''),
|
|
501
|
+
'match_type': match_type,
|
|
502
|
+
'tags': prompt_data.get('tags', [])
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
'count': len(results),
|
|
507
|
+
'results': results
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
def _action_create_folder(self, params: Dict) -> Dict:
|
|
511
|
+
"""
|
|
512
|
+
Create a new folder in the library.
|
|
513
|
+
|
|
514
|
+
Params:
|
|
515
|
+
path (required): Folder path (e.g., "Domain Expertise/Medical")
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
{success: bool, path: str, message: str}
|
|
519
|
+
"""
|
|
520
|
+
path = params.get('path')
|
|
521
|
+
if not path:
|
|
522
|
+
raise ValueError("Missing required parameter: path")
|
|
523
|
+
|
|
524
|
+
success = self.prompt_library.create_folder(path)
|
|
525
|
+
|
|
526
|
+
if success:
|
|
527
|
+
return {
|
|
528
|
+
'success': True,
|
|
529
|
+
'path': path,
|
|
530
|
+
'message': f"Created folder: {path}"
|
|
531
|
+
}
|
|
532
|
+
else:
|
|
533
|
+
raise Exception("Failed to create folder")
|
|
534
|
+
|
|
535
|
+
def _action_toggle_favorite(self, params: Dict) -> Dict:
|
|
536
|
+
"""
|
|
537
|
+
Toggle favorite status of a prompt.
|
|
538
|
+
|
|
539
|
+
Params:
|
|
540
|
+
path (required): Relative path to prompt
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
{success: bool, path: str, favorite: bool, message: str}
|
|
544
|
+
"""
|
|
545
|
+
path = params.get('path')
|
|
546
|
+
if not path:
|
|
547
|
+
raise ValueError("Missing required parameter: path")
|
|
548
|
+
|
|
549
|
+
success = self.prompt_library.toggle_favorite(path)
|
|
550
|
+
|
|
551
|
+
if success:
|
|
552
|
+
is_favorite = self.prompt_library.prompts[path].get('favorite', False)
|
|
553
|
+
return {
|
|
554
|
+
'success': True,
|
|
555
|
+
'path': path,
|
|
556
|
+
'favorite': is_favorite,
|
|
557
|
+
'message': f"{'Added to' if is_favorite else 'Removed from'} favorites"
|
|
558
|
+
}
|
|
559
|
+
else:
|
|
560
|
+
raise Exception("Failed to toggle favorite")
|
|
561
|
+
|
|
562
|
+
def _action_toggle_quick_run(self, params: Dict) -> Dict:
|
|
563
|
+
"""
|
|
564
|
+
Toggle QuickMenu (legacy name: quick_run) status of a prompt.
|
|
565
|
+
|
|
566
|
+
Params:
|
|
567
|
+
path (required): Relative path to prompt
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
{success: bool, path: str, quick_run: bool, message: str}
|
|
571
|
+
"""
|
|
572
|
+
path = params.get('path')
|
|
573
|
+
if not path:
|
|
574
|
+
raise ValueError("Missing required parameter: path")
|
|
575
|
+
|
|
576
|
+
success = self.prompt_library.toggle_quick_run(path)
|
|
577
|
+
|
|
578
|
+
if success:
|
|
579
|
+
is_quick_run = self.prompt_library.prompts[path].get('quick_run', False)
|
|
580
|
+
return {
|
|
581
|
+
'success': True,
|
|
582
|
+
'path': path,
|
|
583
|
+
'quick_run': is_quick_run,
|
|
584
|
+
'message': f"{'Added to' if is_quick_run else 'Removed from'} QuickMenu"
|
|
585
|
+
}
|
|
586
|
+
else:
|
|
587
|
+
raise Exception("Failed to toggle quick run")
|
|
588
|
+
|
|
589
|
+
def _action_get_favorites(self, params: Dict) -> Dict:
|
|
590
|
+
"""
|
|
591
|
+
Get list of favorite prompts.
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
{count: int, favorites: [{name, path}]}
|
|
595
|
+
"""
|
|
596
|
+
favorites = self.prompt_library.get_favorites()
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
'count': len(favorites),
|
|
600
|
+
'favorites': [{'name': name, 'path': path} for path, name in favorites]
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
def _action_get_quick_run(self, params: Dict) -> Dict:
|
|
604
|
+
"""
|
|
605
|
+
Get list of QuickMenu prompts (legacy name: quick_run).
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
{count: int, prompts: [{name, path}]}
|
|
609
|
+
"""
|
|
610
|
+
quick_run = self.prompt_library.get_quick_run_prompts()
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
'count': len(quick_run),
|
|
614
|
+
'prompts': [{'name': name, 'path': path} for path, name in quick_run]
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
def _action_get_folder_structure(self, params: Dict) -> Dict:
|
|
618
|
+
"""
|
|
619
|
+
Get complete folder structure of the library.
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
Nested dictionary representing folder structure
|
|
623
|
+
"""
|
|
624
|
+
structure = self.prompt_library.get_folder_structure()
|
|
625
|
+
|
|
626
|
+
return structure
|
|
627
|
+
|
|
628
|
+
def _action_get_segment_count(self, params: Dict) -> Dict:
|
|
629
|
+
"""
|
|
630
|
+
Get the total number of segments in the current project.
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
{total_segments: int, translated: int, untranslated: int}
|
|
634
|
+
"""
|
|
635
|
+
if not self.parent_app:
|
|
636
|
+
raise ValueError("Parent app not available for segment access")
|
|
637
|
+
|
|
638
|
+
if not hasattr(self.parent_app, 'current_project') or not self.parent_app.current_project:
|
|
639
|
+
raise ValueError("No project currently loaded")
|
|
640
|
+
|
|
641
|
+
project = self.parent_app.current_project
|
|
642
|
+
if not hasattr(project, 'segments') or not project.segments:
|
|
643
|
+
return {
|
|
644
|
+
'total_segments': 0,
|
|
645
|
+
'translated': 0,
|
|
646
|
+
'untranslated': 0
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
segments = project.segments
|
|
650
|
+
total = len(segments)
|
|
651
|
+
translated = sum(1 for seg in segments if seg.target and seg.target.strip())
|
|
652
|
+
untranslated = total - translated
|
|
653
|
+
|
|
654
|
+
return {
|
|
655
|
+
'total_segments': total,
|
|
656
|
+
'translated': translated,
|
|
657
|
+
'untranslated': untranslated
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
def _action_get_segment_info(self, params: Dict) -> Dict:
|
|
661
|
+
"""
|
|
662
|
+
Get detailed information about specific segment(s).
|
|
663
|
+
|
|
664
|
+
Params:
|
|
665
|
+
segment_id (optional): Specific segment ID to retrieve
|
|
666
|
+
segment_ids (optional): List of segment IDs to retrieve
|
|
667
|
+
start_id (optional): Start of range (inclusive)
|
|
668
|
+
end_id (optional): End of range (inclusive)
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
{segments: [{id, source, target, status, type, notes, ...}]}
|
|
672
|
+
"""
|
|
673
|
+
if not self.parent_app:
|
|
674
|
+
raise ValueError("Parent app not available for segment access")
|
|
675
|
+
|
|
676
|
+
if not hasattr(self.parent_app, 'current_project') or not self.parent_app.current_project:
|
|
677
|
+
raise ValueError("No project currently loaded")
|
|
678
|
+
|
|
679
|
+
project = self.parent_app.current_project
|
|
680
|
+
if not hasattr(project, 'segments') or not project.segments:
|
|
681
|
+
return {'segments': []}
|
|
682
|
+
|
|
683
|
+
all_segments = project.segments
|
|
684
|
+
|
|
685
|
+
# Determine which segments to retrieve
|
|
686
|
+
segment_id = params.get('segment_id')
|
|
687
|
+
segment_ids = params.get('segment_ids')
|
|
688
|
+
start_id = params.get('start_id')
|
|
689
|
+
end_id = params.get('end_id')
|
|
690
|
+
|
|
691
|
+
target_segments = []
|
|
692
|
+
|
|
693
|
+
if segment_id is not None:
|
|
694
|
+
# Single segment by ID
|
|
695
|
+
for seg in all_segments:
|
|
696
|
+
if seg.id == segment_id:
|
|
697
|
+
target_segments.append(seg)
|
|
698
|
+
break
|
|
699
|
+
elif segment_ids:
|
|
700
|
+
# Multiple segments by IDs
|
|
701
|
+
id_set = set(segment_ids)
|
|
702
|
+
target_segments = [seg for seg in all_segments if seg.id in id_set]
|
|
703
|
+
elif start_id is not None or end_id is not None:
|
|
704
|
+
# Range of segments
|
|
705
|
+
start = start_id if start_id is not None else 1
|
|
706
|
+
end = end_id if end_id is not None else float('inf')
|
|
707
|
+
target_segments = [seg for seg in all_segments if start <= seg.id <= end]
|
|
708
|
+
else:
|
|
709
|
+
# No filter specified - return all segments (limited to first 50)
|
|
710
|
+
target_segments = all_segments[:50]
|
|
711
|
+
|
|
712
|
+
# Convert segments to dictionaries
|
|
713
|
+
segments_data = []
|
|
714
|
+
for seg in target_segments:
|
|
715
|
+
seg_dict = {
|
|
716
|
+
'id': seg.id,
|
|
717
|
+
'source': seg.source,
|
|
718
|
+
'target': seg.target,
|
|
719
|
+
'status': seg.status,
|
|
720
|
+
'type': seg.type,
|
|
721
|
+
'notes': seg.notes,
|
|
722
|
+
'match_percent': seg.match_percent,
|
|
723
|
+
'locked': seg.locked,
|
|
724
|
+
'paragraph_id': seg.paragraph_id,
|
|
725
|
+
'style': seg.style,
|
|
726
|
+
'document_position': seg.document_position,
|
|
727
|
+
'is_table_cell': seg.is_table_cell
|
|
728
|
+
}
|
|
729
|
+
segments_data.append(seg_dict)
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
'segments': segments_data,
|
|
733
|
+
'count': len(segments_data)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
def _action_activate_prompt(self, params: Dict) -> Dict:
|
|
737
|
+
"""
|
|
738
|
+
Activate/attach a prompt to the current project.
|
|
739
|
+
|
|
740
|
+
Params:
|
|
741
|
+
path (required): Path to the prompt to activate
|
|
742
|
+
mode (optional): 'primary' or 'attach' (default: 'primary')
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
{success: bool, message: str}
|
|
746
|
+
"""
|
|
747
|
+
path = params.get('path')
|
|
748
|
+
mode = params.get('mode', 'primary')
|
|
749
|
+
|
|
750
|
+
if not path:
|
|
751
|
+
raise ValueError("Missing required parameter: path")
|
|
752
|
+
|
|
753
|
+
# Check if prompt exists
|
|
754
|
+
if path not in self.prompt_library.prompts:
|
|
755
|
+
raise ValueError(f"Prompt not found: {path}")
|
|
756
|
+
|
|
757
|
+
if mode == 'primary':
|
|
758
|
+
# Set as primary prompt
|
|
759
|
+
self.prompt_library.set_primary_prompt(path)
|
|
760
|
+
return {
|
|
761
|
+
'success': True,
|
|
762
|
+
'message': f"✓ Activated as primary prompt: {self.prompt_library.prompts[path].get('name', path)}"
|
|
763
|
+
}
|
|
764
|
+
elif mode == 'attach':
|
|
765
|
+
# Attach as additional prompt
|
|
766
|
+
if path not in self.prompt_library.attached_prompts:
|
|
767
|
+
self.prompt_library.attached_prompts.append(path)
|
|
768
|
+
self.prompt_library.save_active_state()
|
|
769
|
+
return {
|
|
770
|
+
'success': True,
|
|
771
|
+
'message': f"✓ Attached prompt: {self.prompt_library.prompts[path].get('name', path)}"
|
|
772
|
+
}
|
|
773
|
+
else:
|
|
774
|
+
return {
|
|
775
|
+
'success': False,
|
|
776
|
+
'message': f"Prompt already attached: {path}"
|
|
777
|
+
}
|
|
778
|
+
else:
|
|
779
|
+
raise ValueError(f"Invalid mode: {mode}. Use 'primary' or 'attach'")
|
|
780
|
+
|
|
781
|
+
def get_system_prompt_addition(self) -> str:
|
|
782
|
+
"""
|
|
783
|
+
Get text to add to AI system prompt to enable action usage.
|
|
784
|
+
|
|
785
|
+
Returns:
|
|
786
|
+
String to append to AI system prompt
|
|
787
|
+
"""
|
|
788
|
+
return """
|
|
789
|
+
|
|
790
|
+
## 🔧 Available Actions
|
|
791
|
+
|
|
792
|
+
You can interact with the Supervertaler Prompt Library using structured actions.
|
|
793
|
+
When you want to perform an action, use this format:
|
|
794
|
+
|
|
795
|
+
ACTION:function_name
|
|
796
|
+
PARAMS:{"param1": "value1", "param2": "value2"}
|
|
797
|
+
|
|
798
|
+
Available actions:
|
|
799
|
+
|
|
800
|
+
### 1. list_prompts
|
|
801
|
+
List all prompts, optionally filtered by folder.
|
|
802
|
+
PARAMS: {"folder": "Domain Expertise" (optional), "include_content": false (optional)}
|
|
803
|
+
|
|
804
|
+
### 2. get_prompt
|
|
805
|
+
Get full details of a specific prompt.
|
|
806
|
+
PARAMS: {"path": "Domain Expertise/Medical.md"}
|
|
807
|
+
|
|
808
|
+
### 3. create_prompt
|
|
809
|
+
Create a new prompt.
|
|
810
|
+
PARAMS: {
|
|
811
|
+
"name": "Medical Translator",
|
|
812
|
+
"content": "You are an expert medical translator...",
|
|
813
|
+
"folder": "Domain Expertise" (optional),
|
|
814
|
+
"description": "Expert medical translation" (optional),
|
|
815
|
+
"tags": ["medical", "technical"] (optional)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
### 4. update_prompt
|
|
819
|
+
Update an existing prompt.
|
|
820
|
+
PARAMS: {
|
|
821
|
+
"path": "Domain Expertise/Medical.md",
|
|
822
|
+
"content": "Updated content..." (optional),
|
|
823
|
+
"name": "New name" (optional),
|
|
824
|
+
"description": "New description" (optional)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
### 5. delete_prompt
|
|
828
|
+
Delete a prompt.
|
|
829
|
+
PARAMS: {"path": "Domain Expertise/Medical.md"}
|
|
830
|
+
|
|
831
|
+
### 6. search_prompts
|
|
832
|
+
Search prompts by query.
|
|
833
|
+
PARAMS: {
|
|
834
|
+
"query": "medical",
|
|
835
|
+
"search_in": "all" (options: name, content, tags, all)
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
### 7. create_folder
|
|
839
|
+
Create a new folder.
|
|
840
|
+
PARAMS: {"path": "Domain Expertise/Medical"}
|
|
841
|
+
|
|
842
|
+
### 8. toggle_favorite
|
|
843
|
+
Toggle favorite status.
|
|
844
|
+
PARAMS: {"path": "Domain Expertise/Medical.md"}
|
|
845
|
+
|
|
846
|
+
### 9. toggle_quick_run
|
|
847
|
+
Toggle QuickMenu status (legacy name: quick_run).
|
|
848
|
+
PARAMS: {"path": "Domain Expertise/Medical.md"}
|
|
849
|
+
|
|
850
|
+
### 10. get_favorites
|
|
851
|
+
Get list of favorite prompts.
|
|
852
|
+
PARAMS: {}
|
|
853
|
+
|
|
854
|
+
### 11. get_quick_run
|
|
855
|
+
Get list of QuickMenu prompts (legacy name: quick_run).
|
|
856
|
+
PARAMS: {}
|
|
857
|
+
|
|
858
|
+
### 12. get_folder_structure
|
|
859
|
+
Get complete folder structure.
|
|
860
|
+
PARAMS: {}
|
|
861
|
+
|
|
862
|
+
### 13. get_segment_count
|
|
863
|
+
Get total segment count and translation progress.
|
|
864
|
+
PARAMS: {}
|
|
865
|
+
|
|
866
|
+
### 14. get_segment_info
|
|
867
|
+
Get detailed information about specific segment(s).
|
|
868
|
+
PARAMS: {
|
|
869
|
+
"segment_id": 5 (single segment) OR
|
|
870
|
+
"segment_ids": [1, 5, 10] (multiple segments) OR
|
|
871
|
+
"start_id": 1, "end_id": 10 (range of segments)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
### 15. activate_prompt
|
|
875
|
+
Activate/attach a prompt to the current project.
|
|
876
|
+
PARAMS: {
|
|
877
|
+
"path": "Domain Expertise/Medical.md",
|
|
878
|
+
"mode": "primary" (or "attach")
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
**Important:**
|
|
882
|
+
- Actions are executed automatically when you include them in your response
|
|
883
|
+
- You'll see the results immediately
|
|
884
|
+
- You can include multiple actions in one response
|
|
885
|
+
- Always use valid JSON for PARAMS
|
|
886
|
+
- Wrap your normal conversational response around the actions
|
|
887
|
+
"""
|
|
888
|
+
|
|
889
|
+
def format_action_results(self, action_results: List[Dict]) -> str:
|
|
890
|
+
"""
|
|
891
|
+
Format action results for display in chat.
|
|
892
|
+
|
|
893
|
+
Args:
|
|
894
|
+
action_results: List of action result dictionaries
|
|
895
|
+
|
|
896
|
+
Returns:
|
|
897
|
+
Formatted string for display
|
|
898
|
+
"""
|
|
899
|
+
if not action_results:
|
|
900
|
+
return ""
|
|
901
|
+
|
|
902
|
+
output = "\n\n**Action Results:**\n"
|
|
903
|
+
|
|
904
|
+
for result in action_results:
|
|
905
|
+
action_name = result['action']
|
|
906
|
+
|
|
907
|
+
if result['success']:
|
|
908
|
+
output += f"\n✓ **{action_name}**: {result['result'].get('message', 'Success')}\n"
|
|
909
|
+
|
|
910
|
+
# Add additional details based on action type
|
|
911
|
+
if action_name == 'list_prompts':
|
|
912
|
+
count = result['result']['count']
|
|
913
|
+
output += f" Found {count} prompts\n"
|
|
914
|
+
|
|
915
|
+
elif action_name == 'search_prompts':
|
|
916
|
+
count = result['result']['count']
|
|
917
|
+
output += f" Found {count} matching prompts\n"
|
|
918
|
+
for match in result['result']['results'][:5]: # Show first 5
|
|
919
|
+
output += f" - {match['name']} ({match['folder']})\n"
|
|
920
|
+
|
|
921
|
+
elif action_name == 'create_prompt':
|
|
922
|
+
output += f" Path: {result['result']['path']}\n"
|
|
923
|
+
|
|
924
|
+
elif action_name == 'activate_prompt':
|
|
925
|
+
# Just use the message from the result
|
|
926
|
+
pass
|
|
927
|
+
|
|
928
|
+
elif action_name == 'get_segment_count':
|
|
929
|
+
total = result['result']['total_segments']
|
|
930
|
+
translated = result['result']['translated']
|
|
931
|
+
untranslated = result['result']['untranslated']
|
|
932
|
+
output += f" Total segments: {total}\n"
|
|
933
|
+
output += f" Translated: {translated}\n"
|
|
934
|
+
output += f" Untranslated: {untranslated}\n"
|
|
935
|
+
|
|
936
|
+
elif action_name == 'get_segment_info':
|
|
937
|
+
segments = result['result']['segments']
|
|
938
|
+
count = result['result']['count']
|
|
939
|
+
output += f" Retrieved {count} segment(s)\n\n"
|
|
940
|
+
for seg in segments:
|
|
941
|
+
output += f" **Segment {seg['id']}:**\n"
|
|
942
|
+
# Escape HTML entities for display
|
|
943
|
+
# Order matters: & must be first to avoid double-escaping
|
|
944
|
+
source = (seg['source']
|
|
945
|
+
.replace('&', '&')
|
|
946
|
+
.replace('<', '<')
|
|
947
|
+
.replace('>', '>')
|
|
948
|
+
.replace('"', '"'))
|
|
949
|
+
output += f" Source: `{source}`\n"
|
|
950
|
+
if seg['target']:
|
|
951
|
+
target = (seg['target']
|
|
952
|
+
.replace('&', '&')
|
|
953
|
+
.replace('<', '<')
|
|
954
|
+
.replace('>', '>')
|
|
955
|
+
.replace('"', '"'))
|
|
956
|
+
output += f" Target: `{target}`\n"
|
|
957
|
+
output += f" Status: {seg['status']}\n"
|
|
958
|
+
if seg['notes']:
|
|
959
|
+
output += f" Notes: {seg['notes']}\n"
|
|
960
|
+
output += "\n"
|
|
961
|
+
else:
|
|
962
|
+
output += f"\n✗ **{action_name}**: {result['error']}\n"
|
|
963
|
+
|
|
964
|
+
return output
|