supervertaler 1.9.147__py3-none-any.whl → 1.9.149__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.
@@ -29,15 +29,15 @@ class AttachmentManager:
29
29
  Initialize the AttachmentManager.
30
30
 
31
31
  Args:
32
- base_dir: Base directory for attachments (default: user_data_private/AI_Assistant)
32
+ base_dir: Base directory for attachments (default: user_data_private/ai_assistant)
33
33
  log_callback: Function to call for logging messages
34
34
  """
35
35
  self.log = log_callback if log_callback else print
36
36
 
37
37
  # Set base directory
38
38
  if base_dir is None:
39
- # Default to user_data_private/AI_Assistant
40
- base_dir = Path("user_data_private") / "AI_Assistant"
39
+ # Default to user_data_private/ai_assistant
40
+ base_dir = Path("user_data_private") / "ai_assistant"
41
41
 
42
42
  self.base_dir = Path(base_dir)
43
43
  self.attachments_dir = self.base_dir / "attachments"
modules/config_manager.py CHANGED
@@ -35,14 +35,14 @@ class ConfigManager:
35
35
  REQUIRED_FOLDERS = [
36
36
  # Note: Old numbered folders (1_System_Prompts, 2_Domain_Prompts, etc.) are deprecated
37
37
  # Migration moves them to unified Library structure
38
- "Prompt_Library/Domain Expertise",
39
- "Prompt_Library/Project Prompts",
40
- "Prompt_Library/Style Guides",
41
- "Translation_Resources/Termbases",
42
- "Translation_Resources/TMs",
43
- "Translation_Resources/Non-translatables",
44
- "Translation_Resources/Segmentation_rules",
45
- "Projects",
38
+ "prompt_library/domain_expertise",
39
+ "prompt_library/project_prompts",
40
+ "prompt_library/style_guides",
41
+ "resources/termbases",
42
+ "resources/tms",
43
+ "resources/non_translatables",
44
+ "resources/segmentation_rules",
45
+ "projects",
46
46
  ]
47
47
 
48
48
  def __init__(self):
@@ -268,8 +268,8 @@ class ConfigManager:
268
268
  Get the full path to a subfolder in user_data.
269
269
 
270
270
  Example:
271
- config.get_subfolder_path('Translation_Resources/TMs')
272
- -> '/home/user/Supervertaler_Data/Translation_Resources/TMs'
271
+ config.get_subfolder_path('resources/tms')
272
+ -> '/home/user/Supervertaler/resources/tms'
273
273
  """
274
274
  user_data_path = self.get_user_data_path()
275
275
  full_path = os.path.join(user_data_path, subfolder)
@@ -1124,6 +1124,12 @@ class DatabaseManager:
1124
1124
  Uses FTS5 full-text search for fast matching on millions of segments.
1125
1125
  Falls back to LIKE queries if FTS5 fails.
1126
1126
 
1127
+ Language filters define what you're searching FOR and what translation you want:
1128
+ - "From: Dutch, To: English" = Search for Dutch text, show English translations
1129
+ - Searches ALL TMs (regardless of their stored language pair direction)
1130
+ - Automatically swaps columns when needed (e.g., finds Dutch in target column of EN→NL TM)
1131
+ - This is MORE intuitive than traditional CAT tools that only search specific TM directions
1132
+
1127
1133
  Args:
1128
1134
  query: Text to search for
1129
1135
  tm_ids: List of TM IDs to search (None = all)
@@ -1141,6 +1147,12 @@ class DatabaseManager:
1141
1147
  # Wrap in quotes for phrase search
1142
1148
  fts_query = f'"{fts_query}"'
1143
1149
 
1150
+ # When language filters specified, we need to search intelligently:
1151
+ # - Don't filter by TM language pair (search ALL TMs)
1152
+ # - Search in BOTH columns to find text
1153
+ # - Swap columns if needed to show correct language order
1154
+ use_smart_search = (source_langs or target_langs)
1155
+
1144
1156
  try:
1145
1157
  # Use FTS5 for fast full-text search
1146
1158
  if direction == 'source':
@@ -1171,20 +1183,105 @@ class DatabaseManager:
1171
1183
  fts_sql += f" AND tu.tm_id IN ({placeholders})"
