supervertaler 1.9.114__py3-none-any.whl → 1.9.128__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 +333 -99
- modules/shortcut_manager.py +14 -1
- modules/termview_widget.py +9 -2
- modules/unified_prompt_library.py +12 -9
- modules/unified_prompt_manager_qt.py +345 -112
- {supervertaler-1.9.114.dist-info → supervertaler-1.9.128.dist-info}/METADATA +43 -8
- {supervertaler-1.9.114.dist-info → supervertaler-1.9.128.dist-info}/RECORD +11 -11
- {supervertaler-1.9.114.dist-info → supervertaler-1.9.128.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.114.dist-info → supervertaler-1.9.128.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.114.dist-info → supervertaler-1.9.128.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.114.dist-info → supervertaler-1.9.128.dist-info}/top_level.txt +0 -0
modules/shortcut_manager.py
CHANGED
|
@@ -18,9 +18,15 @@ class ShortcutManager:
|
|
|
18
18
|
"file_new": {
|
|
19
19
|
"category": "File",
|
|
20
20
|
"description": "New Project",
|
|
21
|
-
"default": "
|
|
21
|
+
"default": "",
|
|
22
22
|
"action": "new_project"
|
|
23
23
|
},
|
|
24
|
+
"editor_focus_notes": {
|
|
25
|
+
"category": "Edit",
|
|
26
|
+
"description": "Focus Segment Note Tab (Ctrl+N)",
|
|
27
|
+
"default": "Ctrl+N",
|
|
28
|
+
"action": "focus_segment_notes"
|
|
29
|
+
},
|
|
24
30
|
"file_open": {
|
|
25
31
|
"category": "File",
|
|
26
32
|
"description": "Open Project",
|
|
@@ -542,6 +548,13 @@ class ShortcutManager:
|
|
|
542
548
|
"action": "copy_source_to_target",
|
|
543
549
|
"context": "grid_editor"
|
|
544
550
|
},
|
|
551
|
+
"editor_add_to_dictionary": {
|
|
552
|
+
"category": "Editor",
|
|
553
|
+
"description": "Add word at cursor to custom dictionary",
|
|
554
|
+
"default": "Alt+D",
|
|
555
|
+
"action": "add_word_to_dictionary",
|
|
556
|
+
"context": "grid_editor"
|
|
557
|
+
},
|
|
545
558
|
|
|
546
559
|
# Filter Operations
|
|
547
560
|
"filter_selected_text": {
|
modules/termview_widget.py
CHANGED
|
@@ -941,13 +941,20 @@ class TermviewWidget(QWidget):
|
|
|
941
941
|
for quote_char in '\"\'\u201C\u201D\u201E\u00AB\u00BB\u2018\u2019\u201A\u2039\u203A':
|
|
942
942
|
normalized_text = normalized_text.replace(quote_char, ' ')
|
|
943
943
|
|
|
944
|
+
# CRITICAL FIX v1.9.118: Strip punctuation from glossary term before matching
|
|
945
|
+
# This allows entries like "...problemen." (with period) to match source text
|
|
946
|
+
# where tokenization strips the period during word splitting
|
|
947
|
+
# Comprehensive set of quote and punctuation characters to strip
|
|
948
|
+
PUNCT_CHARS = '.,;:!?\"\'\u201C\u201D\u201E\u00AB\u00BB\u2018\u2019\u201A\u2039\u203A'
|
|
949
|
+
normalized_term = source_lower.rstrip(PUNCT_CHARS).lstrip(PUNCT_CHARS)
|
|
950
|
+
|
|
944
951
|
# Use word boundaries to match complete words/phrases only
|
|
945
952
|
if ' ' in source_term:
|
|
946
953
|
# Multi-word term - must exist as exact phrase
|
|
947
|
-
pattern = r'\b' + re.escape(
|
|
954
|
+
pattern = r'\b' + re.escape(normalized_term) + r'\b'
|
|
948
955
|
else:
|
|
949
956
|
# Single word
|
|
950
|
-
pattern = r'\b' + re.escape(
|
|
957
|
+
pattern = r'\b' + re.escape(normalized_term) + r'\b'
|
|
951
958
|
|
|
952
959
|
# Try matching on normalized text first, then original
|
|
953
960
|
if not re.search(pattern, normalized_text) and not re.search(pattern, text_lower):
|
|
@@ -171,11 +171,14 @@ class UnifiedPromptLibrary:
|
|
|
171
171
|
# Backward compatibility: quick_run is the legacy field; internally we
|
|
172
172
|
# treat it as the "QuickMenu (future app menu)" flag.
|
|
173
173
|
prompt_data.setdefault('quick_run', False)
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
# Support legacy quickmenu_quickmenu field (rename to sv_quickmenu)
|
|
175
|
+
if 'quickmenu_quickmenu' in prompt_data:
|
|
176
|
+
prompt_data['sv_quickmenu'] = prompt_data['quickmenu_quickmenu']
|
|
177
|
+
prompt_data['sv_quickmenu'] = bool(
|
|
178
|
+
prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False))
|
|
176
179
|
)
|
|
177
180
|
# Keep legacy field in sync so older code/versions still behave.
|
|
178
|
-
prompt_data['quick_run'] = bool(prompt_data['
|
|
181
|
+
prompt_data['quick_run'] = bool(prompt_data['sv_quickmenu'])
|
|
179
182
|
|
|
180
183
|
# New QuickMenu fields
|
|
181
184
|
prompt_data.setdefault('quickmenu_grid', False)
|
|
@@ -270,7 +273,7 @@ class UnifiedPromptLibrary:
|
|
|
270
273
|
'name', 'description', 'domain', 'version', 'task_type',
|
|
271
274
|
'favorite',
|
|
272
275
|
# QuickMenu
|
|
273
|
-
'quickmenu_label', 'quickmenu_grid', '
|
|
276
|
+
'quickmenu_label', 'quickmenu_grid', 'sv_quickmenu',
|
|
274
277
|
# Legacy (kept for backward compatibility)
|
|
275
278
|
'quick_run',
|
|
276
279
|
'folder', 'tags',
|
|
@@ -309,8 +312,8 @@ class UnifiedPromptLibrary:
|
|
|
309
312
|
prompt_data['_relative_path'] = relative_path
|
|
310
313
|
|
|
311
314
|
# Keep legacy field in sync
|
|
312
|
-
if '
|
|
313
|
-
prompt_data['quick_run'] = bool(prompt_data.get('
|
|
315
|
+
if 'sv_quickmenu' in prompt_data:
|
|
316
|
+
prompt_data['quick_run'] = bool(prompt_data.get('sv_quickmenu', False))
|
|
314
317
|
self.prompts[relative_path] = prompt_data
|
|
315
318
|
|
|
316
319
|
self.log(f"✓ Saved prompt: {prompt_data.get('name', relative_path)}")
|
|
@@ -456,8 +459,8 @@ class UnifiedPromptLibrary:
|
|
|
456
459
|
return False
|
|
457
460
|
|
|
458
461
|
prompt_data = self.prompts[relative_path]
|
|
459
|
-
new_value = not bool(prompt_data.get('
|
|
460
|
-
prompt_data['
|
|
462
|
+
new_value = not bool(prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False)))
|
|
463
|
+
prompt_data['sv_quickmenu'] = new_value
|
|
461
464
|
prompt_data['quick_run'] = new_value # keep legacy in sync
|
|
462
465
|
prompt_data['modified'] = datetime.now().strftime("%Y-%m-%d")
|
|
463
466
|
|
|
@@ -493,7 +496,7 @@ class UnifiedPromptLibrary:
|
|
|
493
496
|
"""Update cached QuickMenu (future app menu) list (legacy name: quick_run)."""
|
|
494
497
|
self._quick_run = []
|
|
495
498
|
for path, data in self.prompts.items():
|
|
496
|
-
is_enabled = bool(data.get('
|
|
499
|
+
is_enabled = bool(data.get('sv_quickmenu', data.get('quick_run', False)))
|
|
497
500
|
if not is_enabled:
|
|
498
501
|
continue
|
|
499
502
|
label = (data.get('quickmenu_label') or data.get('name') or Path(path).stem).strip()
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -764,6 +851,143 @@ 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
|
+
"{{DOCUMENT_CONTEXT}}",
|
|
912
|
+
"Formatted list of project segments (configurable % in Settings → AI Settings)",
|
|
913
|
+
"[1] Source text\\n → Target text\\n\\n[2] Source text\\n → Target text\\n\\n..."
|
|
914
|
+
)
|
|
915
|
+
]
|
|
916
|
+
|
|
917
|
+
table.setRowCount(len(placeholders))
|
|
918
|
+
for row, (placeholder, description, example) in enumerate(placeholders):
|
|
919
|
+
# Placeholder column (monospace, bold)
|
|
920
|
+
item_placeholder = QTableWidgetItem(placeholder)
|
|
921
|
+
item_placeholder.setFont(QFont("Courier New", 10, QFont.Weight.Bold))
|
|
922
|
+
table.setItem(row, 0, item_placeholder)
|
|
923
|
+
|
|
924
|
+
# Description column
|
|
925
|
+
item_desc = QTableWidgetItem(description)
|
|
926
|
+
item_desc.setToolTip(description)
|
|
927
|
+
table.setItem(row, 1, item_desc)
|
|
928
|
+
|
|
929
|
+
# Example column (monospace, italic)
|
|
930
|
+
item_example = QTableWidgetItem(example)
|
|
931
|
+
item_example.setFont(QFont("Courier New", 9))
|
|
932
|
+
item_example.setToolTip(example)
|
|
933
|
+
table.setItem(row, 2, item_example)
|
|
934
|
+
|
|
935
|
+
# Set column widths
|
|
936
|
+
table.setColumnWidth(0, 200)
|
|
937
|
+
table.setColumnWidth(1, 300)
|
|
938
|
+
header = table.horizontalHeader()
|
|
939
|
+
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
|
|
940
|
+
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Interactive)
|
|
941
|
+
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
|
|
942
|
+
|
|
943
|
+
# Adjust row heights for readability
|
|
944
|
+
for row in range(table.rowCount()):
|
|
945
|
+
table.setRowHeight(row, 60)
|
|
946
|
+
|
|
947
|
+
splitter.addWidget(table)
|
|
948
|
+
|
|
949
|
+
# Right: Usage tips panel
|
|
950
|
+
tips_panel = QWidget()
|
|
951
|
+
tips_layout = QVBoxLayout(tips_panel)
|
|
952
|
+
tips_layout.setContentsMargins(10, 0, 0, 0)
|
|
953
|
+
|
|
954
|
+
tips_header = QLabel("💡 Usage Tips")
|
|
955
|
+
tips_header.setStyleSheet("font-weight: bold; font-size: 11pt; color: #2196F3; margin-bottom: 8px;")
|
|
956
|
+
tips_layout.addWidget(tips_header)
|
|
957
|
+
|
|
958
|
+
tips_intro = QLabel(
|
|
959
|
+
"Use these placeholders in your prompts. They will be replaced with actual values when the prompt runs."
|
|
960
|
+
)
|
|
961
|
+
tips_intro.setWordWrap(True)
|
|
962
|
+
tips_intro.setStyleSheet("color: #666; margin-bottom: 15px; font-style: italic;")
|
|
963
|
+
tips_layout.addWidget(tips_intro)
|
|
964
|
+
|
|
965
|
+
tips_text = QLabel(
|
|
966
|
+
"• Placeholders are case-sensitive (use UPPERCASE)\n\n"
|
|
967
|
+
"• Surround placeholders with double curly braces: {{ }}\n\n"
|
|
968
|
+
"• You can combine multiple placeholders in one prompt\n\n"
|
|
969
|
+
"• Use {{DOCUMENT_CONTEXT}} for context-aware translations\n\n"
|
|
970
|
+
"• Configure {{DOCUMENT_CONTEXT}} percentage in Settings → AI Settings"
|
|
971
|
+
)
|
|
972
|
+
tips_text.setWordWrap(True)
|
|
973
|
+
tips_text.setStyleSheet("color: #666; line-height: 1.6;")
|
|
974
|
+
tips_layout.addWidget(tips_text)
|
|
975
|
+
|
|
976
|
+
tips_layout.addStretch()
|
|
977
|
+
|
|
978
|
+
tips_panel.setMinimumWidth(280)
|
|
979
|
+
tips_panel.setMaximumWidth(400)
|
|
980
|
+
splitter.addWidget(tips_panel)
|
|
981
|
+
|
|
982
|
+
# Set splitter proportions (75% table, 25% tips)
|
|
983
|
+
splitter.setSizes([750, 250])
|
|
984
|
+
splitter.setStretchFactor(0, 1) # Table expands
|
|
985
|
+
splitter.setStretchFactor(1, 0) # Tips panel fixed-ish
|
|
986
|
+
|
|
987
|
+
layout.addWidget(splitter, 1) # 1 = stretch to fill all available space
|
|
988
|
+
|
|
989
|
+
return tab
|
|
990
|
+
|
|
767
991
|
def _create_context_sidebar(self) -> QWidget:
|
|
768
992
|
"""Create context sidebar showing available resources"""
|
|
769
993
|
panel = QWidget()
|
|
@@ -1470,10 +1694,10 @@ class UnifiedPromptManagerQt:
|
|
|
1470
1694
|
self.editor_quickmenu_label_input.setPlaceholderText("Label shown in QuickMenu")
|
|
1471
1695
|
quickmenu_layout.addWidget(self.editor_quickmenu_label_input, 2)
|
|
1472
1696
|
|
|
1473
|
-
self.editor_quickmenu_in_grid_cb =
|
|
1697
|
+
self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in Grid right-click QuickMenu")
|
|
1474
1698
|
quickmenu_layout.addWidget(self.editor_quickmenu_in_grid_cb, 2)
|
|
1475
1699
|
|
|
1476
|
-
self.editor_quickmenu_in_quickmenu_cb =
|
|
1700
|
+
self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in Supervertaler QuickMenu")
|
|
1477
1701
|
quickmenu_layout.addWidget(self.editor_quickmenu_in_quickmenu_cb, 1)
|
|
1478
1702
|
|
|
1479
1703
|
layout.addLayout(quickmenu_layout)
|
|
@@ -1950,7 +2174,7 @@ class UnifiedPromptManagerQt:
|
|
|
1950
2174
|
indicators = []
|
|
1951
2175
|
if prompt_data.get('favorite'):
|
|
1952
2176
|
indicators.append("⭐")
|
|
1953
|
-
if prompt_data.get('
|
|
2177
|
+
if prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False)):
|
|
1954
2178
|
indicators.append("⚡")
|
|
1955
2179
|
if prompt_data.get('quickmenu_grid', False):
|
|
1956
2180
|
indicators.append("🖱️")
|
|
@@ -2018,7 +2242,7 @@ class UnifiedPromptManagerQt:
|
|
|
2018
2242
|
action_fav.triggered.connect(lambda: self._toggle_favorite(path))
|
|
2019
2243
|
|
|
2020
2244
|
# Toggle QuickMenu (legacy: quick_run)
|
|
2021
|
-
if prompt_data.get('
|
|
2245
|
+
if prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False)):
|
|
2022
2246
|
action_qr = menu.addAction("⚡ Remove from QuickMenu")
|
|
2023
2247
|
else:
|
|
2024
2248
|
action_qr = menu.addAction("⚡ Add to QuickMenu")
|
|
@@ -2070,7 +2294,7 @@ class UnifiedPromptManagerQt:
|
|
|
2070
2294
|
if hasattr(self, 'editor_quickmenu_in_grid_cb'):
|
|
2071
2295
|
self.editor_quickmenu_in_grid_cb.setChecked(bool(prompt_data.get('quickmenu_grid', False)))
|
|
2072
2296
|
if hasattr(self, 'editor_quickmenu_in_quickmenu_cb'):
|
|
2073
|
-
self.editor_quickmenu_in_quickmenu_cb.setChecked(bool(prompt_data.get('
|
|
2297
|
+
self.editor_quickmenu_in_quickmenu_cb.setChecked(bool(prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False))))
|
|
2074
2298
|
self.editor_content.setPlainText(prompt_data.get('content', ''))
|
|
2075
2299
|
|
|
2076
2300
|
# Store current path for saving
|
|
@@ -2083,119 +2307,127 @@ class UnifiedPromptManagerQt:
|
|
|
2083
2307
|
|
|
2084
2308
|
def _save_current_prompt(self):
|
|
2085
2309
|
"""Save currently edited prompt"""
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
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
|
|
2310
|
+
try:
|
|
2311
|
+
name = self.editor_name_input.text().strip()
|
|
2312
|
+
description = self.editor_desc_input.text().strip()
|
|
2313
|
+
content = self.editor_content.toPlainText().strip()
|
|
2116
2314
|
|
|
2117
|
-
#
|
|
2118
|
-
|
|
2119
|
-
|
|
2315
|
+
# Name field now represents the complete filename with extension
|
|
2316
|
+
# No stripping needed - user sees and edits the full filename
|
|
2317
|
+
|
|
2318
|
+
quickmenu_label = ''
|
|
2319
|
+
quickmenu_grid = False
|
|
2320
|
+
sv_quickmenu = False
|
|
2321
|
+
if hasattr(self, 'editor_quickmenu_label_input'):
|
|
2322
|
+
quickmenu_label = self.editor_quickmenu_label_input.text().strip()
|
|
2323
|
+
if hasattr(self, 'editor_quickmenu_in_grid_cb'):
|
|
2324
|
+
quickmenu_grid = bool(self.editor_quickmenu_in_grid_cb.isChecked())
|
|
2325
|
+
if hasattr(self, 'editor_quickmenu_in_quickmenu_cb'):
|
|
2326
|
+
sv_quickmenu = bool(self.editor_quickmenu_in_quickmenu_cb.isChecked())
|
|
2327
|
+
|
|
2328
|
+
if not name or not content:
|
|
2329
|
+
QMessageBox.warning(self.main_widget, "Error", "Name and content are required")
|
|
2120
2330
|
return
|
|
2121
2331
|
|
|
2122
|
-
|
|
2123
|
-
|
|
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
|
|
2332
|
+
# Check if this is a new prompt or editing existing
|
|
2333
|
+
if hasattr(self, 'editor_current_path') and self.editor_current_path:
|
|
2334
|
+
path = self.editor_current_path
|
|
2143
2335
|
|
|
2144
|
-
#
|
|
2145
|
-
if
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2336
|
+
# Handle external prompts (save back to external file)
|
|
2337
|
+
if path.startswith("[EXTERNAL] "):
|
|
2338
|
+
external_file_path = path[11:] # Remove "[EXTERNAL] " prefix
|
|
2339
|
+
self._save_external_prompt(external_file_path, name, description, content)
|
|
2340
|
+
return
|
|
2341
|
+
|
|
2342
|
+
# Editing existing library prompt
|
|
2343
|
+
if path not in self.library.prompts:
|
|
2344
|
+
QMessageBox.warning(self.main_widget, "Error", "Prompt no longer exists")
|
|
2345
|
+
return
|
|
2346
|
+
|
|
2347
|
+
prompt_data = self.library.prompts[path].copy()
|
|
2348
|
+
old_filename = Path(path).name
|
|
2349
|
+
|
|
2350
|
+
# Extract name without extension for metadata
|
|
2351
|
+
name_without_ext = Path(name).stem
|
|
2352
|
+
|
|
2353
|
+
prompt_data['name'] = name_without_ext
|
|
2354
|
+
prompt_data['description'] = description
|
|
2355
|
+
prompt_data['content'] = content
|
|
2356
|
+
prompt_data['quickmenu_label'] = quickmenu_label or name_without_ext
|
|
2357
|
+
prompt_data['quickmenu_grid'] = quickmenu_grid
|
|
2358
|
+
prompt_data['sv_quickmenu'] = sv_quickmenu
|
|
2359
|
+
# Keep legacy field in sync
|
|
2360
|
+
prompt_data['quick_run'] = sv_quickmenu
|
|
2361
|
+
|
|
2362
|
+
# Check if filename changed - need to rename file
|
|
2363
|
+
if old_filename != name:
|
|
2364
|
+
old_path = Path(path)
|
|
2365
|
+
folder = str(old_path.parent) if old_path.parent != Path('.') else ''
|
|
2366
|
+
new_path = f"{folder}/{name}" if folder else name
|
|
2367
|
+
|
|
2368
|
+
# Delete old file and save to new location
|
|
2369
|
+
if self.library.delete_prompt(path):
|
|
2370
|
+
if self.library.save_prompt(new_path, prompt_data):
|
|
2371
|
+
self.library.load_all_prompts()
|
|
2372
|
+
self._refresh_tree()
|
|
2373
|
+
self._select_and_reveal_prompt(new_path)
|
|
2374
|
+
self.editor_current_path = new_path # Update to new path
|
|
2375
|
+
QMessageBox.information(self.main_widget, "Saved", f"Prompt renamed to '{name}' successfully!")
|
|
2376
|
+
self.log_message(f"✓ Renamed prompt: {old_filename} → {name}")
|
|
2377
|
+
else:
|
|
2378
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to rename prompt")
|
|
2153
2379
|
else:
|
|
2154
|
-
QMessageBox.warning(self.main_widget, "Error", "Failed to
|
|
2380
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to delete old prompt file")
|
|
2155
2381
|
else:
|
|
2156
|
-
|
|
2382
|
+
# Name unchanged, just update in place
|
|
2383
|
+
if self.library.save_prompt(path, prompt_data):
|
|
2384
|
+
QMessageBox.information(self.main_widget, "Saved", "Prompt updated successfully!")
|
|
2385
|
+
self._refresh_tree()
|
|
2386
|
+
else:
|
|
2387
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to save prompt")
|
|
2157
2388
|
else:
|
|
2158
|
-
#
|
|
2159
|
-
|
|
2160
|
-
|
|
2389
|
+
# Creating new prompt
|
|
2390
|
+
folder = getattr(self, 'editor_target_folder', 'Project Prompts')
|
|
2391
|
+
|
|
2392
|
+
# Create new prompt data
|
|
2393
|
+
from datetime import datetime
|
|
2394
|
+
prompt_data = {
|
|
2395
|
+
'name': name,
|
|
2396
|
+
'description': description,
|
|
2397
|
+
'content': content,
|
|
2398
|
+
'domain': '',
|
|
2399
|
+
'version': '1.0',
|
|
2400
|
+
'task_type': 'Translation',
|
|
2401
|
+
'favorite': False,
|
|
2402
|
+
# QuickMenu
|
|
2403
|
+
'quickmenu_label': quickmenu_label or name,
|
|
2404
|
+
'quickmenu_grid': quickmenu_grid,
|
|
2405
|
+
'sv_quickmenu': sv_quickmenu,
|
|
2406
|
+
# Legacy
|
|
2407
|
+
'quick_run': sv_quickmenu,
|
|
2408
|
+
'folder': folder,
|
|
2409
|
+
'tags': [],
|
|
2410
|
+
'created': datetime.now().strftime('%Y-%m-%d'),
|
|
2411
|
+
'modified': datetime.now().strftime('%Y-%m-%d')
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
# Create the prompt file (save_prompt creates new file if it doesn't exist)
|
|
2415
|
+
relative_path = f"{folder}/{name}.svprompt"
|
|
2416
|
+
if self.library.save_prompt(relative_path, prompt_data):
|
|
2417
|
+
QMessageBox.information(self.main_widget, "Created", f"Prompt '{name}' created successfully!")
|
|
2418
|
+
self.library.load_all_prompts() # Reload to get new prompt in memory
|
|
2161
2419
|
self._refresh_tree()
|
|
2420
|
+
self.editor_current_path = relative_path # Now editing this prompt
|
|
2162
2421
|
else:
|
|
2163
|
-
QMessageBox.warning(self.main_widget, "Error", "Failed to
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
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")
|
|
2422
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to create prompt")
|
|
2423
|
+
|
|
2424
|
+
except Exception as e:
|
|
2425
|
+
import traceback
|
|
2426
|
+
error_msg = f"Prompt save error: {str(e)}\n{traceback.format_exc()}"
|
|
2427
|
+
print(f"[ERROR] {error_msg}")
|
|
2428
|
+
self.log_message(f"❌ Prompt save error: {str(e)}")
|
|
2429
|
+
QMessageBox.critical(self.main_widget, "Save Error", f"Failed to save prompt:\n\n{str(e)}")
|
|
2430
|
+
return
|
|
2199
2431
|
|
|
2200
2432
|
def _save_external_prompt(self, file_path: str, name: str, description: str, content: str):
|
|
2201
2433
|
"""Save changes to an external prompt file"""
|
|
@@ -2421,7 +2653,7 @@ class UnifiedPromptManagerQt:
|
|
|
2421
2653
|
'favorite': False,
|
|
2422
2654
|
'quickmenu_label': name_without_ext,
|
|
2423
2655
|
'quickmenu_grid': False,
|
|
2424
|
-
'
|
|
2656
|
+
'sv_quickmenu': False,
|
|
2425
2657
|
'quick_run': False,
|
|
2426
2658
|
'folder': folder_path,
|
|
2427
2659
|
'tags': [],
|
|
@@ -2437,6 +2669,7 @@ class UnifiedPromptManagerQt:
|
|
|
2437
2669
|
self._refresh_tree()
|
|
2438
2670
|
self._select_and_reveal_prompt(relative_path)
|
|
2439
2671
|
self._load_prompt_in_editor(relative_path)
|
|
2672
|
+
self.btn_save_prompt.setEnabled(True) # Ensure Save button is enabled for new prompt
|
|
2440
2673
|
self.log_message(f"✓ Created new prompt '{name}' in folder: {folder_path}")
|
|
2441
2674
|
else:
|
|
2442
2675
|
QMessageBox.warning(self.main_widget, "Error", "Failed to create prompt")
|
|
@@ -2469,7 +2702,7 @@ class UnifiedPromptManagerQt:
|
|
|
2469
2702
|
src_data['favorite'] = False
|
|
2470
2703
|
src_data['quick_run'] = False
|
|
2471
2704
|
src_data['quickmenu_grid'] = False
|
|
2472
|
-
src_data['
|
|
2705
|
+
src_data['sv_quickmenu'] = False
|
|
2473
2706
|
src_data['folder'] = folder
|
|
2474
2707
|
src_data['created'] = datetime.now().strftime('%Y-%m-%d')
|
|
2475
2708
|
src_data['modified'] = datetime.now().strftime('%Y-%m-%d')
|