Compare commits

..
Author SHA1 Message Date
yusufipek e282e6b0cf Say the download started before the first byte arrives
Opening the connection takes ten or twenty seconds, and the byte counts
under the model box only start after it. Until then the line read "X has
not been downloaded yet" beside a button that had just turned into Stop,
so a download that was running looked like a click that had not landed.

The stop had the same gap the other way round: should_stop is read
between blocks, and the wait for the server to answer is not between
blocks, so pressing Stop during it changed nothing on screen either.
2026-09-05 11:37:19 +03:00
3 changed files with 38 additions and 55 deletions
+1
View File
@@ -793,6 +793,7 @@ TR = {
"İndirildi, sürüm {version}. Vulkan sürümü yoktu, bu sürüm işlemcide çalışıyor.",
"Fetching the model list…": "Model listesi çekiliyor…",
"Downloading…": "İndiriliyor…",
"Starting the download…": "İndirme başlatılıyor…",
"Downloading: {done} of {total}{share}": "İndiriliyor: {done} / {total}{share}",
"Download stopped.": "İndirme durduruldu.",
"Ready: {name}.": "Hazır: {name}.",
+10 -17
View File
@@ -181,30 +181,14 @@ class WrappedLabel(QLabel):
super().setText(text)
self._fit()
def showEvent(self, event):
# Text set while the window was still being built was measured against
# nothing; this is the first moment the width means anything.
super().showEvent(event)
self._fit()
def resizeEvent(self, event):
super().resizeEvent(event)
self._fit()
def _fit(self):
# A label the layout has not placed yet is a handful of pixels wide,
# and wrapping a sentence against that width invents a hundred lines.
# The minimum set from it does not stay a minimum either: QLabel folds
# it into its own cached size hints and clears that cache only when the
# text changes, so the row stands thousands of pixels tall and carries
# the model box and everything under it off the bottom of the window
# until another publisher is picked. Nothing to measure against yet
# means nothing to claim yet, and the show and resize above come back
# for it.
if not self.isVisible() or self.width() <= 0:
return
# Measured off the font rather than asked of the label, whose own answer
# is floored by the minimum set here a moment ago and so only ever grows.
if self.width() > 0:
wrap = Qt.TextFlag.TextWordWrap | Qt.TextFlag.TextWrapAnywhere
box = QRect(0, 0, self.width(), 0)
self.setMinimumHeight(
@@ -717,12 +701,21 @@ class LocalModelBox(QGroupBox):
def _download(self):
if self._downloading:
self._stop = True
# The flag is only read between blocks, and the wait for the server
# to answer is not between blocks: a click during it changes
# nothing on screen for as long as the connection takes.
self.status.setText(t("Stopping…"))
return
item = self._current_item()
if item is None:
return
self._downloading, self._stop = True, False
self._refresh_buttons()
# Opening the connection can take ten or twenty seconds, and the first
# byte counts are what the line below would otherwise wait for. Left
# saying "not downloaded yet" beside a button that now reads Stop, a
# download that started looks like a click that did not register.
self.status.setText(t("Starting the download…"))
def work():
try:
+23 -34
View File
@@ -14,7 +14,7 @@ import unittest
from typing import ClassVar
from unittest import mock
from PyQt6.QtCore import QPoint, QPointF, QRect, Qt
from PyQt6.QtCore import QPoint, QPointF, Qt
from PyQt6.QtGui import QWheelEvent
from PyQt6.QtWidgets import QApplication, QMessageBox
@@ -244,39 +244,6 @@ class Settings(DikteTest):
label.resize(2000, line)
self.assertLessEqual(label.minimumHeight(), line)
def test_a_label_written_before_the_layout_places_it_claims_nothing(self):
# The publisher note is written while the settings window is still
# being built, when the label is a handful of pixels wide. Wrapped
# against that width the sentence became a hundred lines, and the
# minimum taken from it did not stay a minimum: QLabel folds it into
# its own cached size hints and clears that cache only when the text
# changes. The group box stood thousands of pixels tall, with the
# model box and everything under it off the bottom of the window,
# until another publisher was picked.
label = settings_ui.WrappedLabel()
self.addCleanup(label.deleteLater)
line = label.fontMetrics().height()
label.resize(8, line)
label.setText("Google Gemma 4, the small one. The default: nothing "
"else this size follows an instruction as closely, and "
"cleanup is all instruction.")
self.assertEqual(label.minimumHeight(), 0)
# Placed and shown, which is the first width worth measuring against.
# The room the wrapping needs is claimed then, and it is the lines the
# sentence takes at this width rather than at the last one. Counted
# off the font rather than written down here, because how many lines
# 400 pixels hold is a different answer on every machine.
label.resize(400, line)
label.show()
wrap = Qt.TextFlag.TextWordWrap | Qt.TextFlag.TextWrapAnywhere
needed = label.fontMetrics().boundingRect(
QRect(0, 0, 400, 0), wrap, label.text()).height()
self.assertGreater(needed, line) # or the sentence never wrapped
self.assertEqual(label.minimumHeight(), needed)
# And the label's own hints are the wrapping at this width too, not
# the hundred lines the eight pixel one asked for.
self.assertLessEqual(label.sizeHint().height(), 3 * needed)
def test_saving_without_touching_anything_changes_nothing(self):
"""Every widget has to load what is stored, or Save writes its default
over it. This says so for the whole table at once."""
@@ -1369,6 +1336,28 @@ class LocalModels(DikteTest):
self.assertIn("10", box.program_label.text())
self.assertIn("20", box.status.text())
def test_a_download_says_something_before_the_first_byte(self):
# Opening the connection takes ten or twenty seconds, and the byte
# counts only start after it. The line underneath still read "has not
# been downloaded yet" beside a button that now said Stop, so a
# download that had started looked like a click that had not landed.
box = self.window(cfg.Config()).local_llm
box.load("", "ggml-org/SmolLM3-3B-GGUF")
box.repo.blockSignals(True)
box.repo.setCurrentText("ggml-org/SmolLM3-3B-GGUF")
box.repo.blockSignals(False)
box._on_listed([("models", [self._item("SmolLM3-Q4_K_M.gguf")],
"ggml-org/SmolLM3-3B-GGUF")], "")
with mock.patch.object(settings_ui.threading, "Thread"):
box._download()
self.assertIn("Starting", box.status.text())
# And the same again for the stop, which is read between blocks and so
# not read at all while the connection is still being opened.
with mock.patch.object(settings_ui.threading, "Thread"):
box._download()
self.assertTrue(box._stop)
self.assertIn("Stopping", box.status.text())
def test_a_long_model_name_is_not_cut_in_half(self):
# The list under a combo box takes the box's width and elides what does
# not fit, in the middle: "ggml-org/Qwen....7B-Base-GGUF".