1172
1184
  params.extend(tm_ids)
1173
1185
 
1174
- # Add language filters (support for list of variants)
1175
- if source_langs:
1176
- placeholders = ','.join('?' * len(source_langs))
1177
- fts_sql += f" AND tu.source_lang IN ({placeholders})"
1178
- params.extend(source_langs)
1179
- if target_langs:
1180
- placeholders = ','.join('?' * len(target_langs))
1181
- fts_sql += f" AND tu.target_lang IN ({placeholders})"
1182
- params.extend(target_langs)
1186
+ # DON'T filter by language when smart search active
1187
+ # (we need to search all TMs and figure out which column has our language)
1188
+ if not use_smart_search:
1189
+ # Traditional filtering when no language filters
1190
+ if source_langs:
1191
+ placeholders = ','.join('?' * len(source_langs))
1192
+ fts_sql += f" AND tu.source_lang IN ({placeholders})"
1193
+ params.extend(source_langs)
1194
+ if target_langs:
1195
+ placeholders = ','.join('?' * len(target_langs))
1196
+ fts_sql += f" AND tu.target_lang IN ({placeholders})"
1197
+ params.extend(target_langs)
1183
1198
 
1184
1199
  fts_sql += " ORDER BY tu.modified_date DESC LIMIT 100"
1185
1200
 
1186
1201
  self.cursor.execute(fts_sql, params)
1187
- return [dict(row) for row in self.cursor.fetchall()]
1202
+ raw_results = [dict(row) for row in self.cursor.fetchall()]
1203
+
1204
+ # Smart search: Filter and swap based on language metadata
1205
+ if use_smart_search:
1206
+ processed_results = []
1207
+ for row in raw_results:
1208
+ row_src_lang = row.get('source_lang', '')
1209
+ row_tgt_lang = row.get('target_lang', '')
1210
+
1211
+ # Check if this row matches our language requirements
1212
+ # If "From: Dutch, To: English":
1213
+ # - Accept if source=nl and target=en (normal)
1214
+ # - Accept if source=en and target=nl (swap needed)
1215
+
1216
+ matches = False
1217
+ needs_swap = False
1218
+
1219
+ if source_langs and target_langs:
1220
+ # Both filters specified
1221
+ if row_src_lang in source_langs and row_tgt_lang in target_langs:
1222
+ # Perfect match - no swap
1223
+ matches = True
1224
+ needs_swap = False
1225
+ elif row_src_lang in target_langs and row_tgt_lang in source_langs:
1226
+ # Reversed - needs swap
1227
+ matches = True
1228
+ needs_swap = True
1229
+ elif source_langs:
1230
+ # Only "From" specified - just check if Dutch is in EITHER column
1231
+ if row_src_lang in source_langs:
1232
+ matches = True
1233
+ needs_swap = False
1234
+ elif row_tgt_lang in source_langs:
1235
+ matches = True
1236
+ needs_swap = True
1237
+ elif target_langs:
1238
+ # Only "To" specified - just check if English is in EITHER column
1239
+ if row_tgt_lang in target_langs:
1240
+ matches = True
1241
+ needs_swap = False
1242
+ elif row_src_lang in target_langs:
1243
+ matches = True
1244
+ needs_swap = True
1245
+
1246
+ if matches:
1247
+ # CRITICAL CHECK: Verify the search text is actually in the correct column
1248
+ # If user searches for Dutch with "From: Dutch", the text must be in the source column (after any swap)
1249
+ # This prevents finding Dutch text when user asks to search FOR English
1250
+
1251
+ if needs_swap:
1252
+ # After swap, check if query is in the NEW source column (was target)
1253
+ text_to_check = row['target_text'].lower()
1254
+ else:
1255
+ # No swap, check if query is in source column
1256
+ text_to_check = row['source_text'].lower()
1257
+
1258
+ # Only include if query text is actually in the source column
1259
+ if query.lower() in text_to_check:
1260
+ if needs_swap:
1261
+ # Swap columns to show correct language order
1262
+ swapped_row = row.copy()
1263
+ swapped_row['source'] = row['target_text']
1264
+ swapped_row['target'] = row['source_text']
1265
+ swapped_row['source_lang'] = row['target_lang']
1266
+ swapped_row['target_lang'] = row['source_lang']
1267
+ processed_results.append(swapped_row)
1268
+ else:
1269
+ # No swap needed - just rename columns
1270
+ processed_row = row.copy()
1271
+ processed_row['source'] = row['source_text']
1272
+ processed_row['target'] = row['target_text']
1273
+ processed_results.append(processed_row)
1274
+
1275
+ return processed_results
1276
+ else:
1277
+ # No language filters - just rename columns
1278
+ processed_results = []
1279
+ for row in raw_results:
1280
+ processed_row = row.copy()
1281
+ processed_row['source'] = row['source_text']
1282
+ processed_row['target'] = row['target_text']
1283
+ processed_results.append(processed_row)
1284
+ return processed_results
1188
1285
 
