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.
- Supervertaler.py +4210 -965
- modules/ai_attachment_manager.py +3 -3
- modules/config_manager.py +10 -10
- modules/database_manager.py +197 -52
- modules/keyboard_shortcuts_widget.py +7 -0
- modules/non_translatables_manager.py +1 -1
- modules/prompt_library_migration.py +1 -1
- modules/setup_wizard.py +8 -8
- modules/shortcut_manager.py +29 -1
- modules/superbrowser.py +16 -12
- modules/superlookup.py +18 -10
- modules/tag_manager.py +20 -2
- modules/termview_widget.py +29 -14
- modules/tm_metadata_manager.py +41 -0
- modules/tmx_editor_qt.py +1 -1
- modules/translation_memory.py +54 -9
- modules/unified_prompt_library.py +13 -10
- modules/unified_prompt_manager_qt.py +363 -139
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.172.dist-info}/METADATA +133 -7
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.172.dist-info}/RECORD +24 -24
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.172.dist-info}/WHEEL +1 -1
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.172.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.172.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.116.dist-info → supervertaler-1.9.172.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
|
|
|
@@ -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 / "
|
|
465
|
-
# Use
|
|
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 / "
|
|
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("
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
#
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
2118
|
-
|
|
2119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
2145
|
-
if
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
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
|
|
2371
|
+
QMessageBox.warning(self.main_widget, "Error", "Failed to delete old prompt file")
|
|
2155
2372
|
else:
|
|
2156
|
-
|
|
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
|
-
#
|
|
2159
|
-
|
|
2160
|
-
|
|
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
|
|
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")
|
|
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
|
-
'
|
|
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['
|
|
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/
|
|
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 / "
|
|
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
|