supervertaler 1.9.194__py3-none-any.whl → 1.9.198__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 +295 -62
- modules/llm_clients.py +58 -33
- modules/unified_prompt_manager_qt.py +22 -1
- {supervertaler-1.9.194.dist-info → supervertaler-1.9.198.dist-info}/METADATA +1 -1
- {supervertaler-1.9.194.dist-info → supervertaler-1.9.198.dist-info}/RECORD +10 -10
- /modules/{mt_quick_popup.py → quicktrans.py} +0 -0
- {supervertaler-1.9.194.dist-info → supervertaler-1.9.198.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.194.dist-info → supervertaler-1.9.198.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.194.dist-info → supervertaler-1.9.198.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.194.dist-info → supervertaler-1.9.198.dist-info}/top_level.txt +0 -0
Supervertaler.py
CHANGED
|
@@ -32,9 +32,9 @@ License: MIT
|
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
# Version Information.
|
|
35
|
-
__version__ = "1.9.
|
|
35
|
+
__version__ = "1.9.198"
|
|
36
36
|
__phase__ = "0.9"
|
|
37
|
-
__release_date__ = "2026-02-
|
|
37
|
+
__release_date__ = "2026-02-02"
|
|
38
38
|
__edition__ = "Qt"
|
|
39
39
|
|
|
40
40
|
import sys
|
|
@@ -391,10 +391,10 @@ def runs_to_tagged_text(paragraphs) -> str:
|
|
|
391
391
|
def strip_formatting_tags(text: str) -> str:
|
|
392
392
|
"""
|
|
393
393
|
Remove HTML formatting tags from text, leaving plain text.
|
|
394
|
-
|
|
394
|
+
|
|
395
395
|
Args:
|
|
396
396
|
text: Text with HTML tags like <b>, </b>, <i>, </i>, <u>, </u>
|
|
397
|
-
|
|
397
|
+
|
|
398
398
|
Returns:
|
|
399
399
|
Plain text without tags
|
|
400
400
|
"""
|
|
@@ -403,6 +403,104 @@ def strip_formatting_tags(text: str) -> str:
|
|
|
403
403
|
return re.sub(r'</?[biu]>', '', text)
|
|
404
404
|
|
|
405
405
|
|
|
406
|
+
def strip_outer_wrapping_tags(text: str) -> tuple:
|
|
407
|
+
"""
|
|
408
|
+
Strip outer wrapping tags from text if the entire segment is wrapped in a single tag pair.
|
|
409
|
+
|
|
410
|
+
This handles structural tags like <li-o>...</li-o>, <p>...</p>, <td>...</td>, etc.
|
|
411
|
+
Inner formatting tags like <b>...</b> are preserved.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
text: Text that may be wrapped in outer structural tags
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Tuple of (stripped_text, tag_name) where tag_name is the outer tag that was stripped,
|
|
418
|
+
or (original_text, None) if no outer wrapping tag was found
|
|
419
|
+
|
|
420
|
+
Examples:
|
|
421
|
+
"<li-o>Hello <b>world</b></li-o>" -> ("Hello <b>world</b>", "li-o")
|
|
422
|
+
"<p>Simple text</p>" -> ("Simple text", "p")
|
|
423
|
+
"No tags here" -> ("No tags here", None)
|
|
424
|
+
"<b>Bold text</b>" -> ("<b>Bold text</b>", None) # <b> is formatting, not structural
|
|
425
|
+
"""
|
|
426
|
+
import re
|
|
427
|
+
|
|
428
|
+
if not text or not text.strip():
|
|
429
|
+
return (text, None)
|
|
430
|
+
|
|
431
|
+
text = text.strip()
|
|
432
|
+
|
|
433
|
+
# Structural tags that wrap entire segments (not inline formatting)
|
|
434
|
+
structural_tags = {
|
|
435
|
+
'li-o', 'li-b', 'li', 'p', 'td', 'th', 'tr', 'div', 'span',
|
|
436
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'title', 'caption',
|
|
437
|
+
'blockquote', 'pre', 'code', 'dt', 'dd', 'header', 'footer',
|
|
438
|
+
'article', 'section', 'aside', 'nav', 'main', 'figure', 'figcaption'
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
# Pattern to match opening tag at start: <tag> or <tag attr="...">
|
|
442
|
+
opening_pattern = r'^<([a-zA-Z][a-zA-Z0-9-]*)(?:\s+[^>]*)?>(.*)$'
|
|
443
|
+
opening_match = re.match(opening_pattern, text, re.DOTALL)
|
|
444
|
+
|
|
445
|
+
if not opening_match:
|
|
446
|
+
return (text, None)
|
|
447
|
+
|
|
448
|
+
tag_name = opening_match.group(1).lower()
|
|
449
|
+
rest = opening_match.group(2)
|
|
450
|
+
|
|
451
|
+
# Only strip structural tags, not inline formatting like <b>, <i>, <u>, <em>, <strong>
|
|
452
|
+
if tag_name not in structural_tags:
|
|
453
|
+
return (text, None)
|
|
454
|
+
|
|
455
|
+
# Check if text ends with matching closing tag
|
|
456
|
+
closing_pattern = rf'^(.*)</{re.escape(tag_name)}>$'
|
|
457
|
+
closing_match = re.match(closing_pattern, rest, re.DOTALL | re.IGNORECASE)
|
|
458
|
+
|
|
459
|
+
if not closing_match:
|
|
460
|
+
return (text, None)
|
|
461
|
+
|
|
462
|
+
inner_content = closing_match.group(1)
|
|
463
|
+
|
|
464
|
+
# Verify this is truly a wrapping pair (no other occurrences of this tag inside)
|
|
465
|
+
# Count opening and closing tags of this type in the inner content
|
|
466
|
+
inner_opening_count = len(re.findall(rf'<{re.escape(tag_name)}(?:\s+[^>]*)?>',
|
|
467
|
+
inner_content, re.IGNORECASE))
|
|
468
|
+
inner_closing_count = len(re.findall(rf'</{re.escape(tag_name)}>', inner_content, re.IGNORECASE))
|
|
469
|
+
|
|
470
|
+
# If there are nested tags of the same type, don't strip
|
|
471
|
+
if inner_opening_count > 0 or inner_closing_count > 0:
|
|
472
|
+
return (text, None)
|
|
473
|
+
|
|
474
|
+
return (inner_content, tag_name)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def get_list_prefix_for_tag(tag_name: str, list_position: int = 1) -> str:
|
|
478
|
+
"""
|
|
479
|
+
Get visual prefix for list items in WYSIWYG mode.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
tag_name: The outer wrapping tag name (e.g., 'li-o', 'li-b', 'li')
|
|
483
|
+
list_position: Position within the list (1-indexed) for ordered lists
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
Visual prefix string: "1. ", "2. ", "• ", etc., or "" if not a list tag
|
|
487
|
+
"""
|
|
488
|
+
if not tag_name:
|
|
489
|
+
return ""
|
|
490
|
+
|
|
491
|
+
tag_lower = tag_name.lower()
|
|
492
|
+
|
|
493
|
+
# Ordered list items
|
|
494
|
+
if tag_lower == 'li-o':
|
|
495
|
+
return f"{list_position}. "
|
|
496
|
+
|
|
497
|
+
# Unordered/bullet list items
|
|
498
|
+
if tag_lower in ('li-b', 'li'):
|
|
499
|
+
return "• "
|
|
500
|
+
|
|
501
|
+
return ""
|
|
502
|
+
|
|
503
|
+
|
|
406
504
|
def has_formatting_tags(text: str) -> bool:
|
|
407
505
|
"""
|
|
408
506
|
Check if text contains any formatting tags.
|
|
@@ -6222,7 +6320,10 @@ class SupervertalerQt(QMainWindow):
|
|
|
6222
6320
|
self.enable_alternating_row_colors = True # Enable alternating row colors by default
|
|
6223
6321
|
self.even_row_color = '#FFFFFF' # White for even rows
|
|
6224
6322
|
self.odd_row_color = '#F0F0F0' # Light gray for odd rows
|
|
6225
|
-
|
|
6323
|
+
|
|
6324
|
+
# Hide outer wrapping tags in grid display (e.g. <li-o>...</li-o>)
|
|
6325
|
+
self.hide_outer_wrapping_tags = False # Disabled by default
|
|
6326
|
+
|
|
6226
6327
|
# Termbase highlight style settings
|
|
6227
6328
|
self.termbase_highlight_style = 'semibold' # 'background', 'dotted', or 'semibold'
|
|
6228
6329
|
self.termbase_dotted_color = '#808080' # Medium gray for dotted underline (more visible)
|
|
@@ -7447,7 +7548,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
7447
7548
|
return
|
|
7448
7549
|
|
|
7449
7550
|
# Import and create the popup
|
|
7450
|
-
from modules.
|
|
7551
|
+
from modules.quicktrans import MTQuickPopup
|
|
7451
7552
|
|
|
7452
7553
|
# Create popup
|
|
7453
7554
|
popup = MTQuickPopup(
|
|
@@ -17690,14 +17791,41 @@ class SupervertalerQt(QMainWindow):
|
|
|
17690
17791
|
|
|
17691
17792
|
grid_group.setLayout(grid_layout)
|
|
17692
17793
|
layout.addWidget(grid_group)
|
|
17693
|
-
|
|
17694
|
-
#
|
|
17695
|
-
|
|
17794
|
+
|
|
17795
|
+
# Grid Display Options section
|
|
17796
|
+
grid_display_group = QGroupBox("📊 Grid Display Options")
|
|
17797
|
+
grid_display_layout = QVBoxLayout()
|
|
17798
|
+
|
|
17799
|
+
grid_display_info = QLabel(
|
|
17800
|
+
"Configure how content is displayed in the translation grid."
|
|
17801
|
+
)
|
|
17802
|
+
grid_display_info.setStyleSheet("font-size: 8pt; padding: 8px; border-radius: 2px;")
|
|
17803
|
+
grid_display_info.setWordWrap(True)
|
|
17804
|
+
grid_display_layout.addWidget(grid_display_info)
|
|
17805
|
+
|
|
17806
|
+
# Hide wrapping tags checkbox
|
|
17807
|
+
hide_wrapping_tags_layout = QHBoxLayout()
|
|
17808
|
+
hide_wrapping_tags_check = CheckmarkCheckBox("Hide outer wrapping tags in grid (e.g. <li-o>...</li-o>)")
|
|
17809
|
+
hide_wrapping_tags_check.setChecked(font_settings.get('hide_outer_wrapping_tags', False))
|
|
17810
|
+
hide_wrapping_tags_check.setToolTip(
|
|
17811
|
+
"When enabled, structural tags that wrap the entire segment (like <li-o>, <p>, <td>) are hidden in the grid.\n"
|
|
17812
|
+
"The segment type is already shown in the Type column, so this reduces visual clutter.\n"
|
|
17813
|
+
"Inner formatting tags like <b>bold</b> are still shown.\n"
|
|
17814
|
+
"This affects the Source column display only - the Target column keeps tags for editing."
|
|
17815
|
+
)
|
|
17816
|
+
hide_wrapping_tags_layout.addWidget(hide_wrapping_tags_check)
|
|
17817
|
+
hide_wrapping_tags_layout.addStretch()
|
|
17818
|
+
grid_display_layout.addLayout(hide_wrapping_tags_layout)
|
|
17819
|
+
|
|
17820
|
+
grid_display_group.setLayout(grid_display_layout)
|
|
17821
|
+
layout.addWidget(grid_display_group)
|
|
17822
|
+
|
|
17823
|
+
# Match Panel & Tag Colors section
|
|
17824
|
+
results_group = QGroupBox("📋 Match Panel && Tag Colors")
|
|
17696
17825
|
results_layout = QVBoxLayout()
|
|
17697
17826
|
|
|
17698
17827
|
results_size_info = QLabel(
|
|
17699
|
-
"Set
|
|
17700
|
-
"You can also adjust these using View menu → Translation Results Pane."
|
|
17828
|
+
"Set font sizes for the Match Panel (TM/termbase matches) and tag colors."
|
|
17701
17829
|
)
|
|
17702
17830
|
results_size_info.setStyleSheet("font-size: 8pt; padding: 8px; border-radius: 2px;")
|
|
17703
17831
|
results_size_info.setWordWrap(True)
|
|
@@ -17737,7 +17865,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
17737
17865
|
show_tags_layout.addWidget(show_tags_check)
|
|
17738
17866
|
show_tags_layout.addStretch()
|
|
17739
17867
|
results_layout.addLayout(show_tags_layout)
|
|
17740
|
-
|
|
17868
|
+
|
|
17741
17869
|
# Tag highlight color picker
|
|
17742
17870
|
tag_color_layout = QHBoxLayout()
|
|
17743
17871
|
tag_color_layout.addWidget(QLabel("Tag Highlight Color:"))
|
|
@@ -18295,7 +18423,8 @@ class SupervertalerQt(QMainWindow):
|
|
|
18295
18423
|
grid_font_spin, match_font_spin, compare_font_spin, show_tags_check, tag_color_btn,
|
|
18296
18424
|
alt_colors_check, even_color_btn, odd_color_btn, invisible_char_color_btn, grid_font_family_combo,
|
|
18297
18425
|
termview_font_family_combo, termview_font_spin, termview_bold_check,
|
|
18298
|
-
border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check
|
|
18426
|
+
border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check,
|
|
18427
|
+
hide_wrapping_tags_check
|
|
18299
18428
|
)
|
|
18300
18429
|
|
|
18301
18430
|
save_btn.clicked.connect(save_view_settings_with_scale)
|
|
@@ -18307,14 +18436,15 @@ class SupervertalerQt(QMainWindow):
|
|
|
18307
18436
|
|
|
18308
18437
|
def _create_voice_dictation_settings_tab(self):
|
|
18309
18438
|
"""Create Supervoice Settings tab content with Voice Commands"""
|
|
18310
|
-
from PyQt6.QtWidgets import (QGroupBox, QPushButton, QComboBox, QSpinBox,
|
|
18439
|
+
from PyQt6.QtWidgets import (QGroupBox, QPushButton, QComboBox, QSpinBox,
|
|
18311
18440
|
QTableWidget, QTableWidgetItem, QHeaderView,
|
|
18312
|
-
QAbstractItemView, QCheckBox)
|
|
18441
|
+
QAbstractItemView, QCheckBox, QSplitter)
|
|
18442
|
+
from modules.keyboard_shortcuts_widget import CheckmarkCheckBox
|
|
18313
18443
|
|
|
18314
18444
|
tab = QWidget()
|
|
18315
|
-
|
|
18316
|
-
|
|
18317
|
-
|
|
18445
|
+
main_layout = QVBoxLayout(tab)
|
|
18446
|
+
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
18447
|
+
main_layout.setSpacing(15)
|
|
18318
18448
|
|
|
18319
18449
|
# Load current dictation settings
|
|
18320
18450
|
dictation_settings = self.load_dictation_settings()
|
|
@@ -18327,19 +18457,33 @@ class SupervertalerQt(QMainWindow):
|
|
|
18327
18457
|
header_info.setTextFormat(Qt.TextFormat.RichText)
|
|
18328
18458
|
header_info.setStyleSheet("font-size: 9pt; color: #444; padding: 10px; background-color: #E3F2FD; border-radius: 4px;")
|
|
18329
18459
|
header_info.setWordWrap(True)
|
|
18330
|
-
|
|
18460
|
+
main_layout.addWidget(header_info)
|
|
18331
18461
|
|
|
18332
|
-
# ===== Voice Commands
|
|
18333
|
-
|
|
18334
|
-
|
|
18462
|
+
# ===== Two-column layout: Left = Settings, Right = Voice Commands Table =====
|
|
18463
|
+
columns_layout = QHBoxLayout()
|
|
18464
|
+
columns_layout.setSpacing(15)
|
|
18465
|
+
|
|
18466
|
+
# --- LEFT COLUMN: Settings ---
|
|
18467
|
+
left_column = QVBoxLayout()
|
|
18468
|
+
left_column.setSpacing(15)
|
|
18335
18469
|
|
|
18336
|
-
# Enable voice commands checkbox
|
|
18337
|
-
voice_cmd_enabled =
|
|
18470
|
+
# Enable voice commands checkbox (green checkmark style)
|
|
18471
|
+
voice_cmd_enabled = CheckmarkCheckBox("Enable voice commands (spoken phrases trigger actions)")
|
|
18338
18472
|
voice_cmd_enabled.setChecked(dictation_settings.get('voice_commands_enabled', True))
|
|
18339
18473
|
voice_cmd_enabled.setToolTip("When enabled, spoken phrases like 'confirm' or 'next segment' will execute commands instead of being inserted as text")
|
|
18340
|
-
|
|
18474
|
+
left_column.addWidget(voice_cmd_enabled)
|
|
18341
18475
|
self.voice_commands_enabled_checkbox = voice_cmd_enabled
|
|
18342
18476
|
|
|
18477
|
+
# Create a layout variable for settings sections to be added below
|
|
18478
|
+
layout = left_column
|
|
18479
|
+
|
|
18480
|
+
# --- RIGHT COLUMN: Voice Commands Table ---
|
|
18481
|
+
right_column = QVBoxLayout()
|
|
18482
|
+
right_column.setSpacing(10)
|
|
18483
|
+
|
|
18484
|
+
commands_group = QGroupBox("🗣️ Voice Commands (Talon-style)")
|
|
18485
|
+
commands_layout = QVBoxLayout()
|
|
18486
|
+
|
|
18343
18487
|
commands_info = QLabel(
|
|
18344
18488
|
"Voice commands let you control Supervertaler by voice. Say a phrase to execute an action.\n"
|
|
18345
18489
|
"If no command matches, the spoken text is inserted as dictation."
|
|
@@ -18358,38 +18502,47 @@ class SupervertalerQt(QMainWindow):
|
|
|
18358
18502
|
self.voice_commands_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
|
|
18359
18503
|
self.voice_commands_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
|
18360
18504
|
self.voice_commands_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
|
18361
|
-
self.voice_commands_table.setMinimumHeight(
|
|
18362
|
-
|
|
18363
|
-
|
|
18505
|
+
self.voice_commands_table.setMinimumHeight(350)
|
|
18506
|
+
|
|
18364
18507
|
# Populate table with current commands
|
|
18365
18508
|
self._populate_voice_commands_table()
|
|
18366
18509
|
commands_layout.addWidget(self.voice_commands_table)
|
|
18367
18510
|
|
|
18368
18511
|
# Command buttons
|
|
18369
18512
|
cmd_btn_layout = QHBoxLayout()
|
|
18370
|
-
|
|
18371
|
-
add_cmd_btn = QPushButton("➕ Add
|
|
18513
|
+
|
|
18514
|
+
add_cmd_btn = QPushButton("➕ Add")
|
|
18372
18515
|
add_cmd_btn.clicked.connect(self._add_voice_command)
|
|
18373
18516
|
cmd_btn_layout.addWidget(add_cmd_btn)
|
|
18374
|
-
|
|
18375
|
-
edit_cmd_btn = QPushButton("✏️ Edit
|
|
18517
|
+
|
|
18518
|
+
edit_cmd_btn = QPushButton("✏️ Edit")
|
|
18376
18519
|
edit_cmd_btn.clicked.connect(self._edit_voice_command)
|
|
18377
18520
|
cmd_btn_layout.addWidget(edit_cmd_btn)
|
|
18378
|
-
|
|
18379
|
-
remove_cmd_btn = QPushButton("🗑️ Remove
|
|
18521
|
+
|
|
18522
|
+
remove_cmd_btn = QPushButton("🗑️ Remove")
|
|
18380
18523
|
remove_cmd_btn.clicked.connect(self._remove_voice_command)
|
|
18381
18524
|
cmd_btn_layout.addWidget(remove_cmd_btn)
|
|
18382
|
-
|
|
18525
|
+
|
|
18383
18526
|
cmd_btn_layout.addStretch()
|
|
18384
|
-
|
|
18385
|
-
reset_cmd_btn = QPushButton("🔄 Reset
|
|
18527
|
+
|
|
18528
|
+
reset_cmd_btn = QPushButton("🔄 Reset")
|
|
18386
18529
|
reset_cmd_btn.clicked.connect(self._reset_voice_commands)
|
|
18387
18530
|
cmd_btn_layout.addWidget(reset_cmd_btn)
|
|
18388
|
-
|
|
18531
|
+
|
|
18389
18532
|
commands_layout.addLayout(cmd_btn_layout)
|
|
18390
18533
|
|
|
18391
18534
|
commands_group.setLayout(commands_layout)
|
|
18392
|
-
|
|
18535
|
+
right_column.addWidget(commands_group)
|
|
18536
|
+
right_column.addStretch()
|
|
18537
|
+
|
|
18538
|
+
# Add columns to the two-column layout
|
|
18539
|
+
left_widget = QWidget()
|
|
18540
|
+
left_widget.setLayout(left_column)
|
|
18541
|
+
right_widget = QWidget()
|
|
18542
|
+
right_widget.setLayout(right_column)
|
|
18543
|
+
|
|
18544
|
+
columns_layout.addWidget(left_widget, stretch=1)
|
|
18545
|
+
columns_layout.addWidget(right_widget, stretch=1)
|
|
18393
18546
|
|
|
18394
18547
|
# ===== Always-On Mode Section =====
|
|
18395
18548
|
alwayson_group = QGroupBox("🎧 Always-On Listening Mode")
|
|
@@ -18552,7 +18705,13 @@ class SupervertalerQt(QMainWindow):
|
|
|
18552
18705
|
ahk_group.setLayout(ahk_layout)
|
|
18553
18706
|
layout.addWidget(ahk_group)
|
|
18554
18707
|
|
|
18555
|
-
#
|
|
18708
|
+
# Add stretch to left column to push content up
|
|
18709
|
+
layout.addStretch()
|
|
18710
|
+
|
|
18711
|
+
# Add two-column layout to main layout
|
|
18712
|
+
main_layout.addLayout(columns_layout, stretch=1)
|
|
18713
|
+
|
|
18714
|
+
# Save button (full width, below the two columns)
|
|
18556
18715
|
save_btn = QPushButton("💾 Save Supervoice Settings")
|
|
18557
18716
|
save_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 10px; border: none; outline: none;")
|
|
18558
18717
|
save_btn.clicked.connect(lambda: self._save_voice_settings(
|
|
@@ -18561,15 +18720,13 @@ class SupervertalerQt(QMainWindow):
|
|
|
18561
18720
|
lang_combo.currentText(),
|
|
18562
18721
|
voice_cmd_enabled.isChecked()
|
|
18563
18722
|
))
|
|
18564
|
-
|
|
18723
|
+
main_layout.addWidget(save_btn)
|
|
18565
18724
|
|
|
18566
18725
|
# Store references
|
|
18567
18726
|
self.dictation_model_combo = model_combo
|
|
18568
18727
|
self.dictation_duration_spin = duration_spin
|
|
18569
18728
|
self.dictation_lang_combo = lang_combo
|
|
18570
18729
|
|
|
18571
|
-
layout.addStretch()
|
|
18572
|
-
|
|
18573
18730
|
return tab
|
|
18574
18731
|
|
|
18575
18732
|
def _populate_voice_commands_table(self):
|
|
@@ -20198,7 +20355,8 @@ class SupervertalerQt(QMainWindow):
|
|
|
20198
20355
|
def _save_view_settings_from_ui(self, grid_spin, match_spin, compare_spin, show_tags_check=None, tag_color_btn=None,
|
|
20199
20356
|
alt_colors_check=None, even_color_btn=None, odd_color_btn=None, invisible_char_color_btn=None,
|
|
20200
20357
|
grid_font_family_combo=None, termview_font_family_combo=None, termview_font_spin=None, termview_bold_check=None,
|
|
20201
|
-
border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None
|
|
20358
|
+
border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None,
|
|
20359
|
+
hide_wrapping_tags_check=None):
|
|
20202
20360
|
"""Save view settings from UI"""
|
|
20203
20361
|
# CRITICAL: Suppress TM saves during view settings update
|
|
20204
20362
|
# Grid operations (setStyleSheet, rehighlight, etc.) can trigger textChanged events
|
|
@@ -20211,7 +20369,8 @@ class SupervertalerQt(QMainWindow):
|
|
|
20211
20369
|
grid_spin, match_spin, compare_spin, show_tags_check, tag_color_btn,
|
|
20212
20370
|
alt_colors_check, even_color_btn, odd_color_btn, invisible_char_color_btn,
|
|
20213
20371
|
grid_font_family_combo, termview_font_family_combo, termview_font_spin, termview_bold_check,
|
|
20214
|
-
border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check
|
|
20372
|
+
border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check,
|
|
20373
|
+
hide_wrapping_tags_check
|
|
20215
20374
|
)
|
|
20216
20375
|
finally:
|
|
20217
20376
|
self._suppress_target_change_handlers = previous_suppression
|
|
@@ -20219,16 +20378,18 @@ class SupervertalerQt(QMainWindow):
|
|
|
20219
20378
|
def _save_view_settings_from_ui_impl(self, grid_spin, match_spin, compare_spin, show_tags_check=None, tag_color_btn=None,
|
|
20220
20379
|
alt_colors_check=None, even_color_btn=None, odd_color_btn=None, invisible_char_color_btn=None,
|
|
20221
20380
|
grid_font_family_combo=None, termview_font_family_combo=None, termview_font_spin=None, termview_bold_check=None,
|
|
20222
|
-
border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None
|
|
20381
|
+
border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None,
|
|
20382
|
+
hide_wrapping_tags_check=None):
|
|
20223
20383
|
"""Implementation of save view settings (called with TM saves suppressed)"""
|
|
20224
|
-
|
|
20225
|
-
|
|
20226
|
-
|
|
20384
|
+
# Load existing settings first to preserve all values, then update with new ones
|
|
20385
|
+
general_settings = self.load_general_settings()
|
|
20386
|
+
general_settings.update({
|
|
20387
|
+
'auto_propagate_exact_matches': self.auto_propagate_exact_matches,
|
|
20227
20388
|
'grid_font_size': grid_spin.value(),
|
|
20228
20389
|
'results_match_font_size': match_spin.value(),
|
|
20229
20390
|
'results_compare_font_size': compare_spin.value(),
|
|
20230
|
-
'enable_tm_termbase_matching': self.enable_tm_matching
|
|
20231
|
-
}
|
|
20391
|
+
'enable_tm_termbase_matching': self.enable_tm_matching
|
|
20392
|
+
})
|
|
20232
20393
|
|
|
20233
20394
|
# Add tabs above grid setting if provided
|
|
20234
20395
|
if tabs_above_check is not None:
|
|
@@ -20268,7 +20429,12 @@ class SupervertalerQt(QMainWindow):
|
|
|
20268
20429
|
if invisible_char_color:
|
|
20269
20430
|
general_settings['invisible_char_color'] = invisible_char_color
|
|
20270
20431
|
self.invisible_char_color = invisible_char_color
|
|
20271
|
-
|
|
20432
|
+
|
|
20433
|
+
# Add hide outer wrapping tags setting if provided
|
|
20434
|
+
if hide_wrapping_tags_check is not None:
|
|
20435
|
+
general_settings['hide_outer_wrapping_tags'] = hide_wrapping_tags_check.isChecked()
|
|
20436
|
+
self.hide_outer_wrapping_tags = hide_wrapping_tags_check.isChecked()
|
|
20437
|
+
|
|
20272
20438
|
# Add focus border settings if provided
|
|
20273
20439
|
if border_color_btn is not None:
|
|
20274
20440
|
border_color = border_color_btn.property('selected_color')
|
|
@@ -20432,7 +20598,11 @@ class SupervertalerQt(QMainWindow):
|
|
|
20432
20598
|
self.refresh_grid_tag_colors()
|
|
20433
20599
|
# Also refresh row colors
|
|
20434
20600
|
self.apply_alternating_row_colors()
|
|
20435
|
-
|
|
20601
|
+
|
|
20602
|
+
# Refresh source column if hide_outer_wrapping_tags setting changed
|
|
20603
|
+
if hide_wrapping_tags_check is not None and hasattr(self, 'table') and self.table is not None:
|
|
20604
|
+
self._refresh_source_column_display()
|
|
20605
|
+
|
|
20436
20606
|
self.log("✓ View settings saved and applied")
|
|
20437
20607
|
# Use explicit QMessageBox instance to ensure proper dialog closing
|
|
20438
20608
|
msg = QMessageBox(self)
|
|
@@ -29773,30 +29943,62 @@ class SupervertalerQt(QMainWindow):
|
|
|
29773
29943
|
self.table.setItem(row, 1, type_item)
|
|
29774
29944
|
|
|
29775
29945
|
# Source - Use read-only QTextEdit widget for easy text selection
|
|
29946
|
+
# Strip outer wrapping tags if setting is enabled (display only, data unchanged)
|
|
29947
|
+
source_for_display = segment.source
|
|
29948
|
+
stripped_source_tag = None
|
|
29949
|
+
list_prefix = ""
|
|
29950
|
+
if self.hide_outer_wrapping_tags:
|
|
29951
|
+
stripped, stripped_source_tag = strip_outer_wrapping_tags(segment.source)
|
|
29952
|
+
source_for_display = stripped
|
|
29953
|
+
# Add WYSIWYG list prefix (e.g., "1. " or "• ") for list items
|
|
29954
|
+
if stripped_source_tag:
|
|
29955
|
+
list_pos = list_numbers.get(row, 1)
|
|
29956
|
+
list_prefix = get_list_prefix_for_tag(stripped_source_tag, list_pos)
|
|
29957
|
+
if list_prefix:
|
|
29958
|
+
source_for_display = list_prefix + source_for_display
|
|
29776
29959
|
# Apply invisible character replacements for display only
|
|
29777
|
-
source_display_text = self.apply_invisible_replacements(
|
|
29960
|
+
source_display_text = self.apply_invisible_replacements(source_for_display)
|
|
29778
29961
|
source_editor = ReadOnlyGridTextEditor(source_display_text, self.table, row)
|
|
29779
|
-
|
|
29962
|
+
|
|
29780
29963
|
# Initialize empty termbase matches (will be populated lazily on segment selection or by background worker)
|
|
29781
29964
|
source_editor.termbase_matches = {}
|
|
29782
|
-
|
|
29965
|
+
|
|
29783
29966
|
# Set font to match grid
|
|
29784
29967
|
font = QFont(self.default_font_family, self.default_font_size)
|
|
29785
29968
|
source_editor.setFont(font)
|
|
29786
|
-
|
|
29969
|
+
|
|
29787
29970
|
# Set as cell widget (allows easy text selection)
|
|
29788
29971
|
self.table.setCellWidget(row, 2, source_editor)
|
|
29789
|
-
|
|
29972
|
+
|
|
29790
29973
|
# Also set a placeholder item for row height calculation
|
|
29791
29974
|
source_item = QTableWidgetItem()
|
|
29792
29975
|
source_item.setFlags(Qt.ItemFlag.NoItemFlags) # No interaction
|
|
29793
29976
|
self.table.setItem(row, 2, source_item)
|
|
29794
|
-
|
|
29977
|
+
|
|
29795
29978
|
# Target - Use editable QTextEdit widget for easy text selection and editing
|
|
29979
|
+
# Strip outer wrapping tags if enabled (will be auto-restored when saving)
|
|
29980
|
+
target_for_display = segment.target
|
|
29981
|
+
stripped_target_tag = None
|
|
29982
|
+
target_list_prefix = ""
|
|
29983
|
+
if self.hide_outer_wrapping_tags:
|
|
29984
|
+
stripped, stripped_target_tag = strip_outer_wrapping_tags(segment.target)
|
|
29985
|
+
target_for_display = stripped
|
|
29986
|
+
# Store stripped tag on segment for restore during save
|
|
29987
|
+
# Use source tag as reference (target should match source structure)
|
|
29988
|
+
segment._stripped_outer_tag = stripped_source_tag or stripped_target_tag
|
|
29989
|
+
# Add WYSIWYG list prefix for display (same as source)
|
|
29990
|
+
target_list_prefix = list_prefix # Reuse the prefix calculated for source
|
|
29991
|
+
|
|
29796
29992
|
# Apply invisible character replacements for display (will be reversed when saving)
|
|
29797
|
-
target_display_text = self.apply_invisible_replacements(
|
|
29993
|
+
target_display_text = self.apply_invisible_replacements(target_for_display)
|
|
29994
|
+
# Add list prefix for display (stored separately so we can strip it on save)
|
|
29995
|
+
if target_list_prefix:
|
|
29996
|
+
target_display_text = target_list_prefix + target_display_text
|
|
29798
29997
|
target_editor = EditableGridTextEditor(target_display_text, self.table, row, self.table)
|
|
29799
29998
|
target_editor.setFont(font)
|
|
29999
|
+
# Store stripped tag and list prefix on editor for auto-restore
|
|
30000
|
+
target_editor._stripped_outer_tag = stripped_source_tag or stripped_target_tag
|
|
30001
|
+
target_editor._list_prefix = target_list_prefix
|
|
29800
30002
|
|
|
29801
30003
|
# Connect text changes to update segment
|
|
29802
30004
|
# Use a factory function to create a proper closure that captures the segment ID
|
|
@@ -29808,9 +30010,21 @@ class SupervertalerQt(QMainWindow):
|
|
|
29808
30010
|
nonlocal debounce_timer
|
|
29809
30011
|
new_text = editor_widget.toPlainText()
|
|
29810
30012
|
|
|
30013
|
+
# Strip WYSIWYG list prefix if present (display-only, not saved)
|
|
30014
|
+
list_prefix = getattr(editor_widget, '_list_prefix', '')
|
|
30015
|
+
if list_prefix and new_text.startswith(list_prefix):
|
|
30016
|
+
new_text = new_text[len(list_prefix):]
|
|
30017
|
+
|
|
29811
30018
|
# Reverse invisible character replacements before saving
|
|
29812
30019
|
new_text = self.reverse_invisible_replacements(new_text)
|
|
29813
30020
|
|
|
30021
|
+
# Auto-restore stripped outer wrapping tags if they were hidden
|
|
30022
|
+
stripped_tag = getattr(editor_widget, '_stripped_outer_tag', None)
|
|
30023
|
+
if stripped_tag and self.hide_outer_wrapping_tags:
|
|
30024
|
+
# Only re-add if the text doesn't already have the tag
|
|
30025
|
+
if not new_text.strip().startswith(f'<{stripped_tag}'):
|
|
30026
|
+
new_text = f'<{stripped_tag}>{new_text}</{stripped_tag}>'
|
|
30027
|
+
|
|
29814
30028
|
# DEBUG: Log EVERY call to catch the culprit (only in debug mode)
|
|
29815
30029
|
if self.debug_mode_enabled:
|
|
29816
30030
|
self.log(f"🔔 textChanged FIRED: segment_id={segment_id}, new_text='{new_text[:20] if new_text else 'EMPTY'}...'")
|
|
@@ -31730,7 +31944,10 @@ class SupervertalerQt(QMainWindow):
|
|
|
31730
31944
|
self.enable_alternating_row_colors = settings.get('enable_alternating_row_colors', True)
|
|
31731
31945
|
self.even_row_color = settings.get('even_row_color', '#FFFFFF')
|
|
31732
31946
|
self.odd_row_color = settings.get('odd_row_color', '#F0F0F0')
|
|
31733
|
-
|
|
31947
|
+
|
|
31948
|
+
# Load hide outer wrapping tags setting
|
|
31949
|
+
self.hide_outer_wrapping_tags = settings.get('hide_outer_wrapping_tags', False)
|
|
31950
|
+
|
|
31734
31951
|
# Load termbase highlight style settings
|
|
31735
31952
|
self.termbase_highlight_style = settings.get('termbase_highlight_style', 'semibold')
|
|
31736
31953
|
self.termbase_dotted_color = settings.get('termbase_dotted_color', '#808080')
|
|
@@ -37377,6 +37594,22 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
37377
37594
|
# during load_segments_to_grid when creating cell widgets
|
|
37378
37595
|
self.load_segments_to_grid()
|
|
37379
37596
|
|
|
37597
|
+
def _refresh_source_column_display(self):
|
|
37598
|
+
"""Refresh grid to reflect hide_outer_wrapping_tags setting.
|
|
37599
|
+
|
|
37600
|
+
Reloads the entire grid to properly handle:
|
|
37601
|
+
- Tag stripping on both Source and Target columns
|
|
37602
|
+
- WYSIWYG list numbering/bullet prefixes
|
|
37603
|
+
- Proper list position calculation
|
|
37604
|
+
"""
|
|
37605
|
+
if not hasattr(self, 'table') or not self.table:
|
|
37606
|
+
return
|
|
37607
|
+
if not hasattr(self, 'current_project') or not self.current_project:
|
|
37608
|
+
return
|
|
37609
|
+
|
|
37610
|
+
# Reload grid to apply changes with proper list numbering calculation
|
|
37611
|
+
self.load_segments_to_grid()
|
|
37612
|
+
|
|
37380
37613
|
def apply_invisible_replacements(self, text):
|
|
37381
37614
|
"""Apply invisible character replacements to text based on settings"""
|
|
37382
37615
|
if not hasattr(self, 'invisible_display_settings'):
|
|
@@ -49563,7 +49796,7 @@ class SuperlookupTab(QWidget):
|
|
|
49563
49796
|
target_lang = getattr(main_window, 'target_language', 'Dutch')
|
|
49564
49797
|
|
|
49565
49798
|
# Import and show MT Quick Lookup popup
|
|
49566
|
-
from modules.
|
|
49799
|
+
from modules.quicktrans import MTQuickPopup
|
|
49567
49800
|
|
|
49568
49801
|
# Create popup without Qt parent so it can appear independently over any app
|
|
49569
49802
|
# Pass main_window as parent_app for API access
|
modules/llm_clients.py
CHANGED
|
@@ -594,18 +594,20 @@ class LLMClient:
|
|
|
594
594
|
context: Optional[str] = None,
|
|
595
595
|
custom_prompt: Optional[str] = None,
|
|
596
596
|
max_tokens: Optional[int] = None,
|
|
597
|
-
images: Optional[List] = None
|
|
597
|
+
images: Optional[List] = None,
|
|
598
|
+
system_prompt: Optional[str] = None
|
|
598
599
|
) -> str:
|
|
599
600
|
"""
|
|
600
601
|
Translate text using configured LLM
|
|
601
|
-
|
|
602
|
+
|
|
602
603
|
Args:
|
|
603
604
|
text: Text to translate
|
|
604
605
|
source_lang: Source language code
|
|
605
606
|
target_lang: Target language code
|
|
606
607
|
context: Optional context for translation
|
|
607
608
|
custom_prompt: Optional custom prompt (overrides default simple prompt)
|
|
608
|
-
|
|
609
|
+
system_prompt: Optional system prompt for AI behavior context
|
|
610
|
+
|
|
609
611
|
Returns:
|
|
610
612
|
Translated text
|
|
611
613
|
"""
|
|
@@ -615,30 +617,30 @@ class LLMClient:
|
|
|
615
617
|
else:
|
|
616
618
|
# Build prompt
|
|
617
619
|
prompt = f"Translate the following text from {source_lang} to {target_lang}:\n\n{text}"
|
|
618
|
-
|
|
620
|
+
|
|
619
621
|
if context:
|
|
620
622
|
prompt = f"Context: {context}\n\n{prompt}"
|
|
621
|
-
|
|
623
|
+
|
|
622
624
|
# Log warning if images provided but model doesn't support vision
|
|
623
625
|
if images and not self.model_supports_vision(self.provider, self.model):
|
|
624
626
|
print(f"⚠️ Warning: Model {self.model} doesn't support vision. Images will be ignored.")
|
|
625
627
|
images = None # Don't pass to API
|
|
626
|
-
|
|
628
|
+
|
|
627
629
|
# Call appropriate provider
|
|
628
630
|
if self.provider == "openai":
|
|
629
|
-
return self._call_openai(prompt, max_tokens=max_tokens, images=images)
|
|
631
|
+
return self._call_openai(prompt, max_tokens=max_tokens, images=images, system_prompt=system_prompt)
|
|
630
632
|
elif self.provider == "claude":
|
|
631
|
-
return self._call_claude(prompt, max_tokens=max_tokens, images=images)
|
|
633
|
+
return self._call_claude(prompt, max_tokens=max_tokens, images=images, system_prompt=system_prompt)
|
|
632
634
|
elif self.provider == "gemini":
|
|
633
|
-
return self._call_gemini(prompt, max_tokens=max_tokens, images=images)
|
|
635
|
+
return self._call_gemini(prompt, max_tokens=max_tokens, images=images, system_prompt=system_prompt)
|
|
634
636
|
elif self.provider == "ollama":
|
|
635
|
-
return self._call_ollama(prompt, max_tokens=max_tokens)
|
|
637
|
+
return self._call_ollama(prompt, max_tokens=max_tokens, system_prompt=system_prompt)
|
|
636
638
|
else:
|
|
637
639
|
raise ValueError(f"Unsupported provider: {self.provider}")
|
|
638
640
|
|
|
639
|
-
def _call_openai(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None) -> str:
|
|
641
|
+
def _call_openai(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None, system_prompt: Optional[str] = None) -> str:
|
|
640
642
|
"""Call OpenAI API with GPT-5/o1/o3 reasoning model support and vision capability"""
|
|
641
|
-
print(f"🔵 _call_openai START: model={self.model}, prompt_len={len(prompt)}, max_tokens={max_tokens}, images={len(images) if images else 0}")
|
|
643
|
+
print(f"🔵 _call_openai START: model={self.model}, prompt_len={len(prompt)}, max_tokens={max_tokens}, images={len(images) if images else 0}, has_system={bool(system_prompt)}")
|
|
642
644
|
|
|
643
645
|
try:
|
|
644
646
|
from openai import OpenAI
|
|
@@ -686,10 +688,16 @@ class LLMClient:
|
|
|
686
688
|
# Standard text-only format
|
|
687
689
|
content = prompt
|
|
688
690
|
|
|
691
|
+
# Build messages list
|
|
692
|
+
messages = []
|
|
693
|
+
if system_prompt:
|
|
694
|
+
messages.append({"role": "system", "content": system_prompt})
|
|
695
|
+
messages.append({"role": "user", "content": content})
|
|
696
|
+
|
|
689
697
|
# Build API call parameters
|
|
690
698
|
api_params = {
|
|
691
699
|
"model": self.model,
|
|
692
|
-
"messages":
|
|
700
|
+
"messages": messages,
|
|
693
701
|
"timeout": timeout_seconds
|
|
694
702
|
}
|
|
695
703
|
|
|
@@ -742,7 +750,7 @@ class LLMClient:
|
|
|
742
750
|
print(f" Response: {e.response}")
|
|
743
751
|
raise # Re-raise to be caught by calling code
|
|
744
752
|
|
|
745
|
-
def _call_claude(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None) -> str:
|
|
753
|
+
def _call_claude(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None, system_prompt: Optional[str] = None) -> str:
|
|
746
754
|
"""Call Anthropic Claude API with vision support"""
|
|
747
755
|
try:
|
|
748
756
|
import anthropic
|
|
@@ -786,12 +794,19 @@ class LLMClient:
|
|
|
786
794
|
# Standard text-only format
|
|
787
795
|
content = prompt
|
|
788
796
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
797
|
+
# Build API call parameters
|
|
798
|
+
api_params = {
|
|
799
|
+
"model": self.model,
|
|
800
|
+
"max_tokens": tokens_to_use,
|
|
801
|
+
"messages": [{"role": "user", "content": content}],
|
|
802
|
+
"timeout": timeout_seconds # Explicit timeout
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
# Add system prompt if provided (Claude uses 'system' parameter, not a message)
|
|
806
|
+
if system_prompt:
|
|
807
|
+
api_params["system"] = system_prompt
|
|
808
|
+
|
|
809
|
+
response = client.messages.create(**api_params)
|
|
795
810
|
|
|
796
811
|
translation = response.content[0].text.strip()
|
|
797
812
|
|
|
@@ -800,7 +815,7 @@ class LLMClient:
|
|
|
800
815
|
|
|
801
816
|
return translation
|
|
802
817
|
|
|
803
|
-
def _call_gemini(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None) -> str:
|
|
818
|
+
def _call_gemini(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None, system_prompt: Optional[str] = None) -> str:
|
|
804
819
|
"""Call Google Gemini API with vision support"""
|
|
805
820
|
try:
|
|
806
821
|
import google.generativeai as genai
|
|
@@ -809,10 +824,15 @@ class LLMClient:
|
|
|
809
824
|
raise ImportError(
|
|
810
825
|
"Google AI library not installed. Install with: pip install google-generativeai pillow"
|
|
811
826
|
)
|
|
812
|
-
|
|
827
|
+
|
|
813
828
|
genai.configure(api_key=self.api_key)
|
|
814
|
-
|
|
815
|
-
|
|
829
|
+
|
|
830
|
+
# Gemini supports system instructions via GenerativeModel parameter
|
|
831
|
+
if system_prompt:
|
|
832
|
+
model = genai.GenerativeModel(self.model, system_instruction=system_prompt)
|
|
833
|
+
else:
|
|
834
|
+
model = genai.GenerativeModel(self.model)
|
|
835
|
+
|
|
816
836
|
# Build content (text + optional images)
|
|
817
837
|
if images:
|
|
818
838
|
# Gemini format: list with prompt text followed by PIL Image objects
|
|
@@ -823,7 +843,7 @@ class LLMClient:
|
|
|
823
843
|
else:
|
|
824
844
|
# Standard text-only
|
|
825
845
|
content = prompt
|
|
826
|
-
|
|
846
|
+
|
|
827
847
|
response = model.generate_content(content)
|
|
828
848
|
translation = response.text.strip()
|
|
829
849
|
|
|
@@ -832,20 +852,21 @@ class LLMClient:
|
|
|
832
852
|
|
|
833
853
|
return translation
|
|
834
854
|
|
|
835
|
-
def _call_ollama(self, prompt: str, max_tokens: Optional[int] = None) -> str:
|
|
855
|
+
def _call_ollama(self, prompt: str, max_tokens: Optional[int] = None, system_prompt: Optional[str] = None) -> str:
|
|
836
856
|
"""
|
|
837
857
|
Call local Ollama server for translation.
|
|
838
|
-
|
|
858
|
+
|
|
839
859
|
Ollama provides a simple REST API compatible with local LLM inference.
|
|
840
860
|
Models run entirely on the user's computer - no API keys, no internet required.
|
|
841
|
-
|
|
861
|
+
|
|
842
862
|
Args:
|
|
843
863
|
prompt: The full prompt to send
|
|
844
864
|
max_tokens: Maximum tokens to generate (default: 4096)
|
|
845
|
-
|
|
865
|
+
system_prompt: Optional system prompt for AI behavior context
|
|
866
|
+
|
|
846
867
|
Returns:
|
|
847
868
|
Translated text
|
|
848
|
-
|
|
869
|
+
|
|
849
870
|
Raises:
|
|
850
871
|
ConnectionError: If Ollama is not running
|
|
851
872
|
ValueError: If model is not available
|
|
@@ -866,13 +887,17 @@ class LLMClient:
|
|
|
866
887
|
print(f"🟠 _call_ollama START: model={self.model}, prompt_len={len(prompt)}, max_tokens={tokens_to_use}")
|
|
867
888
|
print(f"🟠 Ollama endpoint: {endpoint}")
|
|
868
889
|
|
|
890
|
+
# Build messages list
|
|
891
|
+
messages = []
|
|
892
|
+
if system_prompt:
|
|
893
|
+
messages.append({"role": "system", "content": system_prompt})
|
|
894
|
+
messages.append({"role": "user", "content": prompt})
|
|
895
|
+
|
|
869
896
|
# Build request payload
|
|
870
897
|
# Using /api/chat for chat-style interaction (better for translation prompts)
|
|
871
898
|
payload = {
|
|
872
899
|
"model": self.model,
|
|
873
|
-
"messages":
|
|
874
|
-
{"role": "user", "content": prompt}
|
|
875
|
-
],
|
|
900
|
+
"messages": messages,
|
|
876
901
|
"stream": False, # Get complete response at once
|
|
877
902
|
"options": {
|
|
878
903
|
"temperature": 0.3, # Low temperature for consistent translations
|
|
@@ -3795,6 +3795,26 @@ Output complete ACTION."""
|
|
|
3795
3795
|
from PyQt6.QtWidgets import QApplication
|
|
3796
3796
|
QApplication.processEvents()
|
|
3797
3797
|
|
|
3798
|
+
# System prompt that explains the ACTION format to the AI
|
|
3799
|
+
ai_system_prompt = """You are an AI assistant for Supervertaler, a professional translation workbench.
|
|
3800
|
+
|
|
3801
|
+
You can execute actions using a special format. When you need to create, modify, or manage prompts, output ACTION blocks in this EXACT format:
|
|
3802
|
+
|
|
3803
|
+
ACTION:function_name PARAMS:{"param1": "value1", "param2": "value2"}
|
|
3804
|
+
|
|
3805
|
+
Available actions:
|
|
3806
|
+
- create_prompt: Create a new prompt. Required params: name, content. Optional: folder, description, activate
|
|
3807
|
+
- update_prompt: Update an existing prompt. Required params: name. Optional: content, folder, description
|
|
3808
|
+
- delete_prompt: Delete a prompt. Required params: name
|
|
3809
|
+
- list_prompts: List all prompts. Optional params: folder
|
|
3810
|
+
- activate_prompt: Set a prompt as active. Required params: name
|
|
3811
|
+
|
|
3812
|
+
IMPORTANT:
|
|
3813
|
+
1. Output ONLY the ACTION block when asked to create/modify prompts - no explanatory text
|
|
3814
|
+
2. The ACTION must be on a single line (PARAMS JSON can be multiline if needed)
|
|
3815
|
+
3. Use valid JSON for PARAMS (double quotes for strings, escape special characters)
|
|
3816
|
+
4. Do not wrap in code fences or add any markdown formatting"""
|
|
3817
|
+
|
|
3798
3818
|
# Call LLM using translate method with custom prompt
|
|
3799
3819
|
# The translate method accepts a custom_prompt parameter that we can use for any text generation
|
|
3800
3820
|
self.log_message("[AI Assistant] Calling LLM translate method...")
|
|
@@ -3802,7 +3822,8 @@ Output complete ACTION."""
|
|
|
3802
3822
|
text="", # Empty text since we're using custom_prompt
|
|
3803
3823
|
source_lang="en",
|
|
3804
3824
|
target_lang="en",
|
|
3805
|
-
custom_prompt=prompt
|
|
3825
|
+
custom_prompt=prompt,
|
|
3826
|
+
system_prompt=ai_system_prompt
|
|
3806
3827
|
)
|
|
3807
3828
|
|
|
3808
3829
|
# Log the response
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supervertaler
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.198
|
|
4
4
|
Summary: Professional AI-enhanced 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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Supervertaler.py,sha256=
|
|
1
|
+
Supervertaler.py,sha256=YY7nUEoaCqP0SZucFNKDqBuGhJDOgcUNb6lPbf9xsvo,2390297
|
|
2
2
|
modules/__init__.py,sha256=G58XleS-EJ2sX4Kehm-3N2m618_W2Es0Kg8CW_eBG7g,327
|
|
3
3
|
modules/ai_actions.py,sha256=i5MJcM-7Y6CAvKUwxmxrVHeoZAVtAP7aRDdWM5KLkO0,33877
|
|
4
4
|
modules/ai_attachment_manager.py,sha256=juZlrW3UPkIkcnj0SREgOQkQROLf0fcu3ShZcKXMxsI,11361
|
|
@@ -23,14 +23,13 @@ modules/find_replace_qt.py,sha256=z06yyjOCmpYBrCzZcCv68VK-2o6pjfFCPtbr9o95XH8,18
|
|
|
23
23
|
modules/glossary_manager.py,sha256=JDxY9RAGcv-l6Nms4FH7CNDucZdY1TI4WTzyylAuj24,16437
|
|
24
24
|
modules/image_extractor.py,sha256=HI6QHnpkjO35GHzTXbzazYdjoHZPFD44WAa4JGa9bAw,8332
|
|
25
25
|
modules/keyboard_shortcuts_widget.py,sha256=H489eknMDJkYusri1hQYr5MT-NhkVoHWdx9k_NMTr68,26411
|
|
26
|
-
modules/llm_clients.py,sha256=
|
|
26
|
+
modules/llm_clients.py,sha256=q-E9PnmtAuN8LWs_EKlMV4sXN5Jx2L57kfKCTk9X-24,50012
|
|
27
27
|
modules/llm_leaderboard.py,sha256=MQ-RbjlE10-CdgVHwoVXzXlCuUfulZ839FaF6dKlt1M,29139
|
|
28
28
|
modules/llm_superbench_ui.py,sha256=lmzsL8lt0KzFw-z8De1zb49Emnv7f1dZv_DJmoQz0bQ,60212
|
|
29
29
|
modules/local_llm_setup.py,sha256=33y-D_LKzkn2w8ejyjeKaovf_An6xQ98mKISoqe-Qjc,42661
|
|
30
30
|
modules/model_update_dialog.py,sha256=kEg0FuO1N-uj6QY5ZIj-FqdiLQuPuAY48pbuwT0HUGI,13113
|
|
31
31
|
modules/model_version_checker.py,sha256=41g7gcWvyrKPYeobaOGCMZLwAHgQmFwVF8zokodKae8,12741
|
|
32
32
|
modules/mqxliff_handler.py,sha256=TVtrf7ieGoxfoLxy4v4S7by9YImKypw1EY0wFpZO3Lo,28792
|
|
33
|
-
modules/mt_quick_popup.py,sha256=jnCwaX8tSwaGlM5BxX2NEz5XqkHu9Pnxd8XBRX3OUFM,26291
|
|
34
33
|
modules/non_translatables_manager.py,sha256=izorabiX6rSQzuBIvnY67wmu5vd85SbzexXccbmwPs4,27465
|
|
35
34
|
modules/pdf_rescue_Qt.py,sha256=9W_M0Zms4miapQbrqm-viHNCpaW39GL9VaKKFCJxpnE,80479
|
|
36
35
|
modules/pdf_rescue_tkinter.py,sha256=a4R_OUnn7X5O_XMR1roybrdu1aXoGCwwO-mwYB2ZpOg,39606
|
|
@@ -41,6 +40,7 @@ modules/prompt_assistant.py,sha256=shkZqNTvyQKNDO_9aFEu1_gN0zQq0fR5krXkWfnTR2Y,1
|
|
|
41
40
|
modules/prompt_library.py,sha256=t5w4cqB6_Sin4BQDVNALKpfB1EN_oaDeHFwlHxILLSY,26894
|
|
42
41
|
modules/prompt_library_migration.py,sha256=fv3RHhe2-EnH50XW5tyTWy0YP_KJ2EsESuTxR8klfmI,17639
|
|
43
42
|
modules/quick_access_sidebar.py,sha256=RPn5ssvYXlitNMWFZN9Yxv7So8u_z5RGNpHN6N-SFDI,10151
|
|
43
|
+
modules/quicktrans.py,sha256=jnCwaX8tSwaGlM5BxX2NEz5XqkHu9Pnxd8XBRX3OUFM,26291
|
|
44
44
|
modules/ribbon_widget.py,sha256=QNGKxmit_oM5C5nJViadYYEzeRlIdIsla8Bzu_RNGO0,21990
|
|
45
45
|
modules/sdlppx_handler.py,sha256=o6Rj_T0B94toiYlvDDwMYLSz4q6kANgegFaDK5i3yhs,33538
|
|
46
46
|
modules/setup_wizard.py,sha256=7apNkeTcmMyw8pzJOWAmTOncxFvt3klOtmg-kKjQNgQ,13091
|
|
@@ -76,13 +76,13 @@ modules/translation_memory.py,sha256=LnG8csZNL2GTHXT4zk0uecJEtvRc-MKwv7Pt7EX3s7s
|
|
|
76
76
|
modules/translation_results_panel.py,sha256=OWqzV9xmJOi8NGCi3h42nq-qE7-v6WStjQWRsghCVbQ,92044
|
|
77
77
|
modules/translation_services.py,sha256=lyVpWuZK1wtVtYZMDMdLoq1DHBoSaeAnp-Yejb0TlVQ,10530
|
|
78
78
|
modules/unified_prompt_library.py,sha256=96u4WlMwnmmhD4uNJHZ-qVQj8v9_8dA2AVCWpBcwTrg,26006
|
|
79
|
-
modules/unified_prompt_manager_qt.py,sha256=
|
|
79
|
+
modules/unified_prompt_manager_qt.py,sha256=y7xAIM9X7f1afXGE1tOcAc5EY7BKFy53Rgqi3fFzKmQ,173374
|
|
80
80
|
modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
|
|
81
81
|
modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
|
|
82
82
|
modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
|
|
83
|
-
supervertaler-1.9.
|
|
84
|
-
supervertaler-1.9.
|
|
85
|
-
supervertaler-1.9.
|
|
86
|
-
supervertaler-1.9.
|
|
87
|
-
supervertaler-1.9.
|
|
88
|
-
supervertaler-1.9.
|
|
83
|
+
supervertaler-1.9.198.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
|
|
84
|
+
supervertaler-1.9.198.dist-info/METADATA,sha256=XD0vrT3puNdRtUEj8oHeAnOkSJ7CohlYNWQo5_VkM8M,5725
|
|
85
|
+
supervertaler-1.9.198.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
86
|
+
supervertaler-1.9.198.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
|
|
87
|
+
supervertaler-1.9.198.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
|
|
88
|
+
supervertaler-1.9.198.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|