supervertaler 1.9.116__py3-none-any.whl → 1.9.172__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.

@@ -18,7 +18,7 @@ from PyQt6.QtWidgets import (
18
18
  QTextEdit, QPlainTextEdit, QSplitter, QGroupBox, QMessageBox, QFileDialog,
19
19
  QInputDialog, QLineEdit, QFrame, QMenu, QCheckBox, QSizePolicy, QScrollArea, QTabWidget,
20
20
  QListWidget, QListWidgetItem, QStyledItemDelegate, QStyleOptionViewItem, QApplication, QDialog,
21
- QAbstractItemView
21
+ QAbstractItemView, QTableWidget, QTableWidgetItem, QHeaderView
22
22
  )
23
23
  from PyQt6.QtCore import Qt, QSettings, pyqtSignal, QThread, QSize, QRect, QRectF
24
24
  from PyQt6.QtGui import QFont, QColor, QAction, QIcon, QPainter, QPen, QBrush, QPainterPath, QLinearGradient
@@ -31,6 +31,89 @@ from modules.ai_file_viewer_dialog import FileViewerDialog, FileRemoveConfirmDia
31
31
  from modules.ai_actions import AIActionSystem
32
32
 
33
33
 
34
+ class CheckmarkCheckBox(QCheckBox):
35
+ """Custom checkbox with green background and white checkmark when checked"""
36
+
37
+ def __init__(self, text="", parent=None):
38
+ super().__init__(text, parent)
39
+ self.setCheckable(True)
40
+ self.setEnabled(True)
41
+ self.setStyleSheet("""
42
+ QCheckBox {
43
+ font-size: 9pt;
44
+ spacing: 6px;
45
+ }
46
+ QCheckBox::indicator {
47
+ width: 16px;
48
+ height: 16px;
49
+ border: 2px solid #999;
50
+ border-radius: 3px;
51
+ background-color: white;
52
+ }
53
+ QCheckBox::indicator:checked {
54
+ background-color: #4CAF50;
55
+ border-color: #4CAF50;
56
+ }
57
+ QCheckBox::indicator:hover {
58
+ border-color: #666;
59
+ }
60
+ QCheckBox::indicator:checked:hover {
61
+ background-color: #45a049;
62
+ border-color: #45a049;
63
+ }
64
+ """)
65
+
66
+ def paintEvent(self, event):
67
+ """Override paint event to draw white checkmark when checked"""
68
+ super().paintEvent(event)
69
+
70
+ if self.isChecked():
71
+ # Get the indicator rectangle using QStyle
72
+ from PyQt6.QtWidgets import QStyleOptionButton
73
+ from PyQt6.QtCore import QPointF
74
+
75
+ opt = QStyleOptionButton()
76
+ self.initStyleOption(opt)
77
+ indicator_rect = self.style().subElementRect(
78
+ self.style().SubElement.SE_CheckBoxIndicator,
79
+ opt,
80
+ self
81
+ )
82
+
83
+ if indicator_rect.isValid():
84
+ # Draw white checkmark
85
+ painter = QPainter(self)
86
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
87
+ pen_width = max(2.0, min(indicator_rect.width(), indicator_rect.height()) * 0.12)
88
+ painter.setPen(QPen(QColor(255, 255, 255), pen_width, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin))
89
+ painter.setBrush(QColor(255, 255, 255))
90
+
91
+ # Draw checkmark (✓ shape) - coordinates relative to indicator
92
+ x = indicator_rect.x()
93
+ y = indicator_rect.y()
94
+ w = indicator_rect.width()
95
+ h = indicator_rect.height()
96
+
97
+ # Add padding (15% on all sides)
98
+ padding = min(w, h) * 0.15
99
+ x += padding
100
+ y += padding
101
+ w -= padding * 2
102
+ h -= padding * 2
103
+
104
+ # Checkmark path
105
+ check_x1 = x + w * 0.10
106
+ check_y1 = y + h * 0.50
107
+ check_x2 = x + w * 0.35
108
+ check_y2 = y + h * 0.70
109
+ check_x3 = x + w * 0.90
110
+ check_y3 = y + h * 0.25
111
+
112
+ # Draw two lines forming the checkmark
113
+ painter.drawLine(QPointF(check_x2, check_y2), QPointF(check_x3, check_y3))
114
+ painter.drawLine(QPointF(check_x1, check_y1), QPointF(check_x2, check_y2))
115
+
116
+
34
117
  class PromptLibraryTreeWidget(QTreeWidget):
