From f6d7378e21ec9d62dd018b517249fc3aeab73c1b Mon Sep 17 00:00:00 2001 From: dazedanon Date: Sat, 25 Oct 2025 22:27:44 -0500 Subject: [PATCH] closer --- gui/translation_tab.py | 237 +++++++++++++++++++++++++++++++++++------ 1 file changed, 206 insertions(+), 31 deletions(-) diff --git a/gui/translation_tab.py b/gui/translation_tab.py index 3656e94..93148eb 100644 --- a/gui/translation_tab.py +++ b/gui/translation_tab.py @@ -14,6 +14,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed, Future import traceback import signal import multiprocessing +import re from colorama import Fore from tqdm import tqdm from dotenv import load_dotenv @@ -434,6 +435,13 @@ class TranslationTab(QWidget): self.files_total = 0 self.file_progress_items = {} # filename -> {widget, label, progress_bar, checkbox} self.current_translating_file = None + # Totals tracking + self.totals_input_tokens = 0 + self.totals_output_tokens = 0 + self.totals_cost = 0.0 + self.totals_time = 0.0 + # Totals widget reference + self.totals_widget = None self.setup_ui() self.setup_module_list() @@ -501,8 +509,7 @@ class TranslationTab(QWidget): background-color: #3e3e42; } """) - files_container.addWidget(self.file_list) - + # Place the two main file buttons on the left and totals on the right # File management buttons (icon-based, vertical on the side) file_buttons = QVBoxLayout() file_buttons.setSpacing(0) @@ -591,13 +598,19 @@ class TranslationTab(QWidget): # Add stretch to push buttons to top file_buttons.addStretch() - - # Add button column to the container + + # Add button column to the container on the LEFT files_container.addLayout(file_buttons) - + # Then add the file list (center) + files_container.addWidget(self.file_list) + + # (Totals footer will be created below and shown only when translation starts) + # Add the container to file list page and allow it to expand so # the file list can grow and push settings to the bottom. file_list_layout.addLayout(files_container, 1) + # (Totals footer removed here; totals will be shown next to the + # back/open buttons in the progress view as requested.) file_list_page.setLayout(file_list_layout) self.file_stack.addWidget(file_list_page) # Index 0 @@ -671,16 +684,77 @@ class TranslationTab(QWidget): self.reset_view_button.setFont(QFont('', 12)) self.open_translations_button.setFont(QFont('', 12)) - # Place both buttons side-by-side (icons centered) + # Create the stop button here so it sits in the same row as the + # back/open buttons. Use a compact icon style to match them but + # make it visually distinct (red) to indicate a destructive action. + stop_button_style = """ + QPushButton { + background-color: #c0392b; /* red */ + color: white; + font-weight: bold; + font-size: 16px; + border: 1px solid #7f2e28; + border-radius: 4px; + min-width: 40px; + max-width: 40px; + min-height: 36px; + max-height: 36px; + } + QPushButton:hover { + background-color: #e04b43; + border-left-color: #ff6b60; + } + QPushButton:pressed { + background-color: #a82a20; + } + """ + + # Use a clear stop-sign emoji so the glyph is rendered as a stop icon + # and not as a colored square on some platforms. + self.stop_button = QPushButton("🛑") + self.stop_button.setToolTip("Stop Translation") + self.stop_button.clicked.connect(self.stop_translation) + self.stop_button.setStyleSheet(stop_button_style) + # Slightly larger font for the emoji to make it visually clear + self.stop_button.setFont(QFont('', 14)) + self.stop_button.setVisible(False) + + # Place both buttons on the left and totals on the right buttons_container = QWidget() + # Prevent the buttons row from changing the file list box size when + # buttons/totals are shown — keep a fixed height for the row. + buttons_container.setFixedHeight(64) + buttons_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) buttons_hbox = QHBoxLayout() buttons_hbox.setContentsMargins(0, 0, 0, 0) buttons_hbox.setSpacing(8) - buttons_hbox.addStretch() - # Back button on the left + # Back/Open/Stop buttons on the left (stop shown while running) + buttons_hbox.addWidget(self.stop_button) buttons_hbox.addWidget(self.reset_view_button) buttons_hbox.addWidget(self.open_translations_button) + # Spacer between buttons and totals buttons_hbox.addStretch() + # Totals widget on the right (hidden until start) + self.totals_widget = QWidget() + # Keep totals widget a fixed height so showing it doesn't expand the + # input files box vertically. + self.totals_widget.setFixedHeight(64) + self.totals_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + totals_layout = QVBoxLayout() + totals_layout.setContentsMargins(6, 2, 6, 2) + totals_layout.setSpacing(2) + self.totals_tokens_label = QLabel("Tokens: 0 in / 0 out") + self.totals_tokens_label.setStyleSheet("color: #f1c40f; font-weight: bold;") + totals_layout.addWidget(self.totals_tokens_label) + self.totals_cost_label = QLabel("Cost: $0.0000") + self.totals_cost_label.setStyleSheet("color: #4ec9b0; font-weight: bold;") + totals_layout.addWidget(self.totals_cost_label) + self.totals_time_label = QLabel("Time: 0.0s") + self.totals_time_label.setStyleSheet("color: #4da6ff; font-weight: bold;") + totals_layout.addWidget(self.totals_time_label) + self.totals_widget.setLayout(totals_layout) + self.totals_widget.setVisible(False) + buttons_hbox.addWidget(self.totals_widget) buttons_container.setLayout(buttons_hbox) progress_view_layout.addWidget(buttons_container) @@ -801,23 +875,6 @@ class TranslationTab(QWidget): } """) button_layout.addWidget(self.translate_button) - self.stop_button = QPushButton("Stop Translation") - self.stop_button.clicked.connect(self.stop_translation) - self.stop_button.setVisible(False) # Hidden by default - self.stop_button.setStyleSheet(""" - QPushButton { - background-color: #cc0000; - color: white; - font-weight: bold; - padding: 10px 20px; - font-size: 14px; - border-radius: 5px; - } - QPushButton:hover { - background-color: #aa0000; - } - """) - button_layout.addWidget(self.stop_button) button_layout.addStretch() layout.addLayout(button_layout) left_widget.setLayout(layout) @@ -1164,7 +1221,26 @@ class TranslationTab(QWidget): progress_label.setStyleSheet("color: #888888; font-size: 12px;") progress_label.setFixedWidth(120) layout.addWidget(progress_label) - + + # Inline result labels (hidden until completion) + tokens_label = QLabel("") + tokens_label.setStyleSheet("color: #f1c40f; font-weight: bold; font-size: 12px;") + tokens_label.setFixedWidth(140) + tokens_label.setVisible(False) + layout.addWidget(tokens_label) + + cost_label = QLabel("") + cost_label.setStyleSheet("color: #4ec9b0; font-weight: bold; font-size: 12px;") + cost_label.setFixedWidth(110) + cost_label.setVisible(False) + layout.addWidget(cost_label) + + time_label = QLabel("") + time_label.setStyleSheet("color: #4da6ff; font-weight: bold; font-size: 12px;") + time_label.setFixedWidth(90) + time_label.setVisible(False) + layout.addWidget(time_label) + # Progress bar (stretch to fill remaining space) progress_bar = QProgressBar() progress_bar.setMaximum(100) @@ -1192,7 +1268,10 @@ class TranslationTab(QWidget): 'widget': widget, 'checkbox': checkbox, 'label': progress_label, - 'progress_bar': progress_bar + 'progress_bar': progress_bar, + 'tokens_label': tokens_label, + 'cost_label': cost_label, + 'time_label': time_label } return widget @@ -1211,10 +1290,24 @@ class TranslationTab(QWidget): if filename in self.file_progress_items: item = self.file_progress_items[filename] item['checkbox'].setChecked(True) + # If we have detailed result labels already set (via append_log), + # show them and hide the progress bar to make room. if success: - item['label'].setText("✓ Complete") - item['label'].setStyleSheet("color: #4ec9b0; font-weight: bold;") - item['progress_bar'].setValue(item['progress_bar'].maximum()) + # If tokens/cost/time were already filled by parse, they'll be visible. + if item.get('tokens_label') and item['tokens_label'].text(): + item['tokens_label'].setVisible(True) + item['cost_label'].setVisible(True) + item['time_label'].setVisible(True) + item['label'].setText("✓ Complete") + item['label'].setStyleSheet("color: #4ec9b0; font-weight: bold;") + try: + item['progress_bar'].setVisible(False) + except Exception: + pass + else: + item['label'].setText("✓ Complete") + item['label'].setStyleSheet("color: #4ec9b0; font-weight: bold;") + item['progress_bar'].setValue(item['progress_bar'].maximum()) else: item['label'].setText("✗ Failed") item['label'].setStyleSheet("color: #f48771; font-weight: bold;") @@ -1228,6 +1321,12 @@ class TranslationTab(QWidget): self.open_translations_button.setVisible(False) except Exception: pass + # Hide totals when returning to file view + try: + if hasattr(self, 'totals_widget') and self.totals_widget: + self.totals_widget.setVisible(False) + except Exception: + pass self.translate_button.setVisible(True) self.stop_button.setVisible(False) self.refresh_file_lists() @@ -1283,6 +1382,22 @@ class TranslationTab(QWidget): # Toggle button visibility self.translate_button.setVisible(False) self.stop_button.setVisible(True) + # Show totals footer and reset totals when starting translation + try: + self.totals_input_tokens = 0 + self.totals_output_tokens = 0 + self.totals_cost = 0.0 + self.totals_time = 0.0 + if hasattr(self, 'totals_tokens_label'): + self.totals_tokens_label.setText("Tokens: 0 in / 0 out") + if hasattr(self, 'totals_cost_label'): + self.totals_cost_label.setText("Cost: $0.0000") + if hasattr(self, 'totals_time_label'): + self.totals_time_label.setText("Time: 0.0s") + if hasattr(self, 'totals_widget') and self.totals_widget: + self.totals_widget.setVisible(True) + except Exception: + pass # Ensure the open translations button is hidden while running try: self.open_translations_button.setVisible(False) @@ -1330,8 +1445,68 @@ class TranslationTab(QWidget): # Forwarding worker messages here caused non-file messages to appear # in the log window. Modules write to the log file themselves, so # we no longer push worker messages into the UI. - # Keep this method as a no-op to preserve the signal connection. + # Parse worker/module output lines for per-file result details. + # Example line: "Enemies.json: [Input: 8865][Output: 663][Cost: $0.0230][20.1s] ✓" + try: + pattern = r'^(?P[^:]+):.*?\[Input:\s*(?P\d+)\].*?\[Output:\s*(?P\d+)\].*?\[Cost:\s*\$(?P[\d\.]+)\].*?\[(?P