1189
1286
  except Exception as e:
1190
1287
  # Fallback to LIKE query if FTS5 fails (e.g., index not built)
@@ -172,7 +172,7 @@ class NonTranslatablesManager:
172
172
  Initialize manager.
173
173
 
174
174
  Args:
175
- base_path: Base path for NT files (typically user_data/Translation_Resources/Non-translatables)
175
+ base_path: Base path for NT files (typically user_data/resources/non_translatables)
176
176
  log_callback: Optional logging function
177
177
  """
178
178
  self.base_path = Path(base_path)
@@ -29,7 +29,7 @@ class PromptLibraryMigration:
29
29
  def __init__(self, prompt_library_dir: str, log_callback=None):
30
30
  """
31
31
  Args:
32
- prompt_library_dir: Path to user_data/Prompt_Library
32
+ prompt_library_dir: Path to user_data/prompt_library
33
33
  log_callback: Function for logging
34
34
  """
35
35
  self.prompt_library_dir = Path(prompt_library_dir)
modules/setup_wizard.py CHANGED
@@ -80,17 +80,17 @@ class SetupWizard:
80
80
  "Supervertaler will create the following structure:\n\n"
81
81
  f"{self.selected_path}\n"
82
82
  f" ├── api_keys.txt\n"
83
- f" ├── Prompt_Library/\n"
83
+ f" ├── prompt_library/\n"
84
84
  f" │ ├── 1_System_Prompts/\n"
85
85
  f" │ ├── 2_Domain_Prompts/\n"
86
86
  f" │ ├── 3_Project_Prompts/\n"
87
87
  f" │ └── 4_Style_Guides/\n"
88
- f" ├── Translation_Resources/\n"
88
+ f" ├── resources/\n"
89
89
  f" │ ├── TMs/\n"
90
90
  f" │ ├── Glossaries/\n"
91
- f" │ ├── Non-translatables/\n"
92
- f" │ └── Segmentation_rules/\n"
93
- f" └── Projects/\n\n"
91
+ f" │ ├── non_translatables/\n"
92
+ f" │ └── segmentation_rules/\n"
93
+ f" └── projects/\n\n"
94
94
  "Is this correct?"
95
95
  )
96
96
 
@@ -140,9 +140,9 @@ class SetupWizard:
140
140
  f"Your data folder: {self.selected_path}\n\n"
141
141
  f"Created:\n"
142
142
  f" • api_keys.txt (add your API keys here)\n"
143
- f" • Prompt_Library/ (your prompts)\n"
144
- f" • Translation_Resources/ (TMs, glossaries)\n"
145
- f" • Projects/ (your work)\n\n"
143
+ f" • prompt_library/ (your prompts)\n"
144
+ f" • resources/ (TMs, glossaries)\n"
145
+ f" • projects/ (your work)\n\n"
146
146
  f"All your translation memories, prompts, and projects\n"
147
147
  f"will be stored in this location."
148
148
  )
modules/superbrowser.py CHANGED
@@ -45,11 +45,12 @@ def _clear_corrupted_cache(storage_path: str):
45
45
  class ChatColumn(QWidget):
46
46
  """A column containing a chat interface with web browser"""
47
47
 
48
- def __init__(self, title, url, header_color, parent=None):
48
+ def __init__(self, title, url, header_color, parent=None, user_data_path=None):
49
49
  super().__init__(parent)
50
50
  self.title = title
51
51
  self.url = url
52
52
  self.header_color = header_color
53
+ self.user_data_path = user_data_path # Store user data path
53
54
  self.init_ui()
54
55
 
55
56
  def init_ui(self):
@@ -102,12 +103,14 @@ class ChatColumn(QWidget):
102
103
  profile_name = f"superbrowser_{self.title.lower()}"
103
104
  self.profile = QWebEngineProfile(profile_name, self)
104
105
 
105
- # Set persistent storage path (use same pattern as main app)
106
- # Check if running in dev mode (private data folder)
107
- dev_mode_marker = os.path.join(os.path.dirname(__file__), "..", ".supervertaler.local")
108
- base_folder = "user_data_private" if os.path.exists(dev_mode_marker) else "user_data"
109
-
110
- storage_path = os.path.join(os.path.dirname(__file__), "..", base_folder, "superbrowser_profiles", profile_name)
106
+ # Set persistent storage path using user_data_path from parent
107
+ if self.user_data_path:
108
+ storage_path = os.path.join(str(self.user_data_path), "superbrowser_profiles", profile_name)
109
+ else:
110
+ # Fallback to script directory if user_data_path not provided
111
+ dev_mode_marker = os.path.join(os.path.dirname(__file__), "..", ".supervertaler.local")
112
+ base_folder = "user_data_private" if os.path.exists(dev_mode_marker) else "user_data"
113
+ storage_path = os.path.join(os.path.dirname(__file__), "..", base_folder, "superbrowser_profiles", profile_name)
111
114
  os.makedirs(storage_path, exist_ok=True)
112
115
 
113
116
  # Clear potentially corrupted cache to prevent Chromium errors
@@ -166,9 +169,10 @@ class SuperbrowserWidget(QWidget):
166
169
  and concurrent interaction with different AI models.
167
170
  """