35
118
  """Tree widget that supports drag-and-drop moves for prompt files."""
36
119
 
@@ -461,8 +544,8 @@ class UnifiedPromptManagerQt:
461
544
  self.log = parent_app.log if hasattr(parent_app, 'log') else print
462
545
 
463
546
  # Paths
464
- self.prompt_library_dir = self.user_data_path / "Prompt_Library"
465
- # 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
466
549
  self.unified_library_dir = self.prompt_library_dir
467
550
 
468
551
  # Run migration if needed
@@ -496,7 +579,7 @@ class UnifiedPromptManagerQt:
496
579
  self._cached_document_markdown: Optional[str] = None # Cached markdown conversion of current document
497
580
 
498
581
  # Initialize Attachment Manager
499
- ai_assistant_dir = self.user_data_path / "AI_Assistant"
582
+ ai_assistant_dir = self.user_data_path / "ai_assistant"
500
583
  self.attachment_manager = AttachmentManager(
501
584
  base_dir=str(ai_assistant_dir),
502
585
  log_callback=self.log_message
@@ -563,6 +646,10 @@ class UnifiedPromptManagerQt:
563
646
  assistant_tab = self._create_ai_assistant_tab()
564
647
  self.sub_tabs.addTab(assistant_tab, "✨ AI Assistant")
565
648
 
649
+ # Tab 3: Placeholders Reference
650
+ placeholders_tab = self._create_placeholders_tab()
651
+ self.sub_tabs.addTab(placeholders_tab, "📝 Placeholders")
652
+
566
653
  # Connect tab change signal to update context
567
654
  self.sub_tabs.currentChanged.connect(self._on_tab_changed)
568
655
 
@@ -607,7 +694,7 @@ class UnifiedPromptManagerQt:
607
694
  layout.setSpacing(5)
608
695
 
609
696
  # Title
610
- title = QLabel("🤖 Prompt Manager")
697
+ title = QLabel("📝 Prompt Manager")
611
698
  title.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1976D2;")
612
699
  layout.addWidget(title, 0)
613
700
 
@@ -764,6 +851,153 @@ class UnifiedPromptManagerQt:
764
851
 
765
852
  return tab
766
853
 
854
+ def _create_placeholders_tab(self) -> QWidget:
855
+ """Create the Placeholders Reference sub-tab"""
856
+ tab = QWidget()
857
+ layout = QVBoxLayout(tab)
858
+ layout.setContentsMargins(10, 10, 10, 10)
859
+ layout.setSpacing(5)
860
+
861
+ # Header (matches standard tool style: Superbench, AutoFingers, TMX Editor)
862
+ header = QLabel("📝 Available Placeholders")
863
+ header.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1976D2;")
864
+ layout.addWidget(header, 0)
865
+
866
+ # Description box (matches standard tool style)
867
+ description = QLabel(
868
+ "Use these placeholders in your prompts. They will be replaced with actual values when the prompt runs."
869
+ )
870
+ description.setWordWrap(True)
871
+ description.setStyleSheet("color: #666; padding: 5px; background-color: #E3F2FD; border-radius: 3px;")
872
+ layout.addWidget(description, 0)
873
+
874
+ # Horizontal splitter for table and tips
875
+ splitter = QSplitter(Qt.Orientation.Horizontal)
876
+ splitter.setHandleWidth(3)
877
+
878
+ # Left: Table with placeholders
879
+ table = QTableWidget()
880
+ table.setColumnCount(3)
881
+ table.setHorizontalHeaderLabels(["Placeholder", "Description", "Example"])
882
+ table.horizontalHeader().setStretchLastSection(True)
883
+ table.verticalHeader().setVisible(False)
884
+ table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
885
+ table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
886
+ table.setAlternatingRowColors(True)
887
+
888
+ # Placeholder data
889
+ placeholders = [
890
+ (
891
+ "{{SELECTION}}",
892
+ "Currently selected text in the grid (source or target cell)",
893
+ "If you select 'translation memory' in the grid, this will contain that text"
894
+ ),
895
+ (
896
+ "{{SOURCE_TEXT}}",
897
+ "Full text of the current source segment",
898
+ "The complete source sentence/paragraph from the active segment"
899
+ ),
900
+ (
901
+ "{{SOURCE_LANGUAGE}}",
902
+ "Project's source language",
903
+ "Dutch, English, German, French, etc."
904
+ ),
905
+ (
906
+ "{{TARGET_LANGUAGE}}",
907
+ "Project's target language",
908
+ "English, Spanish, Portuguese, etc."
909
+ ),
910
+ (
911
+ "{{SOURCE+TARGET_CONTEXT}}",
912
+ "Project segments with BOTH source and target text. Use for proofreading prompts.",
913
+ "[1] Source text\\n → Target text\\n\\n[2] Source text\\n → Target text\\n\\n..."
914
+ ),
915
+ (
916
+ "{{SOURCE_CONTEXT}}",
917
+ "Project segments with SOURCE ONLY. Use for translation/terminology questions.",
918
+ "[1] Source text\\n\\n[2] Source text\\n\\n[3] Source text\\n\\n..."
919
+ ),
920
+ (
921
+ "{{TARGET_CONTEXT}}",
922
+ "Project segments with TARGET ONLY. Use for consistency/style analysis.",
923
+ "[1] Target text\\n\\n[2] Target text\\n\\n[3] Target text\\n\\n..."
924
+ )
925
+ ]
926
+
927
+ table.setRowCount(len(placeholders))
928
+ for row, (placeholder, description, example) in enumerate(placeholders):
929
+ # Placeholder column (monospace, bold)
930
+ item_placeholder = QTableWidgetItem(placeholder)
931
+ item_placeholder.setFont(QFont("Courier New", 10, QFont.Weight.Bold))
932
+ table.setItem(row, 0, item_placeholder)
933
+
934
+ # Description column
935
+ item_desc = QTableWidgetItem(description)
936
+ item_desc.setToolTip(description)
937
+ table.setItem(row, 1, item_desc)
938
+
939
+ # Example column (monospace, italic)
940
+ item_example = QTableWidgetItem(example)
941
+ item_example.setFont(QFont("Courier New", 9))
942
+ item_example.setToolTip(example)
943
+ table.setItem(row, 2, item_example)
944
+
945
+ # Set column widths
946
+ table.setColumnWidth(0, 200)
947
+ table.setColumnWidth(1, 300)
948
+ header = table.horizontalHeader()
949
+ header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
950
+ header.setSectionResizeMode(1, QHeaderView.ResizeMode.Interactive)
951
+ header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
952
+
953
+ # Adjust row heights for readability
954
+ for row in range(table.rowCount()):
955
+ table.setRowHeight(row, 60)
956
+
957
+ splitter.addWidget(table)
958
+
959
+ # Right: Usage tips panel
960
+ tips_panel = QWidget()
961
+ tips_layout = QVBoxLayout(tips_panel)
962
+ tips_layout.setContentsMargins(10, 0, 0, 0)
963
+
964
+ tips_header = QLabel("💡 Usage Tips")
965
+ tips_header.setStyleSheet("font-weight: bold; font-size: 11pt; color: #2196F3; margin-bottom: 8px;")
966
+ tips_layout.addWidget(tips_header)
967
+
968
+ tips_intro = QLabel(
969
+ "Use these placeholders in your prompts. They will be replaced with actual values when the prompt runs."
970
+ )
971
+ tips_intro.setWordWrap(True)
972
+ tips_intro.setStyleSheet("color: #666; margin-bottom: 15px; font-style: italic;")
973
+ tips_layout.addWidget(tips_intro)
974
+
975
+ tips_text = QLabel(
976
+ "• Placeholders are case-sensitive (use UPPERCASE)\n\n"
977
+ "• Surround placeholders with double curly braces: {{ }}\n\n"
978
+ "• You can combine multiple placeholders in one prompt\n\n"
979
+ "• Use {{DOCUMENT_CONTEXT}} for context-aware translations\n\n"
980
+ "• Configure {{DOCUMENT_CONTEXT}} percentage in Settings → AI Settings"
981
+ )
982
+ tips_text.setWordWrap(True)
983
+ tips_text.setStyleSheet("color: #666; line-height: 1.6;")
984
+ tips_layout.addWidget(tips_text)
985
+
986
+ tips_layout.addStretch()
987
+
988
+ tips_panel.setMinimumWidth(280)
989
+ tips_panel.setMaximumWidth(400)
990
+ splitter.addWidget(tips_panel)
991
+
992
+ # Set splitter proportions (75% table, 25% tips)
993
+ splitter.setSizes([750, 250])
994
+ splitter.setStretchFactor(0, 1) # Table expands
995
+ splitter.setStretchFactor(1, 0) # Tips panel fixed-ish
996
+
997
+ layout.addWidget(splitter, 1) # 1 = stretch to fill all available space
998
+
999
+ return tab
1000
+
767
1001
  def _create_context_sidebar(self) -> QWidget:
768
1002
  """Create context sidebar showing available resources"""
769
1003
  panel = QWidget()
@@ -1312,7 +1546,7 @@ class UnifiedPromptManagerQt:
1312
1546
 
1313
1547
  def _create_active_config_panel(self) -> QGroupBox:
1314
1548
  """Create active prompt configuration panel"""
1315
- group = QGroupBox("Active Configuration")
1549
+ group = QGroupBox("Active Prompt")
1316
1550
  layout = QVBoxLayout()
1317
1551
 
1318
1552
  # Mode info (read-only, auto-selected)
@@ -1470,10 +1704,10 @@ class UnifiedPromptManagerQt:
1470
1704
  self.editor_quickmenu_label_input.setPlaceholderText("Label shown in QuickMenu")
1471
1705
  quickmenu_layout.addWidget(self.editor_quickmenu_label_input, 2)
1472
1706
 
1473
- self.editor_quickmenu_in_grid_cb = QCheckBox("Show in Grid right-click QuickMenu")
1707
+ self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in QuickMenu (in-app)")
1474
1708
  quickmenu_layout.addWidget(self.editor_quickmenu_in_grid_cb, 2)
1475
1709
 
1476
- self.editor_quickmenu_in_quickmenu_cb = QCheckBox("Show in QuickMenu")
1710
+ self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in QuickMenu (global)")
1477
1711
  quickmenu_layout.addWidget(self.editor_quickmenu_in_quickmenu_cb, 1)
1478
1712
 
1479
1713
  layout.addLayout(quickmenu_layout)
@@ -1526,26 +1760,7 @@ class UnifiedPromptManagerQt:
1526
1760
  item.setFlags(item.flags() | Qt.ItemFlag.ItemIsDragEnabled)
1527
1761
  favorites_root.addChild(item)
1528
1762
 
1529
- # QuickMenu section (legacy kind name: quick_run)
1530
- quick_run_root = QTreeWidgetItem(["⚡ QuickMenu"])
1531
- # Special node: not draggable/droppable
1532
- quick_run_root.setData(0, Qt.ItemDataRole.UserRole, {'type': 'special', 'kind': 'quick_run'})
1533
- quick_run_root.setExpanded(False)
1534
- font = quick_run_root.font(0)
1535
- font.setBold(True)
1536
- quick_run_root.setFont(0, font)
1537
- self.tree_widget.addTopLevelItem(quick_run_root)
1538
-
1539
- quickmenu_items = self.library.get_quickmenu_prompts() if hasattr(self.library, 'get_quickmenu_prompts') else self.library.get_quick_run_prompts()
1540
- self.log_message(f"🔍 DEBUG: QuickMenu count: {len(quickmenu_items)}")
1541
- for path, label in quickmenu_items:
1542
- item = QTreeWidgetItem([label])
1543
- item.setData(0, Qt.ItemDataRole.UserRole, {'type': 'prompt', 'path': path})
1544
- # Quick Run entries are shortcuts, but allow dragging to move the actual prompt file.
1545
- item.setFlags(item.flags() | Qt.ItemFlag.ItemIsDragEnabled)
1546
- quick_run_root.addChild(item)
1547
-
1548
- # Library folders
1763
+ # Library folders (QuickMenu parent folder removed - folder hierarchy now defines menu structure)
1549
1764
  self.log_message(f"🔍 DEBUG: Building tree from {self.unified_library_dir}")
1550
1765
  self._build_tree_recursive(None, self.unified_library_dir, "")
1551
1766
 
@@ -1950,7 +2165,7 @@ class UnifiedPromptManagerQt:
1950
2165
  indicators = []
1951
2166
  if prompt_data.get('favorite'):
1952
2167
  indicators.append("⭐")
1953
- if prompt_data.get('quickmenu_quickmenu', prompt_data.get('quick_run', False)):
2168
+ if prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False)):
1954
2169
  indicators.append("⚡")
