supervertaler 1.9.116__py3-none-any.whl → 1.9.131__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 +438 -103
- modules/shortcut_manager.py +29 -1
- modules/termview_widget.py +9 -2
- modules/unified_prompt_library.py +12 -9
- modules/unified_prompt_manager_qt.py +355 -112
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.131.dist-info}/METADATA +37 -3
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.131.dist-info}/RECORD +11 -11
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.131.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.131.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.131.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.131.dist-info}/top_level.txt +0 -0
|
@@ -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,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()
|
|
@@ -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 =
|
|
1707
|
+
self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in Grid right-click QuickMenu")
|
|
1474
1708
|
quickmenu_layout.addWidget(self.editor_quickmenu_in_grid_cb, 2)
|
|
1475
1709
|
|
|
1476
|
-
self.editor_quickmenu_in_quickmenu_cb =
|
|
1710
|
+
self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in Supervertaler QuickMenu")
|
|
1477
1711
|
quickmenu_layout.addWidget(self.editor_quickmenu_in_quickmenu_cb, 1)
|
|
1478
1712
|
|
|
1479
1713
|
layout.addLayout(quickmenu_layout)
|
|
@@ -1950,7 +2184,7 @@ class UnifiedPromptManagerQt:
|
|
|
1950
2184
|
indicators = []
|
|
1951
2185
|
if prompt_data.get('favorite'):
|
|
1952
2186
|
indicators.append("⭐")
|
|
1953
|
-
if prompt_data.get('
|
|
2187
|
+
if prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False)):
|
|
1954
2188
|
indicators.append("⚡")
|
|
1955
2189
|
if prompt_data.get('quickmenu_grid', False):
|
|
1956
2190
|
indicators.append("🖱️")
|
|
@@ -2018,7 +2252,7 @@ class UnifiedPromptManagerQt:
|
|
|
2018
2252
|
action_fav.triggered.connect(lambda: self._toggle_favorite(path))
|
|
2019
2253
|
|
|
2020
2254
|
# Toggle QuickMenu (legacy: quick_run)
|
|
2021
|
-
if prompt_data.get('
|
|
2255
|
+
if prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False)):
|
|
2022
2256
|
action_qr = menu.addAction("⚡ Remove from QuickMenu")
|
|
2023
2257
|
else:
|
|
2024
2258
|
action_qr = menu.addAction("⚡ Add to QuickMenu")
|
|
@@ -2070,7 +2304,7 @@ class UnifiedPromptManagerQt:
|
|
|
2070
2304
|
if hasattr(self, 'editor_quickmenu_in_grid_cb'):
|
|
2071
2305
|
self.editor_quickmenu_in_grid_cb.setChecked(bool(prompt_data.get('quickmenu_grid', False)))
|
|
2072
2306
|
if hasattr(self, 'editor_quickmenu_in_quickmenu_cb'):
|
|
2073
|
-
self.editor_quickmenu_in_quickmenu_cb.setChecked(bool(prompt_data.get('
|
|
2307
|
+
self.editor_quickmenu_in_quickmenu_cb.setChecked(bool(prompt_data.get('sv_quickmenu', prompt_data.get('quick_run', False))))
|
|
2074
2308
|
self.editor_content.setPlainText(prompt_data.get('content', ''))
|
|
2075
2309
|
|
|
2076
2310
|
# Store current path for saving
|
|
@@ -2083,119 +2317,127 @@ class UnifiedPromptManagerQt:
|
|
|
2083
2317
|
|
|
2084
2318
|
def _save_current_prompt(self):
|
|
2085
2319
|
"""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
|
|
2320
|
+
try:
|
|
2321
|
+
name = self.editor_name_input.text().strip()
|
|
2322
|
+
description = self.editor_desc_input.text().strip()
|
|
2323
|
+
content = self.editor_content.toPlainText().strip()
|
|
2116
2324
|
|
|
2117
|
-
#
|
|
2118
|
-
|
|
2119
|
-
|
|
2325
|
+
# Name field now represents the complete filename with extension
|
|
2326
|
+
# No stripping needed - user sees and edits the full filename
|
|
2327
|
+
|
|
2328
|
+
quickmenu_label = ''
|
|
2329
|
+
quickmenu_grid = False
|
|
2330
|
+
sv_quickmenu = False
|
|
2331
|
+
if hasattr(self, 'editor_quickmenu_label_input'):
|
|
2332
|
+
quickmenu_label = self.editor_quickmenu_label_input.text().strip()
|
|
2333
|
+
if hasattr(self, 'editor_quickmenu_in_grid_cb'):
|
|
2334
|
+
quickmenu_grid = bool(self.editor_quickmenu_in_grid_cb.isChecked())
|
|
2335
|
+
if hasattr(self, 'editor_quickmenu_in_quickmenu_cb'):
|
|
2336
|
+
sv_quickmenu = bool(self.editor_quickmenu_in_quickmenu_cb.isChecked())
|
|
2337
|
+
|
|
2338
|
+
if not name or not content:
|
|
2339
|
+
QMessageBox.warning(self.main_widget, "Error", "Name and content are required")
|
|
2120
2340
|
return
|
|
2121
2341
|
|
|
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
|
|
2342
|
+
# Check if this is a new prompt or editing existing
|
|
2343
|
+
if hasattr(self, 'editor_current_path') and self.editor_current_path:
|
|
2344
|
+
path = self.editor_current_path
|
|
2143
2345
|
|
|
2144
|
-
#
|
|
2145
|
-
if
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2346
|
+
# Handle external prompts (save back to external file)
|
|
2347
|
+
if path.startswith("[EXTERNAL] "):
|
|
2348
|
+
external_file_path = path[11:] # Remove "[EXTERNAL] " prefix
|
|
2349
|
+
self._save_external_prompt(external_file_path, name, description, content)
|
|
2350
|
+
return
|
|
2351
|
+
|
|
2352
|
+
# Editing existing library prompt
|
|
2353
|
+
if path not in self.library.prompts:
|
|
2354
|
+
QMessageBox.warning(self.main_widget, "Error", "Prompt no longer exists")
|
|
2355
|
+
return
|
|
2356
|
+
|
|
2357
|
+
prompt_data = self.library.prompts[path].copy()
|
|
2358
|
+
old_filename = Path(path).name
|
|
2359
|
+
|
|
2360
|
+
# Extract name without extension for metadata
|
|
2361
|
+
name_without_ext = Path(name).stem
|
|
2362
|
+
|
|
2363
|
+
prompt_data['name'] = name_without_ext
|
|
2364
|
+
prompt_data['description'] = description
|
|
2365
|
+
prompt_data['content'] = content
|
|
2366
|
+
prompt_data['quickmenu_label'] = quickmenu_label or name_without_ext
|
|
2367
|
+
prompt_data['quickmenu_grid'] = quickmenu_grid
|
|
2368
|
+
prompt_data['sv_quickmenu'] = sv_quickmenu
|
|
2369
|
+
# Keep legacy field in sync
|
|
2370
|
+
prompt_data['quick_run'] = sv_quickmenu
|
|
2371
|
+
|
|
2372
|
+
# Check if filename changed - need to rename file
|
|
2373
|
+
if old_filename != name:
|
|
2374
|
+
old_path = Path(path)
|
|
2375
|
+
folder = str(old_path.parent) if old_path.parent != Path('.') else ''
|
|
2376
|
+
new_path = f"{folder}/{name}" if folder else name
|
|
2377
|
+
|
|
2378
|
+
# Delete old file and save to new location
|
|
2379
|
+
if self.library.delete_prompt(path):
|
|
2380
|
+
if self.library.save_prompt(new_path, prompt_data):
|
|
2381
|
+
self.library.load_all_prompts()
|
|
2382
|
+
self._refresh_tree()
|
|
2383
|
+
self._select_and_reveal_prompt(new_path)
|
|
2384
|
+
self.editor_current_path = new_path # Update to new path
|
|
2385
|
+
QMessageBox.information(self.main_widget, "Saved", f"Prompt renamed to '{name}' successfully!")
|
|
2386
|
+
self.log_message(f"✓ Renamed prompt: {old_filename} → {name}")
|
|
2387
|
+
else:
|
|
2388
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to rename prompt")
|
|
2153
2389
|
else:
|
|
2154
|
-
QMessageBox.warning(self.main_widget, "Error", "Failed to
|
|
2390
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to delete old prompt file")
|
|
2155
2391
|
else:
|
|
2156
|
-
|
|
2392
|
+
# Name unchanged, just update in place
|
|
2393
|
+
if self.library.save_prompt(path, prompt_data):
|
|
2394
|
+
QMessageBox.information(self.main_widget, "Saved", "Prompt updated successfully!")
|
|
2395
|
+
self._refresh_tree()
|
|
2396
|
+
else:
|
|
2397
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to save prompt")
|
|
2157
2398
|
else:
|
|
2158
|
-
#
|
|
2159
|
-
|
|
2160
|
-
|
|
2399
|
+
# Creating new prompt
|
|
2400
|
+
folder = getattr(self, 'editor_target_folder', 'Project Prompts')
|
|
2401
|
+
|
|
2402
|
+
# Create new prompt data
|
|
2403
|
+
from datetime import datetime
|
|
2404
|
+
prompt_data = {
|
|
2405
|
+
'name': name,
|
|
2406
|
+
'description': description,
|
|
2407
|
+
'content': content,
|
|
2408
|
+
'domain': '',
|
|
2409
|
+
'version': '1.0',
|
|
2410
|
+
'task_type': 'Translation',
|
|
2411
|
+
'favorite': False,
|
|
2412
|
+
# QuickMenu
|
|
2413
|
+
'quickmenu_label': quickmenu_label or name,
|
|
2414
|
+
'quickmenu_grid': quickmenu_grid,
|
|
2415
|
+
'sv_quickmenu': sv_quickmenu,
|
|
2416
|
+
# Legacy
|
|
2417
|
+
'quick_run': sv_quickmenu,
|
|
2418
|
+
'folder': folder,
|
|
2419
|
+
'tags': [],
|
|
2420
|
+
'created': datetime.now().strftime('%Y-%m-%d'),
|
|
2421
|
+
'modified': datetime.now().strftime('%Y-%m-%d')
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
# Create the prompt file (save_prompt creates new file if it doesn't exist)
|
|
2425
|
+
relative_path = f"{folder}/{name}.svprompt"
|
|
2426
|
+
if self.library.save_prompt(relative_path, prompt_data):
|
|
2427
|
+
QMessageBox.information(self.main_widget, "Created", f"Prompt '{name}' created successfully!")
|
|
2428
|
+
self.library.load_all_prompts() # Reload to get new prompt in memory
|
|
2161
2429
|
self._refresh_tree()
|
|
2430
|
+
self.editor_current_path = relative_path # Now editing this prompt
|
|
2162
2431
|
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")
|
|
2432
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to create prompt")
|
|
2433
|
+
|
|
2434
|
+
except Exception as e:
|
|
2435
|
+
import traceback
|
|
2436
|
+
error_msg = f"Prompt save error: {str(e)}\n{traceback.format_exc()}"
|
|
2437
|
+
print(f"[ERROR] {error_msg}")
|
|
2438
|
+
self.log_message(f"❌ Prompt save error: {str(e)}")
|
|
2439
|
+
QMessageBox.critical(self.main_widget, "Save Error", f"Failed to save prompt:\n\n{str(e)}")
|
|
2440
|
+
return
|
|
2199
2441
|
|
|
2200
2442
|
def _save_external_prompt(self, file_path: str, name: str, description: str, content: str):
|
|
2201
2443
|
"""Save changes to an external prompt file"""
|
|
@@ -2421,7 +2663,7 @@ class UnifiedPromptManagerQt:
|
|
|
2421
2663
|
'favorite': False,
|
|
2422
2664
|
'quickmenu_label': name_without_ext,
|
|
2423
2665
|
'quickmenu_grid': False,
|
|
2424
|
-
'
|
|
2666
|
+
'sv_quickmenu': False,
|
|
2425
2667
|
'quick_run': False,
|
|
2426
2668
|
'folder': folder_path,
|
|
2427
2669
|
'tags': [],
|
|
@@ -2437,6 +2679,7 @@ class UnifiedPromptManagerQt:
|
|
|
2437
2679
|
self._refresh_tree()
|
|
2438
2680
|
self._select_and_reveal_prompt(relative_path)
|
|
2439
2681
|
self._load_prompt_in_editor(relative_path)
|
|
2682
|
+
self.btn_save_prompt.setEnabled(True) # Ensure Save button is enabled for new prompt
|
|
2440
2683
|
self.log_message(f"✓ Created new prompt '{name}' in folder: {folder_path}")
|
|
2441
2684
|
else:
|
|
2442
2685
|
QMessageBox.warning(self.main_widget, "Error", "Failed to create prompt")
|
|
@@ -2469,7 +2712,7 @@ class UnifiedPromptManagerQt:
|
|
|
2469
2712
|
src_data['favorite'] = False
|
|
2470
2713
|
src_data['quick_run'] = False
|
|
2471
2714
|
src_data['quickmenu_grid'] = False
|
|
2472
|
-
src_data['
|
|
2715
|
+
src_data['sv_quickmenu'] = False
|
|
2473
2716
|
src_data['folder'] = folder
|
|
2474
2717
|
src_data['created'] = datetime.now().strftime('%Y-%m-%d')
|
|
2475
2718
|
src_data['modified'] = datetime.now().strftime('%Y-%m-%d')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supervertaler
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.131
|
|
4
4
|
Summary: Professional AI-powered translation workbench with multi-LLM support, glossary system, TM, spellcheck, voice commands, and PyQt6 interface. Batteries included (core).
|
|
5
5
|
Home-page: https://supervertaler.com
|
|
6
6
|
Author: Michael Beijer
|
|
@@ -71,7 +71,7 @@ Dynamic: home-page
|
|
|
71
71
|
Dynamic: license-file
|
|
72
72
|
Dynamic: requires-python
|
|
73
73
|
|
|
74
|
-
# 🚀 Supervertaler v1.9.
|
|
74
|
+
# 🚀 Supervertaler v1.9.128
|
|
75
75
|
|
|
76
76
|
[](https://pypi.org/project/Supervertaler/)
|
|
77
77
|
[](https://www.python.org/downloads/)
|
|
@@ -79,7 +79,41 @@ Dynamic: requires-python
|
|
|
79
79
|
|
|
80
80
|
AI-enhanced CAT tool with multi-LLM support (GPT-4, Claude, Gemini, Ollama), innovative Superlookup concordance system offering access to multiple terminology sources (TMs, glossaries, web resources, etc.), and seamless CAT tool integration (memoQ, Trados, CafeTran, Phrase).
|
|
81
81
|
|
|
82
|
-
**Current Version:** v1.9.
|
|
82
|
+
**Current Version:** v1.9.131 (January 20, 2026)
|
|
83
|
+
|
|
84
|
+
### ENHANCED in v1.9.128 - 📝 Placeholders Tab Layout Optimization
|
|
85
|
+
|
|
86
|
+
**Better Use of Space:** The Placeholders reference tab in the Prompt Manager now uses a compact sidebar layout for Usage Tips, giving the placeholders table much more vertical space. Matches the clean, efficient layout style of other tools like AutoFingers and TMX Editor.
|
|
87
|
+
|
|
88
|
+
### FIXED in v1.9.125 - 🐛 Prompt Save Crash
|
|
89
|
+
|
|
90
|
+
**Critical Fix:** Wrapped prompt save logic in comprehensive error handling to prevent silent crashes. Now shows detailed error messages if save fails instead of crashing the app.
|
|
91
|
+
|
|
92
|
+
### ADDED in v1.9.124 - 📄 QuickMenu Document Context
|
|
93
|
+
|
|
94
|
+
**Context-Aware AI Suggestions:** QuickMenu prompts can now access the full document context! Use the new `{{DOCUMENT_CONTEXT}}` placeholder to give the AI access to your project's source segments. Configure what percentage of segments to include (0-100%, default 50%) in Settings → AI Settings. Perfect for: "Suggest the best translation of '{{SELECTION}}' within the context of this project."
|
|
95
|
+
|
|
96
|
+
### FIXED in v1.9.123 - 🤖 QuickMenu Generic AI Support
|
|
97
|
+
|
|
98
|
+
**QuickMenu Now Works for Any Task:** Fixed bug where QuickMenu prompts were forced into translation mode. Now supports any AI task: explain terms, define concepts, suggest multiple options, analyze tone, etc.
|
|
99
|
+
|
|
100
|
+
### ENHANCED in v1.9.122 - ⌨️ Ctrl+N for Quick Notes
|
|
101
|
+
|
|
102
|
+
**Faster Note-Taking:** Ctrl+N now focuses the Segment Note tab and places your cursor in the notes field, ready to type. Perfect for quick proofreading notes, context reminders, or translation decisions.
|
|
103
|
+
|
|
104
|
+
### FIXED in v1.9.121 - 🐛 Find & Replace Performance
|
|
105
|
+
|
|
106
|
+
**Critical Fix:** v1.9.120 accidentally made Find & Replace slower (37+ seconds). v1.9.121 actually fixes it by updating cells in-place instead of recreating all widgets. Now near-instant!
|
|
107
|
+
|
|
108
|
+
### OPTIMIZED in v1.9.120 - ⚡ Find & Replace Speed (BUGGY - USE v1.9.121)
|
|
109
|
+
|
|
110
|
+
### ADDED in v1.9.119 - ⌨️ Alt+D Dictionary Shortcut
|
|
111
|
+
|
|
112
|
+
**Quick Dictionary Addition:** Press Alt+D to instantly add misspelled words to your custom dictionary without using the right-click menu.
|
|
113
|
+
|
|
114
|
+
### FIXED in v1.9.117 - 🐛 Glossary Punctuation Matching
|
|
115
|
+
|
|
116
|
+
**Critical Fix:** Glossary entries with trailing punctuation (periods, quotes, etc.) now match correctly! Previously, "sentence." in glossary wouldn't match "sentence." in source text due to tokenization stripping punctuation from source but not from glossary entries.
|
|
83
117
|
|
|
84
118
|
### FIXED in v1.9.116 - 🐛 Tab Navigation & Startup
|
|
85
119
|
|