168
171
 
169
- def __init__(self, parent=None):
172
+ def __init__(self, parent=None, user_data_path=None):
170
173
  super().__init__(parent)
171
174
  self.parent_window = parent
175
+ self.user_data_path = user_data_path # Store user data path for profiles
172
176
 
173
177
  # Default URLs for AI chat interfaces
174
178
  self.chatgpt_url = "https://chatgpt.com/"
@@ -257,10 +261,10 @@ class SuperbrowserWidget(QWidget):
257
261
  splitter = QSplitter(Qt.Orientation.Horizontal)
258
262
  splitter.setHandleWidth(3)
259
263
 
260
- # Create chat columns
261
- self.chatgpt_column = ChatColumn("ChatGPT", self.chatgpt_url, "#10a37f", self)
262
- self.claude_column = ChatColumn("Claude", self.claude_url, "#c17c4f", self)
263
- self.gemini_column = ChatColumn("Gemini", self.gemini_url, "#4285f4", self)
264
+ # Create chat columns - pass user_data_path for profile storage
265
+ self.chatgpt_column = ChatColumn("ChatGPT", self.chatgpt_url, "#10a37f", self, user_data_path=self.user_data_path)
266
+ self.claude_column = ChatColumn("Claude", self.claude_url, "#c17c4f", self, user_data_path=self.user_data_path)
267
+ self.gemini_column = ChatColumn("Gemini", self.gemini_url, "#4285f4", self, user_data_path=self.user_data_path)
264
268
 
265
269
  # Add columns to splitter
266
270
  splitter.addWidget(self.chatgpt_column)
modules/superlookup.py CHANGED
@@ -157,9 +157,13 @@ class SuperlookupEngine:
157
157
 
158
158
  # Convert to LookupResult format (limit results)
159
159
  for match in matches[:max_results]:
160
+ # Use 'source' and 'target' keys (matches database column names)
161
+ source_text = match.get('source', '')
162
+ target_text = match.get('target', '')
163
+ print(f"[Superlookup] Extracted: source='{source_text[:50]}...', target='{target_text[:50]}...'")
160
164
  results.append(LookupResult(
161
- source=match.get('source', ''),
162
- target=match.get('target', ''),
165
+ source=source_text,
166
+ target=target_text,
163
167
  match_percent=100, # Concordance = contains the text
164
168
  source_type='tm',
165
169
  metadata={
modules/tmx_editor_qt.py CHANGED
@@ -2655,7 +2655,7 @@ if __name__ == "__main__":
2655
2655
  os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".supervertaler.local")
2656
2656
  )
2657
2657
  user_data_path = Path("user_data_private" if ENABLE_PRIVATE_FEATURES else "user_data")
2658
- db_path = user_data_path / "Translation_Resources" / "supervertaler.db"
2658
+ db_path = user_data_path / "resources" / "supervertaler.db"
2659
2659
 
2660
2660
  # Ensure database directory exists
2661
2661
  db_path.parent.mkdir(parents=True, exist_ok=True)
@@ -401,7 +401,7 @@ class TMDatabase:
401
401
 
402
402
  def load_tmx_file(self, filepath: str, src_lang: str, tgt_lang: str,
403
403
  tm_name: str = None, read_only: bool = False,
404
- strip_variants: bool = True) -> tuple[str, int]:
404
+ strip_variants: bool = True, progress_callback=None) -> tuple[str, int]:
405
405
  """
406
406
  Load TMX file into a new custom TM
407
407
 
@@ -412,6 +412,7 @@ class TMDatabase:
412
412
  tm_name: Custom name for TM (default: filename)
413
413
  read_only: Make TM read-only
414
414
  strip_variants: Match base languages ignoring regional variants (default: True)
415
+ progress_callback: Optional callback function(current, total, message) for progress updates
415
416
 
416
417
  Returns: (tm_id, entry_count)
417
418
  """
@@ -423,16 +424,18 @@ class TMDatabase:
423
424
  self.add_custom_tm(tm_name, tm_id, read_only=read_only)
424
425
 
425
426
  # Load TMX content
426
- loaded_count = self._load_tmx_into_db(filepath, src_lang, tgt_lang, tm_id, strip_variants=strip_variants)
427
+ loaded_count = self._load_tmx_into_db(filepath, src_lang, tgt_lang, tm_id,
428
+ strip_variants=strip_variants,
429
+ progress_callback=progress_callback)
427
430
 
428
431
  self.log(f"✓ Loaded {loaded_count} entries from {os.path.basename(filepath)}")
429
432
 
430
433
  return tm_id, loaded_count
431
434
 
432
435
  def _load_tmx_into_db(self, filepath: str, src_lang: str, tgt_lang: str, tm_id: str,
433
- strip_variants: bool = False) -> int:
436
+ strip_variants: bool = False, progress_callback=None) -> int:
434
437
  """
435
- Internal: Load TMX content into database
438
+ Internal: Load TMX content into database with chunked processing
436
439
 
437
440
  Args:
438
441
  filepath: Path to TMX file
@@ -440,12 +443,24 @@ class TMDatabase:
440
443
  tgt_lang: Target target language code
441
444
  tm_id: TM identifier
442
445
  strip_variants: If True, match base languages ignoring regional variants
446
+ progress_callback: Optional callback function(current, total, message) for progress updates
443
447
  """
444
448
  loaded_count = 0
449
+ chunk_size = 1000 # Process in chunks for responsiveness
450
+ chunk_buffer = []
445
451
 
446
452
  try:
453
+ # First pass: count total TUs for progress bar
454
+ if progress_callback:
455
+ progress_callback(0, 0, "Counting translation units...")
456
+
447
457
  tree = ET.parse(filepath)
448
458
  root = tree.getroot()
459
+ total_tus = len(root.findall('.//tu'))
460
+
461
+ if progress_callback:
462
+ progress_callback(0, total_tus, f"Processing 0 / {total_tus:,} entries...")
463
+
449
464
  xml_ns = "http://www.w3.org/XML/1998/namespace"
450
465
 
451
466
  # Normalize language codes
@@ -458,6 +473,7 @@ class TMDatabase:
458
473
  src_base = get_base_lang_code(src_lang_normalized)
459
474
  tgt_base = get_base_lang_code(tgt_lang_normalized)
460
475
 
476
+ processed = 0
461
477
  for tu in root.findall('.//tu'):
462
478
  src_text, tgt_text = None, None
463
479
 