1955
2170
  if prompt_data.get('quickmenu_grid', False):
1956
2171
  indicators.append("🖱️")
@@ -2018,7 +2233,7 @@ class UnifiedPromptManagerQt:
2018
2233
  action_fav.triggered.connect(lambda: self._toggle_favorite(path))
2019
2234
 
2020
2235
  # Toggle QuickMenu (legacy: quick_run)
2021
- if prompt_data.get('quickmenu_quickmenu', prompt_data.get('quick_run', False)):
2236
+ if prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False)):
2022
2237
  action_qr = menu.addAction("⚡ Remove from QuickMenu")
2023
2238
  else:
2024
2239
  action_qr = menu.addAction("⚡ Add to QuickMenu")
@@ -2070,7 +2285,7 @@ class UnifiedPromptManagerQt:
2070
2285
  if hasattr(self, 'editor_quickmenu_in_grid_cb'):
2071
2286
  self.editor_quickmenu_in_grid_cb.setChecked(bool(prompt_data.get('quickmenu_grid', False)))
2072
2287
  if hasattr(self, 'editor_quickmenu_in_quickmenu_cb'):
2073
- self.editor_quickmenu_in_quickmenu_cb.setChecked(bool(prompt_data.get('quickmenu_quickmenu', prompt_data.get('quick_run', False))))
2288
+ self.editor_quickmenu_in_quickmenu_cb.setChecked(bool(prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False))))
2074
2289
  self.editor_content.setPlainText(prompt_data.get('content', ''))
