supervertaler 1.9.131__py3-none-any.whl → 1.9.173__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 +3799 -873
- modules/ai_attachment_manager.py +3 -3
- modules/config_manager.py +10 -10
- modules/database_manager.py +243 -65
- 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/superbrowser.py +16 -12
- modules/superlookup.py +18 -10
- modules/tag_manager.py +20 -2
- modules/termview_widget.py +20 -12
- modules/tm_metadata_manager.py +41 -0
- modules/tmx_editor_qt.py +1 -1
- modules/translation_memory.py +53 -8
- modules/unified_prompt_library.py +1 -1
- modules/unified_prompt_manager_qt.py +10 -29
- {supervertaler-1.9.131.dist-info → supervertaler-1.9.173.dist-info}/METADATA +105 -7
- {supervertaler-1.9.131.dist-info → supervertaler-1.9.173.dist-info}/RECORD +23 -23
- {supervertaler-1.9.131.dist-info → supervertaler-1.9.173.dist-info}/WHEEL +1 -1
- {supervertaler-1.9.131.dist-info → supervertaler-1.9.173.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.131.dist-info → supervertaler-1.9.173.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.131.dist-info → supervertaler-1.9.173.dist-info}/top_level.txt +0 -0
modules/superbrowser.py
CHANGED
|
@@ -45,11 +45,12 @@ def _clear_corrupted_cache(storage_path: str):
|
|
|
45
45
|
class ChatColumn(QWidget):
|
|
46
46
|
"""A column containing a chat interface with web browser"""
|
|
47
47
|
|
|
48
|
-
def __init__(self, title, url, header_color, parent=None):
|
|
48
|
+
def __init__(self, title, url, header_color, parent=None, user_data_path=None):
|
|
49
49
|
super().__init__(parent)
|
|
50
50
|
self.title = title
|
|
51
51
|
self.url = url
|
|
52
52
|
self.header_color = header_color
|
|
53
|
+
self.user_data_path = user_data_path # Store user data path
|
|
53
54
|
self.init_ui()
|
|
54
55
|
|
|
55
56
|
def init_ui(self):
|
|
@@ -102,12 +103,14 @@ class ChatColumn(QWidget):
|
|
|
102
103
|
profile_name = f"superbrowser_{self.title.lower()}"
|
|
103
104
|
self.profile = QWebEngineProfile(profile_name, self)
|
|
104
105
|
|
|
105
|
-
# Set persistent storage path
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
# Set persistent storage path using user_data_path from parent
|
|
107
|
+
if self.user_data_path:
|
|
108
|
+
storage_path = os.path.join(str(self.user_data_path), "superbrowser_profiles", profile_name)
|
|
109
|
+
else:
|
|
110
|
+
# Fallback to script directory if user_data_path not provided
|
|
111
|
+
dev_mode_marker = os.path.join(os.path.dirname(__file__), "..", ".supervertaler.local")
|
|
112
|
+
base_folder = "user_data_private" if os.path.exists(dev_mode_marker) else "user_data"
|
|
113
|
+
storage_path = os.path.join(os.path.dirname(__file__), "..", base_folder, "superbrowser_profiles", profile_name)
|
|
111
114
|
os.makedirs(storage_path, exist_ok=True)
|
|
112
115
|
|
|
113
116
|
# Clear potentially corrupted cache to prevent Chromium errors
|
|
@@ -166,9 +169,10 @@ class SuperbrowserWidget(QWidget):
|
|
|
166
169
|
and concurrent interaction with different AI models.
|
|
167
170
|
"""
|
|
168
171
|
|
|
169
|
-
def __init__(self, parent=None):
|
|
172
|
+
def __init__(self, parent=None, user_data_path=None):
|
|
170
173
|
super().__init__(parent)
|
|
171
174
|
self.parent_window = parent
|
|
175
|
+
self.user_data_path = user_data_path # Store user data path for profiles
|
|
172
176
|
|
|
173
177
|
# Default URLs for AI chat interfaces
|
|
174
178
|
self.chatgpt_url = "https://chatgpt.com/"
|
|
@@ -257,10 +261,10 @@ class SuperbrowserWidget(QWidget):
|
|
|
257
261
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
258
262
|
splitter.setHandleWidth(3)
|
|
259
263
|
|
|
260
|
-
# Create chat columns
|
|
261
|
-
self.chatgpt_column = ChatColumn("ChatGPT", self.chatgpt_url, "#10a37f", self)
|
|
262
|
-
self.claude_column = ChatColumn("Claude", self.claude_url, "#c17c4f", self)
|
|
263
|
-
self.gemini_column = ChatColumn("Gemini", self.gemini_url, "#4285f4", self)
|
|
264
|
+
# Create chat columns - pass user_data_path for profile storage
|
|
265
|
+
self.chatgpt_column = ChatColumn("ChatGPT", self.chatgpt_url, "#10a37f", self, user_data_path=self.user_data_path)
|
|
266
|
+
self.claude_column = ChatColumn("Claude", self.claude_url, "#c17c4f", self, user_data_path=self.user_data_path)
|
|
267
|
+
self.gemini_column = ChatColumn("Gemini", self.gemini_url, "#4285f4", self, user_data_path=self.user_data_path)
|
|
264
268
|
|
|
265
269
|
# Add columns to splitter
|
|
266
270
|
splitter.addWidget(self.chatgpt_column)
|
modules/superlookup.py
CHANGED
|
@@ -88,14 +88,18 @@ class SuperlookupEngine:
|
|
|
88
88
|
Captured text or None if failed
|
|
89
89
|
"""
|
|
90
90
|
try:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
# keyboard module is Windows-only
|
|
92
|
+
try:
|
|
93
|
+
import keyboard
|
|
94
|
+
# Wait for hotkey to release before sending Ctrl+C
|
|
95
|
+
time.sleep(0.2)
|
|
96
|
+
# Use keyboard library to send Ctrl+C
|
|
97
|
+
keyboard.press_and_release('ctrl+c')
|
|
98
|
+
time.sleep(0.2)
|
|
99
|
+
except ImportError:
|
|
100
|
+
# On non-Windows, just try to get clipboard content directly
|
|
101
|
+
# (user needs to have copied text manually)
|
|
102
|
+
pass
|
|
99
103
|
|
|
100
104
|
# Get clipboard
|
|
101
105
|
text = pyperclip.paste()
|
|
@@ -157,9 +161,13 @@ class SuperlookupEngine:
|
|
|
157
161
|
|
|
158
162
|
# Convert to LookupResult format (limit results)
|
|
159
163
|
for match in matches[:max_results]:
|
|
164
|
+
# Use 'source' and 'target' keys (matches database column names)
|
|
165
|
+
source_text = match.get('source', '')
|
|
166
|
+
target_text = match.get('target', '')
|
|
167
|
+
print(f"[Superlookup] Extracted: source='{source_text[:50]}...', target='{target_text[:50]}...'")
|
|
160
168
|
results.append(LookupResult(
|
|
161
|
-
source=
|
|
162
|
-
target=
|
|
169
|
+
source=source_text,
|
|
170
|
+
target=target_text,
|
|
163
171
|
match_percent=100, # Concordance = contains the text
|
|
164
172
|
source_type='tm',
|
|
165
173
|
metadata={
|
modules/tag_manager.py
CHANGED
|
@@ -77,15 +77,33 @@ class TagManager:
|
|
|
77
77
|
runs = []
|
|
78
78
|
current_pos = 0
|
|
79
79
|
|
|
80
|
+
# Check if paragraph style has bold/italic formatting
|
|
81
|
+
# This handles cases like "Subtitle" or "Title" styles that are bold
|
|
82
|
+
style_bold = False
|
|
83
|
+
style_italic = False
|
|
84
|
+
try:
|
|
85
|
+
if paragraph.style and paragraph.style.font:
|
|
86
|
+
if paragraph.style.font.bold:
|
|
87
|
+
style_bold = True
|
|
88
|
+
if paragraph.style.font.italic:
|
|
89
|
+
style_italic = True
|
|
90
|
+
except Exception:
|
|
91
|
+
pass # If we can't read style, just use run-level formatting
|
|
92
|
+
|
|
80
93
|
for run in paragraph.runs:
|
|
81
94
|
text = run.text
|
|
82
95
|
if not text:
|
|
83
96
|
continue
|
|
84
97
|
|
|
98
|
+
# Combine run-level formatting with style-level formatting
|
|
99
|
+
# run.bold can be True, False, or None (None means inherit from style)
|
|
100
|
+
is_bold = run.bold if run.bold is not None else style_bold
|
|
101
|
+
is_italic = run.italic if run.italic is not None else style_italic
|
|
102
|
+
|
|
85
103
|
run_info = FormattingRun(
|
|
86
104
|
text=text,
|
|
87
|
-
bold=
|
|
88
|
-
italic=
|
|
105
|
+
bold=is_bold or False,
|
|
106
|
+
italic=is_italic or False,
|
|
89
107
|
underline=run.underline or False,
|
|
90
108
|
subscript=run.font.subscript or False if run.font else False,
|
|
91
109
|
superscript=run.font.superscript or False if run.font else False,
|
modules/termview_widget.py
CHANGED
|
@@ -515,6 +515,9 @@ class TermviewWidget(QWidget):
|
|
|
515
515
|
self.current_target_lang = None
|
|
516
516
|
self.current_project_id = None # Store project ID for termbase priority lookup
|
|
517
517
|
|
|
518
|
+
# Debug mode - disable verbose tokenization logging by default (performance)
|
|
519
|
+
self.debug_tokenize = False
|
|
520
|
+
|
|
518
521
|
# Default font settings (will be updated from main app settings)
|
|
519
522
|
self.current_font_family = "Segoe UI"
|
|
520
523
|
self.current_font_size = 10
|
|
@@ -750,7 +753,10 @@ class TermviewWidget(QWidget):
|
|
|
750
753
|
if not source_term or not target_term:
|
|
751
754
|
continue
|
|
752
755
|
|
|
753
|
-
key
|
|
756
|
+
# Strip punctuation from key to match lookup normalization
|
|
757
|
+
# This ensures "ca." in glossary matches "ca." token stripped to "ca"
|
|
758
|
+
PUNCT_CHARS_FOR_KEY = '.,;:!?\"\'\u201C\u201D\u201E\u00AB\u00BB\u2018\u2019\u201A\u2039\u203A()[]'
|
|
759
|
+
key = source_term.lower().strip(PUNCT_CHARS_FOR_KEY)
|
|
754
760
|
if key not in matches_dict:
|
|
755
761
|
matches_dict[key] = []
|
|
756
762
|
|
|
@@ -803,7 +809,8 @@ class TermviewWidget(QWidget):
|
|
|
803
809
|
|
|
804
810
|
# Comprehensive set of quote and punctuation characters to strip
|
|
805
811
|
# Using Unicode escapes to avoid encoding issues
|
|
806
|
-
|
|
812
|
+
# Include brackets for terms like "(typisch)" to match "typisch"
|
|
813
|
+
PUNCT_CHARS = '.,;:!?\"\'\u201C\u201D\u201E\u00AB\u00BB\u2018\u2019\u201A\u2039\u203A()[]'
|
|
807
814
|
|
|
808
815
|
# Track which terms have already been assigned shortcuts (avoid duplicates)
|
|
809
816
|
assigned_shortcuts = set()
|
|
@@ -816,7 +823,6 @@ class TermviewWidget(QWidget):
|
|
|
816
823
|
|
|
817
824
|
# Check if this is a non-translatable
|
|
818
825
|
if lookup_key in nt_dict:
|
|
819
|
-
# Create NT block
|
|
820
826
|
nt_block = NTBlock(token, nt_dict[lookup_key], self, theme_manager=self.theme_manager,
|
|
821
827
|
font_size=self.current_font_size, font_family=self.current_font_family,
|
|
822
828
|
font_bold=self.current_font_bold)
|
|
@@ -992,9 +998,9 @@ class TermviewWidget(QWidget):
|
|
|
992
998
|
Returns:
|
|
993
999
|
List of tokens (words/phrases/numbers), with multi-word terms kept together
|
|
994
1000
|
"""
|
|
995
|
-
# DEBUG: Log multi-word terms we're looking for
|
|
1001
|
+
# DEBUG: Log multi-word terms we're looking for (only if debug_tokenize enabled)
|
|
996
1002
|
multi_word_terms = [k for k in matches.keys() if ' ' in k]
|
|
997
|
-
if multi_word_terms:
|
|
1003
|
+
if multi_word_terms and self.debug_tokenize:
|
|
998
1004
|
self.log(f"🔍 Tokenize: Looking for {len(multi_word_terms)} multi-word terms:")
|
|
999
1005
|
for term in sorted(multi_word_terms, key=len, reverse=True)[:3]:
|
|
1000
1006
|
self.log(f" - '{term}'")
|
|
@@ -1019,11 +1025,12 @@ class TermviewWidget(QWidget):
|
|
|
1019
1025
|
else:
|
|
1020
1026
|
pattern = r'\b' + term_escaped + r'\b'
|
|
1021
1027
|
|
|
1022
|
-
# DEBUG: Check if multi-word term is found
|
|
1028
|
+
# DEBUG: Check if multi-word term is found (only if debug_tokenize enabled)
|
|
1023
1029
|
found = re.search(pattern, text_lower)
|
|
1024
|
-
self.
|
|
1025
|
-
|
|
1026
|
-
|
|
1030
|
+
if self.debug_tokenize:
|
|
1031
|
+
self.log(f"🔍 Tokenize: Pattern '{pattern}' for '{term}' → {'FOUND' if found else 'NOT FOUND'}")
|
|
1032
|
+
if found:
|
|
1033
|
+
self.log(f" Match at position {found.span()}: '{text[found.start():found.end()]}'")
|
|
1027
1034
|
|
|
1028
1035
|
# Find all matches using regex
|
|
1029
1036
|
for match in re.finditer(pattern, text_lower):
|
|
@@ -1036,10 +1043,11 @@ class TermviewWidget(QWidget):
|
|
|
1036
1043
|
original_term = text[pos:pos + len(term)]
|
|
1037
1044
|
tokens_with_positions.append((pos, len(term), original_term))
|
|
1038
1045
|
used_positions.update(term_positions)
|
|
1039
|
-
self.
|
|
1046
|
+
if self.debug_tokenize:
|
|
1047
|
+
self.log(f" ✅ Added multi-word token: '{original_term}' covering positions {pos}-{pos+len(term)}")
|
|
1040
1048
|
|
|
1041
|
-
# DEBUG: Log used_positions after first pass
|
|
1042
|
-
if ' ' in sorted(matches.keys(), key=len, reverse=True)[0]:
|
|
1049
|
+
# DEBUG: Log used_positions after first pass (only if debug_tokenize enabled)
|
|
1050
|
+
if matches and ' ' in sorted(matches.keys(), key=len, reverse=True)[0] and self.debug_tokenize:
|
|
1043
1051
|
self.log(f"🔍 After first pass: {len(used_positions)} positions marked as used")
|
|
1044
1052
|
self.log(f" Used positions: {sorted(list(used_positions))[:20]}...")
|
|
1045
1053
|
|
modules/tm_metadata_manager.py
CHANGED
|
@@ -396,6 +396,47 @@ class TMMetadataManager:
|
|
|
396
396
|
self.log(f"✗ Error fetching active tm_ids: {e}")
|
|
397
397
|
return []
|
|
398
398
|
|
|
399
|
+
def get_writable_tm_ids(self, project_id: Optional[int]) -> List[str]:
|
|
400
|
+
"""
|
|
401
|
+
Get list of writable tm_id strings for a project.
|
|
402
|
+
|
|
403
|
+
Returns TMs where:
|
|
404
|
+
- The TM has an activation record for this project AND
|
|
405
|
+
- read_only = 0 (Write checkbox is enabled)
|
|
406
|
+
|
|
407
|
+
This is used for SAVING segments to TM, separate from get_active_tm_ids()
|
|
408
|
+
which is used for READING/matching from TM.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
List of tm_id strings that are writable for the project
|
|
412
|
+
"""
|
|
413
|
+
if project_id is None:
|
|
414
|
+
# No project - return all writable TMs
|
|
415
|
+
try:
|
|
416
|
+
cursor = self.db_manager.cursor
|
|
417
|
+
cursor.execute("SELECT tm_id FROM translation_memories WHERE read_only = 0")
|
|
418
|
+
return [row[0] for row in cursor.fetchall()]
|
|
419
|
+
except Exception as e:
|
|
420
|
+
self.log(f"✗ Error fetching all writable tm_ids: {e}")
|
|
421
|
+
return []
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
cursor = self.db_manager.cursor
|
|
425
|
+
|
|
426
|
+
# Return TMs where Write checkbox is enabled (read_only = 0)
|
|
427
|
+
# AND the TM has an activation record for this project
|
|
428
|
+
cursor.execute("""
|
|
429
|
+
SELECT tm.tm_id
|
|
430
|
+
FROM translation_memories tm
|
|
431
|
+
INNER JOIN tm_activation ta ON tm.id = ta.tm_id
|
|
432
|
+
WHERE ta.project_id = ? AND tm.read_only = 0
|
|
433
|
+
""", (project_id,))
|
|
434
|
+
|
|
435
|
+
return [row[0] for row in cursor.fetchall()]
|
|
436
|
+
except Exception as e:
|
|
437
|
+
self.log(f"✗ Error fetching writable tm_ids: {e}")
|
|
438
|
+
return []
|
|
439
|
+
|
|
399
440
|
# ========================================================================
|
|
400
441
|
# PROJECT TM MANAGEMENT (similar to termbases)
|
|
401
442
|
# ========================================================================
|
modules/tmx_editor_qt.py
CHANGED
|
@@ -2655,7 +2655,7 @@ if __name__ == "__main__":
|
|
|
2655
2655
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".supervertaler.local")
|
|
2656
2656
|
)
|
|
2657
2657
|
user_data_path = Path("user_data_private" if ENABLE_PRIVATE_FEATURES else "user_data")
|
|
2658
|
-
db_path = user_data_path / "
|
|
2658
|
+
db_path = user_data_path / "resources" / "supervertaler.db"
|
|
2659
2659
|
|
|
2660
2660
|
# Ensure database directory exists
|
|
2661
2661
|
db_path.parent.mkdir(parents=True, exist_ok=True)
|
modules/translation_memory.py
CHANGED
|
@@ -123,7 +123,7 @@ class TMDatabase:
|
|
|
123
123
|
if source_lang and target_lang:
|
|
124
124
|
self.set_tm_languages(source_lang, target_lang)
|
|
125
125
|
|
|
126
|
-
# Global fuzzy threshold
|
|
126
|
+
# Global fuzzy threshold (75% minimum similarity for fuzzy matches)
|
|
127
127
|
self.fuzzy_threshold = 0.75
|
|
128
128
|
|
|
129
129
|
# TM metadata cache (populated from database as needed)
|
|
@@ -401,7 +401,7 @@ class TMDatabase:
|
|
|
401
401
|
|
|
402
402
|
def load_tmx_file(self, filepath: str, src_lang: str, tgt_lang: str,
|
|
403
403
|
tm_name: str = None, read_only: bool = False,
|
|
404
|
-
strip_variants: bool = True) -> tuple[str, int]:
|
|
404
|
+
strip_variants: bool = True, progress_callback=None) -> tuple[str, int]:
|
|
405
405
|
"""
|
|
406
406
|
Load TMX file into a new custom TM
|
|
407
407
|
|
|
@@ -412,6 +412,7 @@ class TMDatabase:
|
|
|
412
412
|
tm_name: Custom name for TM (default: filename)
|
|
413
413
|
read_only: Make TM read-only
|
|
414
414
|
strip_variants: Match base languages ignoring regional variants (default: True)
|
|
415
|
+
progress_callback: Optional callback function(current, total, message) for progress updates
|
|
415
416
|
|
|
416
417
|
Returns: (tm_id, entry_count)
|
|
417
418
|
"""
|
|
@@ -423,16 +424,18 @@ class TMDatabase:
|
|
|
423
424
|
self.add_custom_tm(tm_name, tm_id, read_only=read_only)
|
|
424
425
|
|
|
425
426
|
# Load TMX content
|
|
426
|
-
loaded_count = self._load_tmx_into_db(filepath, src_lang, tgt_lang, tm_id,
|
|
427
|
+
loaded_count = self._load_tmx_into_db(filepath, src_lang, tgt_lang, tm_id,
|
|
428
|
+
strip_variants=strip_variants,
|
|
429
|
+
progress_callback=progress_callback)
|
|
427
430
|
|
|
428
431
|
self.log(f"✓ Loaded {loaded_count} entries from {os.path.basename(filepath)}")
|
|
429
432
|
|
|
430
433
|
return tm_id, loaded_count
|
|
431
434
|
|
|
432
435
|
def _load_tmx_into_db(self, filepath: str, src_lang: str, tgt_lang: str, tm_id: str,
|
|
433
|
-
strip_variants: bool = False) -> int:
|
|
436
|
+
strip_variants: bool = False, progress_callback=None) -> int:
|
|
434
437
|
"""
|
|
435
|
-
Internal: Load TMX content into database
|
|
438
|
+
Internal: Load TMX content into database with chunked processing
|
|
436
439
|
|
|
437
440
|
Args:
|
|
438
441
|
filepath: Path to TMX file
|
|
@@ -440,12 +443,24 @@ class TMDatabase:
|
|
|
440
443
|
tgt_lang: Target target language code
|
|
441
444
|
tm_id: TM identifier
|
|
442
445
|
strip_variants: If True, match base languages ignoring regional variants
|
|
446
|
+
progress_callback: Optional callback function(current, total, message) for progress updates
|
|
443
447
|
"""
|
|
444
448
|
loaded_count = 0
|
|
449
|
+
chunk_size = 1000 # Process in chunks for responsiveness
|
|
450
|
+
chunk_buffer = []
|
|
445
451
|
|
|
446
452
|
try:
|
|
453
|
+
# First pass: count total TUs for progress bar
|
|
454
|
+
if progress_callback:
|
|
455
|
+
progress_callback(0, 0, "Counting translation units...")
|
|
456
|
+
|
|
447
457
|
tree = ET.parse(filepath)
|
|
448
458
|
root = tree.getroot()
|
|
459
|
+
total_tus = len(root.findall('.//tu'))
|
|
460
|
+
|
|
461
|
+
if progress_callback:
|
|
462
|
+
progress_callback(0, total_tus, f"Processing 0 / {total_tus:,} entries...")
|
|
463
|
+
|
|
449
464
|
xml_ns = "http://www.w3.org/XML/1998/namespace"
|
|
450
465
|
|
|
451
466
|
# Normalize language codes
|
|
@@ -458,6 +473,7 @@ class TMDatabase:
|
|
|
458
473
|
src_base = get_base_lang_code(src_lang_normalized)
|
|
459
474
|
tgt_base = get_base_lang_code(tgt_lang_normalized)
|
|
460
475
|
|
|
476
|
+
processed = 0
|
|
461
477
|
for tu in root.findall('.//tu'):
|
|
462
478
|
src_text, tgt_text = None, None
|
|
463
479
|
|
|
@@ -488,14 +504,43 @@ class TMDatabase:
|
|
|
488
504
|
tgt_text = text
|
|
489
505
|
|
|
490
506
|
if src_text and tgt_text:
|
|
507
|
+
chunk_buffer.append((src_text, tgt_text))
|
|
508
|
+
loaded_count += 1
|
|
509
|
+
|
|
510
|
+
# Process chunk when buffer is full
|
|
511
|
+
if len(chunk_buffer) >= chunk_size:
|
|
512
|
+
for src, tgt in chunk_buffer:
|
|
513
|
+
self.db.add_translation_unit(
|
|
514
|
+
source=src,
|
|
515
|
+
target=tgt,
|
|
516
|
+
source_lang=src_lang_normalized,
|
|
517
|
+
target_lang=tgt_lang_normalized,
|
|
518
|
+
tm_id=tm_id
|
|
519
|
+
)
|
|
520
|
+
chunk_buffer.clear()
|
|
521
|
+
|
|
522
|
+
# Update progress
|
|
523
|
+
if progress_callback:
|
|
524
|
+
progress_callback(processed + 1, total_tus,
|
|
525
|
+
f"Processing {loaded_count:,} / {total_tus:,} entries...")
|
|
526
|
+
|
|
527
|
+
processed += 1
|
|
528
|
+
|
|
529
|
+
# Process remaining entries in buffer
|
|
530
|
+
if chunk_buffer:
|
|
531
|
+
for src, tgt in chunk_buffer:
|
|
491
532
|
self.db.add_translation_unit(
|
|
492
|
-
source=
|
|
493
|
-
target=
|
|
533
|
+
source=src,
|
|
534
|
+
target=tgt,
|
|
494
535
|
source_lang=src_lang_normalized,
|
|
495
536
|
target_lang=tgt_lang_normalized,
|
|
496
537
|
tm_id=tm_id
|
|
497
538
|
)
|
|
498
|
-
|
|
539
|
+
chunk_buffer.clear()
|
|
540
|
+
|
|
541
|
+
# Final progress update
|
|
542
|
+
if progress_callback:
|
|
543
|
+
progress_callback(total_tus, total_tus, f"Completed: {loaded_count:,} entries imported")
|
|
499
544
|
|
|
500
545
|
return loaded_count
|
|
501
546
|
except Exception as e:
|
|
@@ -30,7 +30,7 @@ class UnifiedPromptLibrary:
|
|
|
30
30
|
Initialize the Unified Prompt Library.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
|
-
library_dir: Path to unified library directory (user_data/
|
|
33
|
+
library_dir: Path to unified library directory (user_data/prompt_library)
|
|
34
34
|
log_callback: Function to call for logging messages
|
|
35
35
|
"""
|
|
36
36
|
self.library_dir = Path(library_dir) if library_dir else None
|
|
@@ -544,8 +544,8 @@ class UnifiedPromptManagerQt:
|
|
|
544
544
|
self.log = parent_app.log if hasattr(parent_app, 'log') else print
|
|
545
545
|
|
|
546
546
|
# Paths
|
|
547
|
-
self.prompt_library_dir = self.user_data_path / "
|
|
548
|
-
# Use
|
|
547
|
+
self.prompt_library_dir = self.user_data_path / "prompt_library"
|
|
548
|
+
# Use prompt_library directly, not prompt_library/Library
|
|
549
549
|
self.unified_library_dir = self.prompt_library_dir
|
|
550
550
|
|
|
551
551
|
# Run migration if needed
|
|
@@ -579,7 +579,7 @@ class UnifiedPromptManagerQt:
|
|
|
579
579
|
self._cached_document_markdown: Optional[str] = None # Cached markdown conversion of current document
|
|
580
580
|
|
|
581
581
|
# Initialize Attachment Manager
|
|
582
|
-
ai_assistant_dir = self.user_data_path / "
|
|
582
|
+
ai_assistant_dir = self.user_data_path / "ai_assistant"
|
|
583
583
|
self.attachment_manager = AttachmentManager(
|
|
584
584
|
base_dir=str(ai_assistant_dir),
|
|
585
585
|
log_callback=self.log_message
|
|
@@ -694,7 +694,7 @@ class UnifiedPromptManagerQt:
|
|
|
694
694
|
layout.setSpacing(5)
|
|
695
695
|
|
|
696
696
|
# Title
|
|
697
|
-
title = QLabel("
|
|
697
|
+
title = QLabel("📝 Prompt Manager")
|
|
698
698
|
title.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1976D2;")
|
|
699
699
|
layout.addWidget(title, 0)
|
|
700
700
|
|
|
@@ -1546,7 +1546,7 @@ class UnifiedPromptManagerQt:
|
|
|
1546
1546
|
|
|
1547
1547
|
def _create_active_config_panel(self) -> QGroupBox:
|
|
1548
1548
|
"""Create active prompt configuration panel"""
|
|
1549
|
-
group = QGroupBox("Active
|
|
1549
|
+
group = QGroupBox("Active Prompt")
|
|
1550
1550
|
layout = QVBoxLayout()
|
|
1551
1551
|
|
|
1552
1552
|
# Mode info (read-only, auto-selected)
|
|
@@ -1704,10 +1704,10 @@ class UnifiedPromptManagerQt:
|
|
|
1704
1704
|
self.editor_quickmenu_label_input.setPlaceholderText("Label shown in QuickMenu")
|
|
1705
1705
|
quickmenu_layout.addWidget(self.editor_quickmenu_label_input, 2)
|
|
1706
1706
|
|
|
1707
|
-
self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in
|
|
1707
|
+
self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in QuickMenu (in-app)")
|
|
1708
1708
|
quickmenu_layout.addWidget(self.editor_quickmenu_in_grid_cb, 2)
|
|
1709
1709
|
|
|
1710
|
-
self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in
|
|
1710
|
+
self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in QuickMenu (global)")
|
|
1711
1711
|
quickmenu_layout.addWidget(self.editor_quickmenu_in_quickmenu_cb, 1)
|
|
1712
1712
|
|
|
1713
1713
|
layout.addLayout(quickmenu_layout)
|
|
@@ -1760,26 +1760,7 @@ class UnifiedPromptManagerQt:
|
|
|
1760
1760
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsDragEnabled)
|
|
1761
1761
|
favorites_root.addChild(item)
|
|
1762
1762
|
|
|
1763
|
-
#
|
|
1764
|
-
quick_run_root = QTreeWidgetItem(["⚡ QuickMenu"])
|
|
1765
|
-
# Special node: not draggable/droppable
|
|
1766
|
-
quick_run_root.setData(0, Qt.ItemDataRole.UserRole, {'type': 'special', 'kind': 'quick_run'})
|
|
1767
|
-
quick_run_root.setExpanded(False)
|
|
1768
|
-
font = quick_run_root.font(0)
|
|
1769
|
-
font.setBold(True)
|
|
1770
|
-
quick_run_root.setFont(0, font)
|
|
1771
|
-
self.tree_widget.addTopLevelItem(quick_run_root)
|
|
1772
|
-
|
|
1773
|
-
quickmenu_items = self.library.get_quickmenu_prompts() if hasattr(self.library, 'get_quickmenu_prompts') else self.library.get_quick_run_prompts()
|
|
1774
|
-
self.log_message(f"🔍 DEBUG: QuickMenu count: {len(quickmenu_items)}")
|
|
1775
|
-
for path, label in quickmenu_items:
|
|
1776
|
-
item = QTreeWidgetItem([label])
|
|
1777
|
-
item.setData(0, Qt.ItemDataRole.UserRole, {'type': 'prompt', 'path': path})
|
|
1778
|
-
# Quick Run entries are shortcuts, but allow dragging to move the actual prompt file.
|
|
1779
|
-
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsDragEnabled)
|
|
1780
|
-
quick_run_root.addChild(item)
|
|
1781
|
-
|
|
1782
|
-
# Library folders
|
|
1763
|
+
# Library folders (QuickMenu parent folder removed - folder hierarchy now defines menu structure)
|
|
1783
1764
|
self.log_message(f"🔍 DEBUG: Building tree from {self.unified_library_dir}")
|
|
1784
1765
|
self._build_tree_recursive(None, self.unified_library_dir, "")
|
|
1785
1766
|
|
|
@@ -3548,7 +3529,7 @@ Output complete ACTION."""
|
|
|
3548
3529
|
"""
|
|
3549
3530
|
Save the markdown conversion of the current document.
|
|
3550
3531
|
|
|
3551
|
-
Saves to: user_data_private/
|
|
3532
|
+
Saves to: user_data_private/ai_assistant/current_document/
|
|
3552
3533
|
|
|
3553
3534
|
Args:
|
|
3554
3535
|
original_path: Original document file path
|
|
@@ -3556,7 +3537,7 @@ Output complete ACTION."""
|
|
|
3556
3537
|
"""
|
|
3557
3538
|
try:
|
|
3558
3539
|
# Create directory for current document markdown
|
|
3559
|
-
doc_dir = self.user_data_path / "
|
|
3540
|
+
doc_dir = self.user_data_path / "ai_assistant" / "current_document"
|
|
3560
3541
|
doc_dir.mkdir(parents=True, exist_ok=True)
|
|
3561
3542
|
|
|
3562
3543
|
# Create filename based on original
|