@@ -488,14 +504,43 @@ class TMDatabase:
488
504
  tgt_text = text
489
505
 
490
506
  if src_text and tgt_text:
507
+ chunk_buffer.append((src_text, tgt_text))
508
+ loaded_count += 1
509
+
510
+ # Process chunk when buffer is full
511
+ if len(chunk_buffer) >= chunk_size:
512
+ for src, tgt in chunk_buffer:
513
+ self.db.add_translation_unit(
514
+ source=src,
515
+ target=tgt,
516
+ source_lang=src_lang_normalized,
517
+ target_lang=tgt_lang_normalized,
518
+ tm_id=tm_id
519
+ )
520
+ chunk_buffer.clear()
521
+
522
+ # Update progress
523
+ if progress_callback:
524
+ progress_callback(processed + 1, total_tus,
525
+ f"Processing {loaded_count:,} / {total_tus:,} entries...")
526
+
527
+ processed += 1
528
+
529
+ # Process remaining entries in buffer
530
+ if chunk_buffer:
531
+ for src, tgt in chunk_buffer:
491
532
  self.db.add_translation_unit(
492
- source=src_text,
493
- target=tgt_text,
533
+ source=src,
534
+ target=tgt,
494
535
  source_lang=src_lang_normalized,
495
536
  target_lang=tgt_lang_normalized,
496
537
  tm_id=tm_id
497
538
  )
498
- loaded_count += 1
539
+ chunk_buffer.clear()
540
+
541
+ # Final progress update
542
+ if progress_callback:
543
+ progress_callback(total_tus, total_tus, f"Completed: {loaded_count:,} entries imported")
499
544
 
500
545
  return loaded_count
501
546
  except Exception as e:
@@ -30,7 +30,7 @@ class UnifiedPromptLibrary:
30
30
  Initialize the Unified Prompt Library.
31
31
 
32
32
  Args:
33
- library_dir: Path to unified library directory (user_data/Prompt_Library/Library)
33
+ library_dir: Path to unified library directory (user_data/prompt_library)
34
34
  log_callback: Function to call for logging messages