2075
2290
 
2076
2291
  # Store current path for saving
@@ -2083,119 +2298,127 @@ class UnifiedPromptManagerQt:
2083
2298
 
2084
2299
  def _save_current_prompt(self):
2085
2300
  """Save currently edited prompt"""
2086
- name = self.editor_name_input.text().strip()
2087
- description = self.editor_desc_input.text().strip()
2088
- content = self.editor_content.toPlainText().strip()
2089
-
2090
- # Name field now represents the complete filename with extension
2091
- # No stripping needed - user sees and edits the full filename
2092
-
2093
- quickmenu_label = ''
2094
- quickmenu_grid = False
2095
- quickmenu_quickmenu = False
2096
- if hasattr(self, 'editor_quickmenu_label_input'):
2097
- quickmenu_label = self.editor_quickmenu_label_input.text().strip()
2098
- if hasattr(self, 'editor_quickmenu_in_grid_cb'):
2099
- quickmenu_grid = bool(self.editor_quickmenu_in_grid_cb.isChecked())
2100
- if hasattr(self, 'editor_quickmenu_in_quickmenu_cb'):
2101
- quickmenu_quickmenu = bool(self.editor_quickmenu_in_quickmenu_cb.isChecked())
2102
-
2103
- if not name or not content:
2104
- QMessageBox.warning(self.main_widget, "Error", "Name and content are required")
2105
- return
2106
-
2107
- # Check if this is a new prompt or editing existing
2108
- if hasattr(self, 'editor_current_path') and self.editor_current_path:
2109
- path = self.editor_current_path
2110
-
2111
- # Handle external prompts (save back to external file)
2112
- if path.startswith("[EXTERNAL] "):
2113
- external_file_path = path[11:] # Remove "[EXTERNAL] " prefix
2114
- self._save_external_prompt(external_file_path, name, description, content)
2115
- return
2301
+ try:
2302
+ name = self.editor_name_input.text().strip()
2303
+ description = self.editor_desc_input.text().strip()
2304
+ content = self.editor_content.toPlainText().strip()
2116
2305
 
