spacr.qt.ai.worker
==================

.. py:module:: spacr.qt.ai.worker

.. autoapi-nested-parse::

   QThread worker that streams chat completions from a ChatProvider so
   the UI stays responsive during long generations.

   Emits:
       stage_changed(str)  — coarse progress: "connecting", "streaming"
       chunk_ready(str)    — a partial completion chunk
       finished(bool, str) — (ok, full_text_or_error)







Module Contents
---------------

.. py:class:: StreamWorker(provider: spacr.qt.ai.providers.ChatProvider, messages: List[Dict], system: str = '', model: Optional[str] = None)

   Bases: :py:obj:`PySide6.QtCore.QObject`


   QObject that drives one provider stream on a worker QThread.

   :ivar stage_changed: coarse progress signal ("connecting", "streaming").
   :ivar chunk_ready: emitted with each partial completion chunk.
   :ivar finished: emitted with ``(ok, full_text_or_error)`` on completion.


   .. py:attribute:: stage_changed


   .. py:attribute:: chunk_ready


   .. py:attribute:: finished


   .. py:method:: cancel() -> None

      Cancel: kill the subprocess so the reader unblocks.

      Setting a Python flag alone isn't enough — the worker is
      blocked in a `for line in proc.stdout` iteration until the
      subprocess writes or closes. We terminate the subprocess
      directly via `provider.cancel_stream()`; the reader then
      exits with an empty read and run() completes cleanly.



   .. py:method:: run() -> None

      Consume the provider stream, emitting stage/chunk/finished signals.



.. py:function:: make_stream_thread(provider: spacr.qt.ai.providers.ChatProvider, messages: List[Dict], system: str = '', model: Optional[str] = None, parent: Optional[PySide6.QtCore.QObject] = None) -> tuple[PySide6.QtCore.QThread, StreamWorker]

   Return (QThread, StreamWorker) — connect signals, then start().

   IMPORTANT: pass a `parent` (typically the panel that owns this
   stream). Without a Qt parent the QThread's C++ object gets tied
   exclusively to Python's refcount — and dropping the ref while
   QThread.isRunning() is still True (which happens in the tiny
   window between worker.run returning and thread.finished firing)
   triggers Qt's `QThread: Destroyed while thread is still running /
   Aborted` crash. A parent keeps the C++ object alive until
   deleteLater runs.

   Callers must ALSO keep a Python reference to the worker until
   the stream truly finishes (see ConsolePanel._retire).


