TUI: stop clipping newest lines; read streaming logprobs for confidence
ober
1d52d374941b4777f41cdb4371cbfdd9418d03a7
--- a/src/jcode/core/models.ss +++ b/src/jcode/core/models.ss @@ -54,7 +54,7 @@ ((deepseek) "deepseek-chat") ((google) "gemini-2.5-flash") ((ollama) "llama3.2") - ((mlx) "/Users/user/mine/jerboa-lora/jerboa-mlx-6bit-v3") + ((mlx) "/Users/user/llm/mlx-models/Qwen3.6-35B-A3B-TurboQuant-6bit") ((xai) "grok-3-mini") ((groq) "llama-3.3-70b-versatile") ((mistral) "mistral-large-latest") @@ -219,7 +219,7 @@ ("gemma2" . "Gemma 2"))) (def mlx-models - '(("/Users/user/mine/jerboa-lora/jerboa-mlx-4bit-v2" . "Jerboa Qwen3 30B (MLX 4-bit)"))) + '(("/Users/user/llm/mlx-models/Qwen3.6-35B-A3B-TurboQuant-6bit" . "Qwen3.6 35B-A3B TurboQuant (MLX 6-bit)"))) (def xai-models '(("grok-3" . "Grok 3") @@ -437,6 +437,10 @@ ;; ctx limit) dominates. Keep this conservative so compaction kicks ;; in before prefill blows the 32 GB wired budget. ((string-contains mid "jerboa-mlx") 16384) + ;; majentik TurboQuant local MLX builds (Qwen3.6-35B-A3B 6-bit): ~28 GB + ;; weights leave only ~4 GB under the 32 GB wired cap, so keep the window + ;; conservative — compaction must fire before prefill OOMs the GPU. + ((string-contains mid "TurboQuant") 16384) ((string-contains mid "qwen3") 32768) ((string-contains mid "llama-3") 131072) ((string-contains mid "deepseek") 131072) --- a/src/jcode/provider/provider.ss +++ b/src/jcode/provider/provider.ss @@ -606,10 +606,14 @@ (if (> p 0) (* p (log p)) 0)) norms))))))) -(def (accumulate-logprobs! delta logprob-box entropy-box) - ;; delta is a hash-table; if it has logprobs.content[], walk each token - ;; entry, push logprob and (top-k entropy if available). - (let ((lp (and (hash-table? delta) (hash-get delta "logprobs")))) +(def (accumulate-logprobs! choice logprob-box entropy-box) + ;; OpenAI-standard streaming carries logprobs at choice.logprobs (a sibling + ;; of delta); some servers nest it under choice.delta.logprobs. Accept the + ;; whole choice and check both. If logprobs.content[] is present, walk each + ;; token entry, push logprob and (top-k entropy if available). + (let* ((delta (and (hash-table? choice) (hash-get choice "delta"))) + (lp (or (and (hash-table? choice) (hash-get choice "logprobs")) + (and (hash-table? delta) (hash-get delta "logprobs"))))) (when (and lp (hash-table? lp)) (let ((entries (hash-get lp "content"))) (when (and entries (list? entries)) @@ -1196,10 +1200,12 @@ (when choice (let ((fr (hash-get choice "finish_reason"))) (when (and fr (string? fr) (not (string=? fr ""))) - (set-box! finish-reason-box fr)))) + (set-box! finish-reason-box fr))) + ;; Accumulate per-token logprobs/entropy when requested. + ;; logprobs sit at the choice level (sibling of delta), + ;; so this runs on `choice`, not gated on `delta`. + (accumulate-logprobs! choice logprob-box entropy-box)) (when delta - ;; Accumulate per-token logprobs/entropy when requested - (accumulate-logprobs! delta logprob-box entropy-box) ;; Text content token (let ((content (hash-get delta "content"))) (when (and content (string? content) (> (string-length content) 0)) --- a/src/jcode/ui/tui-message.ss +++ b/src/jcode/ui/tui-message.ss @@ -254,9 +254,14 @@ ((error) 'msg-border-error) (else 'msg-border-tool))) -(def (render-msg-block! msg x y width max-lines) - "Render msg-block at position (x, y) within width, up to max-lines rows." - (let* ((lines (msg-block-lines msg)) +(def (render-msg-block! msg x y width max-lines from-line) + "Render msg-block at position (x, y) within width, up to max-lines rows. + Skips the first FROM-LINE wrapped lines: used to bottom-anchor a block + taller than the visible area so its newest (tail) lines show, not its head." + (let* ((all (msg-block-lines msg)) + (lines (if (and (> from-line 0) (<= from-line (length all))) + (list-tail all from-line) + all)) (bg-face (face-ref 'default)) (bf (role->border-face (msg-block-role msg))) (bfg (face-fg-attr bf)) --- a/src/jcode/ui/tui.ss +++ b/src/jcode/ui/tui.ss @@ -1579,12 +1579,16 @@ ;; Partial skip: render bottom portion of this block (let* ((visible-h (- mh skip)) (start-row (max y (- row visible-h -1)))) - (render-msg-block! msg x start-row w visible-h) + ;; Head of the block; the bottom `skip` lines are below the fold. + (render-msg-block! msg x start-row w visible-h 0) (loop (cdr msgs) (- start-row 1) 0))) ;; Full render (let ((start-row (max y (- row mh -1))) (avail (min mh (- row y -1)))) - (render-msg-block! msg x start-row w avail) + ;; Bottom-anchored: when the block overflows above the top of the + ;; message area, drop its first (mh - avail) lines so the newest + ;; lines stay visible instead of being clipped off the bottom. + (render-msg-block! msg x start-row w avail (- mh avail)) (loop (cdr msgs) (- start-row 1) 0)))))))) (def (draw-spinner! state)