35
35
  """
36
36
  self.library_dir = Path(library_dir) if library_dir else None
@@ -544,8 +544,8 @@ class UnifiedPromptManagerQt:
544
544
  self.log = parent_app.log if hasattr(parent_app, 'log') else print
545
545
 
546
546
  # Paths
547
- self.prompt_library_dir = self.user_data_path / "Prompt_Library"
548
- # Use Prompt_Library directly, not Prompt_Library/Library
547
+ self.prompt_library_dir = self.user_data_path / "prompt_library"
548
+ # Use prompt_library directly, not prompt_library/Library
549
549
  self.unified_library_dir = self.prompt_library_dir
550
550
 
551
551
  # Run migration if needed
@@ -579,7 +579,7 @@ class UnifiedPromptManagerQt:
579
579
  self._cached_document_markdown: Optional[str] = None # Cached markdown conversion of current document
580
580
 
581
581
  # Initialize Attachment Manager
582
- ai_assistant_dir = self.user_data_path / "AI_Assistant"
582
+ ai_assistant_dir = self.user_data_path / "ai_assistant"
583
583
  self.attachment_manager = AttachmentManager(
584
584
  base_dir=str(ai_assistant_dir),
585
585
  log_callback=self.log_message
@@ -640,7 +640,7 @@ class UnifiedPromptManagerQt:
640
640
 
641
641
  # Tab 1: Prompt Library
642
642
  library_tab = self._create_prompt_library_tab()
643
- self.sub_tabs.addTab(library_tab, "📚 Prompt Library")
643
+ self.sub_tabs.addTab(library_tab, "📚 Library")
644
644
 
645
645
  # Tab 2: AI Assistant (placeholder for now)
646
646
  assistant_tab = self._create_ai_assistant_tab()
@@ -694,7 +694,7 @@ class UnifiedPromptManagerQt:
694
694
  layout.setSpacing(5)
695
695
 
696
696
  # Title
697
- title = QLabel("🤖 Prompt Manager")
697
+ title = QLabel(" QuickMenu")
698
698
  title.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1976D2;")
699
699
  layout.addWidget(title, 0)
700
700
 
@@ -1546,7 +1546,7 @@ class UnifiedPromptManagerQt:
1546
1546
 
1547
1547
  def _create_active_config_panel(self) -> QGroupBox:
1548
1548
  """Create active prompt configuration panel"""
1549
- group = QGroupBox("Active Configuration")
1549
+ group = QGroupBox("Active Prompt")
1550
1550
  layout = QVBoxLayout()
1551
1551
 
1552
1552
  # Mode info (read-only, auto-selected)
@@ -1704,10 +1704,10 @@ class UnifiedPromptManagerQt:
1704
1704
  self.editor_quickmenu_label_input.setPlaceholderText("Label shown in QuickMenu")
1705
1705
  quickmenu_layout.addWidget(self.editor_quickmenu_label_input, 2)
1706
1706
 
1707
- self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in Grid right-click QuickMenu")
1707
+ self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in QuickMenu (in-app)")
1708
1708
  quickmenu_layout.addWidget(self.editor_quickmenu_in_grid_cb, 2)
1709
1709
 
1710
- self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in Supervertaler QuickMenu")
1710
+ self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in QuickMenu (global)")
1711
1711
  quickmenu_layout.addWidget(self.editor_quickmenu_in_quickmenu_cb, 1)
1712
1712
 
1713
1713
  layout.addLayout(quickmenu_layout)
@@ -1760,26 +1760,7 @@ class UnifiedPromptManagerQt:
1760
1760
  item.setFlags(item.flags() | Qt.ItemFlag.ItemIsDragEnabled)
1761
1761
  favorites_root.addChild(item)
1762
1762
 
1763
- # QuickMenu section (legacy kind name: quick_run)
1764
- quick_run_root = QTreeWidgetItem(["⚡ QuickMenu"])
1765
- # Special node: not draggable/droppable
1766
- quick_run_root.setData(0, Qt.ItemDataRole.UserRole, {'type': 'special', 'kind': 'quick_run'})
1767
- quick_run_root.setExpanded(False)
1768
- font = quick_run_root.font(0)
1769
- font.setBold(True)
1770
- quick_run_root.setFont(0, font)
1771
- self.tree_widget.addTopLevelItem(quick_run_root)
1772
-
1773
- quickmenu_items = self.library.get_quickmenu_prompts() if hasattr(self.library, 'get_quickmenu_prompts') else self.library.get_quick_run_prompts()
1774
- self.log_message(f"🔍 DEBUG: QuickMenu count: {len(quickmenu_items)}")
1775
- for path, label in quickmenu_items:
1776
- item = QTreeWidgetItem([label])
1777
- item.setData(0, Qt.ItemDataRole.UserRole, {'type': 'prompt', 'path': path})
1778
- # Quick Run entries are shortcuts, but allow dragging to move the actual prompt file.
1779
- item.setFlags(item.flags() | Qt.ItemFlag.ItemIsDragEnabled)
1780
- quick_run_root.addChild(item)
1781
-
1782
- # Library folders
1763
+ # Library folders (QuickMenu parent folder removed - folder hierarchy now defines menu structure)
1783
1764
  self.log_message(f"🔍 DEBUG: Building tree from {self.unified_library_dir}")
1784
1765
  self._build_tree_recursive(None, self.unified_library_dir, "")
1785
1766
 
@@ -3548,7 +3529,7 @@ Output complete ACTION."""
3548
3529
  """
3549
3530
  Save the markdown conversion of the current document.
3550
3531
 
3551
- Saves to: user_data_private/AI_Assistant/current_document/
3532
+ Saves to: user_data_private/ai_assistant/current_document/
3552
3533
 
3553
3534
  Args:
3554
3535
  original_path: Original document file path
@@ -3556,7 +3537,7 @@ Output complete ACTION."""
3556
3537
  """
3557
3538
  try:
3558
3539
  # Create directory for current document markdown
3559
- doc_dir = self.user_data_path / "AI_Assistant" / "current_document"
3540
+ doc_dir = self.user_data_path / "ai_assistant" / "current_document"
3560
3541
  doc_dir.mkdir(parents=True, exist_ok=True)
3561
3542
 
3562
3543
  # Create filename based on original