2117
- # Editing existing library prompt
2118
- if path not in self.library.prompts:
2119
- QMessageBox.warning(self.main_widget, "Error", "Prompt no longer exists")
2306
+ # Name field now represents the complete filename with extension
2307
+ # No stripping needed - user sees and edits the full filename
2308
+
2309
+ quickmenu_label = ''
2310
+ quickmenu_grid = False
2311
+ sv_quickmenu = False
2312
+ if hasattr(self, 'editor_quickmenu_label_input'):
2313
+ quickmenu_label = self.editor_quickmenu_label_input.text().strip()
2314
+ if hasattr(self, 'editor_quickmenu_in_grid_cb'):
2315
+ quickmenu_grid = bool(self.editor_quickmenu_in_grid_cb.isChecked())
2316
+ if hasattr(self, 'editor_quickmenu_in_quickmenu_cb'):
2317
+ sv_quickmenu = bool(self.editor_quickmenu_in_quickmenu_cb.isChecked())
2318
+
2319
+ if not name or not content:
2320
+ QMessageBox.warning(self.main_widget, "Error", "Name and content are required")
2120
2321
  return
2121
2322
 
2122
- prompt_data = self.library.prompts[path].copy()
2123
- old_filename = Path(path).name
2124
-
2125
- # Extract name without extension for metadata
2126
- name_without_ext = Path(name).stem
2127
-
2128
- prompt_data['name'] = name_without_ext
2129
- prompt_data['description'] = description
2130
- prompt_data['content'] = content
2131
- prompt_data['quickmenu_label'] = quickmenu_label or name_without_ext
2132
- prompt_data['quickmenu_grid'] = quickmenu_grid
2133
- prompt_data['quickmenu_quickmenu'] = quickmenu_quickmenu
2134
- # Keep legacy field in sync
2135
- prompt_data['quick_run'] = quickmenu_quickmenu
2136
-
2137
- # Check if filename changed - need to rename file
2138
- if old_filename != name:
2139
- from pathlib import Path
2140
- old_path = Path(path)
2141
- folder = str(old_path.parent) if old_path.parent != Path('.') else ''
2142
- new_path = f"{folder}/{name}" if folder else name
2323
+ # Check if this is a new prompt or editing existing
2324
+ if hasattr(self, 'editor_current_path') and self.editor_current_path:
2325
+ path = self.editor_current_path
2143
2326
 
2144
- # Delete old file and save to new location
2145
- if self.library.delete_prompt(path):
2146
- if self.library.save_prompt(new_path, prompt_data):
2147
- self.library.load_all_prompts()
2148
- self._refresh_tree()
2149
- self._select_and_reveal_prompt(new_path)
2150
- self.editor_current_path = new_path # Update to new path
2151
- QMessageBox.information(self.main_widget, "Saved", f"Prompt renamed to '{name}' successfully!")
2152
- self.log_message(f" Renamed prompt: {old_filename} → {name}")
2327
+ # Handle external prompts (save back to external file)
2328
+ if path.startswith("[EXTERNAL] "):
2329
+ external_file_path = path[11:] # Remove "[EXTERNAL] " prefix
2330
+ self._save_external_prompt(external_file_path, name, description, content)
2331
+ return
2332
+
2333
+ # Editing existing library prompt
2334
+ if path not in self.library.prompts:
2335
+ QMessageBox.warning(self.main_widget, "Error", "Prompt no longer exists")
2336
+ return
2337
+
2338
+ prompt_data = self.library.prompts[path].copy()
2339
+ old_filename = Path(path).name
2340
+
2341
+ # Extract name without extension for metadata
2342
+ name_without_ext = Path(name).stem
2343
+
2344
+ prompt_data['name'] = name_without_ext
2345
+ prompt_data['description'] = description
2346
+ prompt_data['content'] = content
2347
+ prompt_data['quickmenu_label'] = quickmenu_label or name_without_ext
2348
+ prompt_data['quickmenu_grid'] = quickmenu_grid
2349
+ prompt_data['sv_quickmenu'] = sv_quickmenu
2350
+ # Keep legacy field in sync
2351
+ prompt_data['quick_run'] = sv_quickmenu
2352
+
2353
+ # Check if filename changed - need to rename file
2354
+ if old_filename != name:
2355
+ old_path = Path(path)
2356
+ folder = str(old_path.parent) if old_path.parent != Path('.') else ''
2357
+ new_path = f"{folder}/{name}" if folder else name
2358
+
2359
+ # Delete old file and save to new location
2360
+ if self.library.delete_prompt(path):
2361
+ if self.library.save_prompt(new_path, prompt_data):
2362
+ self.library.load_all_prompts()
2363
+ self._refresh_tree()
2364
+ self._select_and_reveal_prompt(new_path)
2365
+ self.editor_current_path = new_path # Update to new path
2366
+ QMessageBox.information(self.main_widget, "Saved", f"Prompt renamed to '{name}' successfully!")
2367
+ self.log_message(f"✓ Renamed prompt: {old_filename} → {name}")
2368
+ else:
2369
+ QMessageBox.warning(self.main_widget, "Error", "Failed to rename prompt")
2153
2370
  else:
2154
- QMessageBox.warning(self.main_widget, "Error", "Failed to rename prompt")
2371
+ QMessageBox.warning(self.main_widget, "Error", "Failed to delete old prompt file")
2155
2372
  else:
2156
- QMessageBox.warning(self.main_widget, "Error", "Failed to delete old prompt file")
2373
+ # Name unchanged, just update in place
2374
+ if self.library.save_prompt(path, prompt_data):
2375
+ QMessageBox.information(self.main_widget, "Saved", "Prompt updated successfully!")
2376
+ self._refresh_tree()
2377
+ else:
2378
+ QMessageBox.warning(self.main_widget, "Error", "Failed to save prompt")
2157
2379
  else:
2158
- # Name unchanged, just update in place
2159
- if self.library.save_prompt(path, prompt_data):
2160
- QMessageBox.information(self.main_widget, "Saved", "Prompt updated successfully!")
2380
+ # Creating new prompt
2381
+ folder = getattr(self, 'editor_target_folder', 'Project Prompts')
2382
+
2383
+ # Create new prompt data
2384
+ from datetime import datetime
2385
+ prompt_data = {
2386
+ 'name': name,
2387
+ 'description': description,
2388
+ 'content': content,
2389
+ 'domain': '',
2390
+ 'version': '1.0',
2391
+ 'task_type': 'Translation',
2392
+ 'favorite': False,
2393
+ # QuickMenu
2394
+ 'quickmenu_label': quickmenu_label or name,
2395
+ 'quickmenu_grid': quickmenu_grid,
2396
+ 'sv_quickmenu': sv_quickmenu,
2397
+ # Legacy
2398
+ 'quick_run': sv_quickmenu,
2399
+ 'folder': folder,
2400
+ 'tags': [],
2401
+ 'created': datetime.now().strftime('%Y-%m-%d'),
2402
+ 'modified': datetime.now().strftime('%Y-%m-%d')
2403
+ }
2404
+
2405
+ # Create the prompt file (save_prompt creates new file if it doesn't exist)
2406
+ relative_path = f"{folder}/{name}.svprompt"
2407
+ if self.library.save_prompt(relative_path, prompt_data):
2408
+ QMessageBox.information(self.main_widget, "Created", f"Prompt '{name}' created successfully!")
2409
+ self.library.load_all_prompts() # Reload to get new prompt in memory
2161
2410
  self._refresh_tree()
2411
+ self.editor_current_path = relative_path # Now editing this prompt
2162
2412
  else:
2163
- QMessageBox.warning(self.main_widget, "Error", "Failed to save prompt")
2164
- else:
2165
- # Creating new prompt
2166
- folder = getattr(self, 'editor_target_folder', 'Project Prompts')
2167
-
2168
- # Create new prompt data
2169
- from datetime import datetime
2170
- prompt_data = {
2171
- 'name': name,
2172
- 'description': description,
2173
- 'content': content,
2174
- 'domain': '',
2175
- 'version': '1.0',
2176
- 'task_type': 'Translation',
2177
- 'favorite': False,
2178
- # QuickMenu
2179
- 'quickmenu_label': quickmenu_label or name,
2180
- 'quickmenu_grid': quickmenu_grid,
2181
- 'quickmenu_quickmenu': quickmenu_quickmenu,
2182
- # Legacy
2183
- 'quick_run': quickmenu_quickmenu,
2184
- 'folder': folder,
2185
- 'tags': [],
2186
- 'created': datetime.now().strftime('%Y-%m-%d'),
2187
- 'modified': datetime.now().strftime('%Y-%m-%d')
2188
- }
2189
-
2190
- # Create the prompt file (save_prompt creates new file if it doesn't exist)
2191
- relative_path = f"{folder}/{name}.svprompt"
2192
- if self.library.save_prompt(relative_path, prompt_data):
2193
- QMessageBox.information(self.main_widget, "Created", f"Prompt '{name}' created successfully!")
2194
- self.library.load_all_prompts() # Reload to get new prompt in memory
2195
- self._refresh_tree()
2196
- self.editor_current_path = relative_path # Now editing this prompt
2197
- else:
2198
- QMessageBox.warning(self.main_widget, "Error", "Failed to create prompt")
2413
+ QMessageBox.warning(self.main_widget, "Error", "Failed to create prompt")
2414
+
2415
+ except Exception as e:
2416
+ import traceback
2417
+ error_msg = f"Prompt save error: {str(e)}\n{traceback.format_exc()}"
2418
+ print(f"[ERROR] {error_msg}")
2419
+ self.log_message(f"❌ Prompt save error: {str(e)}")
2420
+ QMessageBox.critical(self.main_widget, "Save Error", f"Failed to save prompt:\n\n{str(e)}")
2421
+ return
2199
2422
 
2200
2423
  def _save_external_prompt(self, file_path: str, name: str, description: str, content: str):
2201
2424
  """Save changes to an external prompt file"""
@@ -2421,7 +2644,7 @@ class UnifiedPromptManagerQt:
2421
2644
  'favorite': False,
2422
2645
  'quickmenu_label': name_without_ext,
2423
2646
  'quickmenu_grid': False,
2424
- 'quickmenu_quickmenu': False,
2647
+ 'sv_quickmenu': False,
2425
2648
  'quick_run': False,
2426
2649
  'folder': folder_path,
2427
2650
  'tags': [],
@@ -2437,6 +2660,7 @@ class UnifiedPromptManagerQt:
2437
2660
  self._refresh_tree()
2438
2661
  self._select_and_reveal_prompt(relative_path)
2439
2662
  self._load_prompt_in_editor(relative_path)
2663
+ self.btn_save_prompt.setEnabled(True) # Ensure Save button is enabled for new prompt
2440
2664
  self.log_message(f"✓ Created new prompt '{name}' in folder: {folder_path}")
2441
2665
  else:
2442
2666
  QMessageBox.warning(self.main_widget, "Error", "Failed to create prompt")
@@ -2469,7 +2693,7 @@ class UnifiedPromptManagerQt:
2469
2693
  src_data['favorite'] = False
2470
2694
  src_data['quick_run'] = False
2471
2695
  src_data['quickmenu_grid'] = False
2472
- src_data['quickmenu_quickmenu'] = False
2696
+ src_data['sv_quickmenu'] = False
2473
2697
  src_data['folder'] = folder
2474
2698
  src_data['created'] = datetime.now().strftime('%Y-%m-%d')
2475
2699
  src_data['modified'] = datetime.now().strftime('%Y-%m-%d')
@@ -3305,7 +3529,7 @@ Output complete ACTION."""
3305
3529
  """
3306
3530
  Save the markdown conversion of the current document.
3307
3531
 
3308
- Saves to: user_data_private/AI_Assistant/current_document/
3532
+ Saves to: user_data_private/ai_assistant/current_document/
3309
3533
 
3310
3534
  Args:
3311
3535
  original_path: Original document file path
@@ -3313,7 +3537,7 @@ Output complete ACTION."""
3313
3537
  """
3314
3538
  try:
3315
3539
  # Create directory for current document markdown
3316
- doc_dir = self.user_data_path / "AI_Assistant" / "current_document"
3540
+ doc_dir = self.user_data_path / "ai_assistant" / "current_document"
3317
3541
  doc_dir.mkdir(parents=True, exist_ok=True)
3318
3542
 
3319
3543
  # Create filename based on original