v0.10.2: voice system — all speakers use │, neuro-thinking bg bar, blinking cursor
- Theme: added :agent-border, :thinking-bg, :symbolic-border to all 13 presets with theme-load fallback for saved themes. - Agent output now draws │ with :agent-border color (muted tan). - Neuro-thinking (streaming): draw-rect at column 0 with :thinking-bg (dark grey block) instead of a grey │ character. No border text. - Gate traces: │ with :symbolic-border (was ╎ with :dim). - Tool calls: │ with tool status color (was ╎). - Removed > prompt prefix from input line. - Added position-cursor function: blinking block cursor at insertion point, called every frame from the main loop after sleep.
This commit is contained in:
@@ -1065,8 +1065,10 @@ Returns T on success, nil on failure. Does NOT wait or retry."
|
||||
(cl-tty.backend:draw-text be 0 (- h 3)
|
||||
(format nil "> ~a" (or filter ""))
|
||||
(theme-color :input-prompt) bg-p))
|
||||
(cl-tty.backend:end-sync be))
|
||||
(sleep 0.1)))
|
||||
(cl-tty.backend:end-sync be))
|
||||
(sleep 0.1)
|
||||
;; Show cursor at input position every frame
|
||||
(passepartout.channel-tui:position-cursor be w h)))
|
||||
(progn (disconnect-daemon)))))
|
||||
#+END_SRC
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ All state mutation flows through event handlers in the controller.
|
||||
(:use :cl :passepartout :usocket :bordeaux-threads)
|
||||
(:export :tui-main :st :add-msg :now :input-string
|
||||
:queue-event :drain-queue :init-state
|
||||
:view-status :view-chat :view-input :redraw
|
||||
:view-status :view-chat :view-input :redraw
|
||||
:position-cursor
|
||||
:on-key :on-daemon-msg :send-daemon
|
||||
:connect-daemon :disconnect-daemon
|
||||
:*tui-theme* :theme-color))
|
||||
@@ -33,8 +34,8 @@ All state mutation flows through event handlers in the controller.
|
||||
(defvar *event-lock* (bt:make-lock "tui-event-lock"))
|
||||
|
||||
(defvar *tui-theme*
|
||||
'(:user-fg "#fab283" :user-bg "#1e1e1e" :user-border "#fab283"
|
||||
:agent-header "#d4956a" :agent-fg "#e8e8e8"
|
||||
'( :user-fg "#fab283" :user-bg "#1e1e1e" :user-border "#fab283"
|
||||
:agent-border "#c0a080" :agent-header "#d4956a" :agent-fg "#e8e8e8"
|
||||
:system "#808080"
|
||||
:input-prompt "#fab283" :input-fg "#e8e8e8"
|
||||
:hint "#606060"
|
||||
@@ -45,17 +46,20 @@ All state mutation flows through event handlers in the controller.
|
||||
:bg-input "#2e2e2e"
|
||||
:error "#e06c75"
|
||||
:tool-running "#fab283" :tool-done "#7fd88f" :tool-error "#e06c75"
|
||||
:thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
||||
:separator "#3c3c3c" :accent "#fab283" :dim "#606060")
|
||||
"Dark-neutral color theme with warm amber accent. Backgrounds are dark grays,
|
||||
semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:text-muted, :user-fg/bg/border, :agent-header/fg, :system, :input-prompt/fg,
|
||||
:hint, :status-bg/fg, :bg-input, :dot-connected/disconnected, :error, :tool-*,
|
||||
:text-muted, :user-fg/bg/border, :agent-border/header/fg, :system,
|
||||
:input-prompt/fg, :hint, :status-bg/fg, :bg-input, :thinking-bg,
|
||||
:symbolic-border, :dot-connected/disconnected, :error, :tool-*,
|
||||
:separator, :accent, :dim.")
|
||||
|
||||
(defvar *tui-theme-presets*
|
||||
'(:amber
|
||||
(:user-fg "#fab283" :user-bg "#1e1e1e" :user-border "#fab283"
|
||||
:agent-header "#d4956a" :agent-fg "#e8e8e8"
|
||||
:agent-border "#c0a080" :thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
||||
:system "#808080"
|
||||
:input-prompt "#fab283" :input-fg "#e8e8e8"
|
||||
:hint "#606060"
|
||||
@@ -70,6 +74,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:gold
|
||||
(:user-fg "#ffd700" :user-bg "#1e1e1e" :user-border "#ffd700"
|
||||
:agent-header "#d4a574" :agent-fg "#e8e8e8"
|
||||
:agent-border "#c0a080" :thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
||||
:system "#808080"
|
||||
:input-prompt "#ffd700" :input-fg "#e8e8e8"
|
||||
:hint "#606060"
|
||||
@@ -84,6 +89,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:terracotta
|
||||
(:user-fg "#e87a5d" :user-bg "#1e1e1e" :user-border "#e87a5d"
|
||||
:agent-header "#d4956a" :agent-fg "#e0c8b0"
|
||||
:agent-border "#c0a080" :thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
||||
:system "#808080"
|
||||
:input-prompt "#e87a5d" :input-fg "#e0c8b0"
|
||||
:hint "#606060"
|
||||
@@ -153,7 +159,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:separator "#3c3c3c" :accent "#d8a657" :dim "#606060")
|
||||
:light-amber
|
||||
(:user-fg "#cc6600" :user-bg "#f5f5f5" :user-border "#cc6600"
|
||||
:agent-header "#8b6914" :agent-fg "#3a2a1a"
|
||||
:agent-header "#8b6914" :agent-fg "#3a2a1a"
|
||||
:agent-border "#a08060" :thinking-bg "#d4d4d4" :symbolic-border "#b0b0b0"
|
||||
:system "#808080"
|
||||
:input-prompt "#cc6600" :input-fg "#3a2a1a"
|
||||
:hint "#a0a0a0"
|
||||
@@ -167,7 +174,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:separator "#d4d4d4" :accent "#cc6600" :dim "#a0a0a0")
|
||||
:catppuccin
|
||||
(:user-fg "#fab387" :user-bg "#1e1e2e" :user-border "#fab387"
|
||||
:agent-header "#cba6f7" :agent-fg "#cdd6f4"
|
||||
:agent-header "#cba6f7" :agent-fg "#cdd6f4"
|
||||
:agent-border "#a6adc8" :thinking-bg "#363650" :symbolic-border "#6c7086"
|
||||
:system "#808080"
|
||||
:input-prompt "#fab387" :input-fg "#cdd6f4"
|
||||
:hint "#6c7086"
|
||||
@@ -181,7 +189,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:separator "#313244" :accent "#fab387" :dim "#585b70")
|
||||
:tokyonight
|
||||
(:user-fg "#ff9e64" :user-bg "#1a1b26" :user-border "#ff9e64"
|
||||
:agent-header "#7aa2f7" :agent-fg "#a9b1d6"
|
||||
:agent-header "#7aa2f7" :agent-fg "#a9b1d6"
|
||||
:agent-border "#7982a8" :thinking-bg "#363b54" :symbolic-border "#565f89"
|
||||
:system "#808080"
|
||||
:input-prompt "#ff9e64" :input-fg "#a9b1d6"
|
||||
:hint "#565f89"
|
||||
@@ -195,7 +204,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:separator "#292e42" :accent "#ff9e64" :dim "#444b6a")
|
||||
:dracula
|
||||
(:user-fg "#ff9580" :user-bg "#1e1f2b" :user-border "#ff9580"
|
||||
:agent-header "#bd93f9" :agent-fg "#f8f8f2"
|
||||
:agent-header "#bd93f9" :agent-fg "#f8f8f2"
|
||||
:agent-border "#c0c0e0" :thinking-bg "#3a3b50" :symbolic-border "#6272a4"
|
||||
:system "#808080"
|
||||
:input-prompt "#ff9580" :input-fg "#f8f8f2"
|
||||
:hint "#6272a4"
|
||||
@@ -209,7 +219,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:separator "#34354a" :accent "#ff9580" :dim "#5a5b7a")
|
||||
:gemini
|
||||
(:user-fg "#87afff" :user-bg "#1a1a1a" :user-border "#87afff"
|
||||
:agent-header "#d7afff" :agent-fg "#ffffff"
|
||||
:agent-header "#d7afff" :agent-fg "#ffffff"
|
||||
:agent-border "#d0d0d0" :thinking-bg "#3a3a3a" :symbolic-border "#707070"
|
||||
:system "#808080"
|
||||
:input-prompt "#87afff" :input-fg "#ffffff"
|
||||
:hint "#606060"
|
||||
@@ -223,7 +234,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
|
||||
:separator "#3a3a3a" :accent "#87afff" :dim "#5f5f5f")
|
||||
:mono
|
||||
(:user-fg "#e0e0e0" :user-bg "#1a1a1a" :user-border "#808080"
|
||||
:agent-header "#c0c0c0" :agent-fg "#d0d0d0"
|
||||
:agent-header "#c0c0c0" :agent-fg "#d0d0d0"
|
||||
:agent-border "#a0a0a0" :thinking-bg "#3a3a3a" :symbolic-border "#808080"
|
||||
:system "#808080"
|
||||
:input-prompt "#ffffff" :input-fg "#d0d0d0"
|
||||
:hint "#606060"
|
||||
@@ -263,7 +275,7 @@ Adds any missing keys with defaults to handle saved themes from older versions."
|
||||
;; Fill in any missing keys from the default preset
|
||||
(let ((defaults (getf *tui-theme-presets* *tui-theme-current-name*)))
|
||||
(when defaults
|
||||
(dolist (key '(:bg-input :bg-element :text-muted))
|
||||
(dolist (key '(:bg-input :bg-element :text-muted :agent-border :thinking-bg :symbolic-border))
|
||||
(unless (getf *tui-theme* key)
|
||||
(let ((val (getf defaults key)))
|
||||
(when val (setf (getf *tui-theme* key) val)))))))))
|
||||
|
||||
@@ -95,9 +95,9 @@ Returns a list of strings, one per line."
|
||||
(chat-w (- w sidebar-w))
|
||||
(msgs (st :messages)) (total (length msgs))
|
||||
(max-lines (- h 7)) (is-search (st :search-mode))
|
||||
(bordered-w (- chat-w (* 2 hpad) 2))
|
||||
(unbordered-w (- chat-w (* 2 hpad)))
|
||||
(y 0))
|
||||
(bordered-w (- chat-w (* 2 hpad) 2))
|
||||
(unbordered-w (- chat-w (* 2 hpad)))
|
||||
(y 0))
|
||||
(when is-search
|
||||
(let* ((matches (st :search-matches)) (idx (st :search-match-idx))
|
||||
(query (st :search-query))
|
||||
@@ -111,7 +111,9 @@ Returns a list of strings, one per line."
|
||||
(content (getf msg :content))
|
||||
(cs (if is-search (search-highlight content (st :search-query)) content))
|
||||
(pairs nil)
|
||||
(dim-fg (theme-color :dim))
|
||||
(think-bg (theme-color :thinking-bg))
|
||||
(sym-bdr (theme-color :symbolic-border))
|
||||
(agent-bdr (theme-color :agent-border))
|
||||
(user-bdr (theme-color :user-border))
|
||||
(user-fg (theme-color :user-fg))
|
||||
(agent-fg (theme-color :agent-fg))
|
||||
@@ -120,34 +122,35 @@ Returns a list of strings, one per line."
|
||||
(:user
|
||||
(dolist (l (cl-tty.box:word-wrap cs bordered-w))
|
||||
(push (list "│" user-bdr l user-fg) pairs)))
|
||||
(:agent
|
||||
( :agent
|
||||
(let* ((streaming (getf msg :streaming))
|
||||
(bdr-color (if streaming dim-fg nil))
|
||||
(bdr-str (if streaming "│" ""))
|
||||
(wrap-w (if streaming bordered-w unbordered-w))
|
||||
(think-rect (if streaming think-bg nil))
|
||||
(bdr (if streaming nil agent-bdr))
|
||||
(bstr (if streaming nil "│"))
|
||||
(wrap-w (if streaming unbordered-w bordered-w))
|
||||
(nodes (cl-tty.markdown:parse-blocks cs))
|
||||
(raw-body (or (and nodes (cl-tty.markdown:render-md nodes)) (list "")))
|
||||
(body (mapcan (lambda (l) (cl-tty.box:word-wrap l wrap-w)) raw-body)))
|
||||
(dolist (l body)
|
||||
(push (list bdr-str bdr-color l agent-fg) pairs))))
|
||||
(push (list bstr bdr l agent-fg think-rect) pairs))))
|
||||
(t (dolist (l (cl-tty.box:word-wrap cs unbordered-w))
|
||||
(push (list "" nil l system-fg) pairs))))
|
||||
(push (list nil nil l system-fg) pairs))))
|
||||
;; Gate trace
|
||||
(let ((gt (getf msg :gate-trace)))
|
||||
(when (and gt (eq role :agent))
|
||||
(if (member i (st :collapsed-gates))
|
||||
(push (list "╎" dim-fg (format nil "Gate trace: ~a gates" (length gt)) dim-fg) pairs)
|
||||
(push (list "│" sym-bdr (format nil "Gate trace: ~a gates" (length gt)) sym-bdr) pairs)
|
||||
(dolist (entry (passepartout::gate-trace-lines gt))
|
||||
(let ((ec (theme-color (getf (cdr entry) :fgcolor))))
|
||||
(dolist (l (cl-tty.box:word-wrap (car entry) bordered-w))
|
||||
(push (list "╎" dim-fg l ec) pairs)))))))
|
||||
(push (list "│" sym-bdr l ec) pairs)))))))
|
||||
;; Tool calls
|
||||
(let ((tc (getf msg :tool-calls)))
|
||||
(when tc
|
||||
(if (member i (st :collapsed-tools))
|
||||
(let* ((n (or (getf (first tc) :name) "tool"))
|
||||
(d (or (getf (first tc) :duration) 0.0)))
|
||||
(push (list "╎" dim-fg (format nil "~a … ~,1fs" n d) (theme-color :tool-done)) pairs))
|
||||
(push (list "│" (theme-color :tool-done) (format nil "~a … ~,1fs" n d) (theme-color :tool-done)) pairs))
|
||||
(dolist (call tc)
|
||||
(let* ((name (or (getf call :name) "tool"))
|
||||
(dur (or (getf call :duration) 0.0))
|
||||
@@ -159,9 +162,9 @@ Returns a list of strings, one per line."
|
||||
(t :tool-done))))
|
||||
(pfx (cond ((eq st :error) "✗") ((eq st :running) "●") (t "✓")))
|
||||
(ol (when out (cl-tty.box:word-wrap out bordered-w))))
|
||||
(push (list "╎" bc (format nil "~a ~a ~,1fs" pfx name dur) bc) pairs)
|
||||
(push (list "│" bc (format nil "~a ~a ~,1fs" pfx name dur) bc) pairs)
|
||||
(dolist (l ol)
|
||||
(push (list "╎" bc l bc) pairs)))))))
|
||||
(push (list "│" bc l bc) pairs)))))))
|
||||
(setf (aref msg-lines i) (nreverse pairs))
|
||||
(setf (aref msg-heights i) (length pairs))))
|
||||
(let ((msg-count 0) (lines-remaining max-lines))
|
||||
@@ -177,11 +180,13 @@ Returns a list of strings, one per line."
|
||||
do (let ((pairs (aref msg-lines i)))
|
||||
(dolist (pair pairs)
|
||||
(when (>= y (- h 7)) (return))
|
||||
(destructuring-bind (bstr bcolor tstr tcolor) pair
|
||||
(let ((has-border (and bstr (> (length bstr) 0))))
|
||||
(when has-border
|
||||
(cl-tty.backend:draw-text fb hpad y bstr bcolor nil))
|
||||
(cl-tty.backend:draw-text fb (+ hpad (if has-border 2 0)) y tstr tcolor nil)))
|
||||
(destructuring-bind (bstr bcolor tstr tcolor &optional rect-bg) pair
|
||||
(when rect-bg
|
||||
(cl-tty.backend:draw-rect fb 0 y 1 1 :bg rect-bg))
|
||||
(let ((has-border (and bstr (> (length bstr) 0))))
|
||||
(when has-border
|
||||
(cl-tty.backend:draw-text fb hpad y bstr bcolor nil))
|
||||
(cl-tty.backend:draw-text fb (+ hpad (if has-border 2 0)) y tstr tcolor nil)))
|
||||
(incf y)))))))))
|
||||
#+END_SRC
|
||||
|
||||
@@ -205,7 +210,7 @@ Returns a list of strings, one per line."
|
||||
;; Light grey input panel: h-7 to h-4 (4 rows), indented by hpad
|
||||
(cl-tty.backend:draw-rect fb hpad (- h 7) inner-w 4 :bg bg-i)
|
||||
;; Prompt at h-6, second row at h-5 (placeholder for expansion)
|
||||
(cl-tty.backend:draw-text fb hpad (- h 6) (format nil"> ~a" visible) input-fg nil)
|
||||
(cl-tty.backend:draw-text fb hpad (- h 6) visible input-fg nil)
|
||||
;; Hint — lowercase, right-aligned at h-2
|
||||
(let ((hint "ctrl+p | /help"))
|
||||
(cl-tty.backend:draw-text fb (- chat-w (length hint) 2) (- h 2) hint hint-fg (theme-color :bg)))))
|
||||
@@ -279,7 +284,23 @@ Returns a list of strings, one per line."
|
||||
(when (sidebar-visible-p w)
|
||||
(view-sidebar fb w h))
|
||||
(cl-tty.backend:end-sync fb)
|
||||
(position-cursor fb w h)
|
||||
(setf (st :dirty) (list nil nil nil))))
|
||||
|
||||
(defun position-cursor (fb w h)
|
||||
"Move blinking block cursor to the input insertion point."
|
||||
(let* ((sw (if (sidebar-visible-p w) (or (st :sidebar-width) 42) 0))
|
||||
(cw (- w sw))
|
||||
(hpad 2)
|
||||
(text (input-string))
|
||||
(pos (or (st :cursor-pos) 0))
|
||||
(prompt-w (- cw (* 2 hpad) 2))
|
||||
(display-start (max 0 (- pos (1- prompt-w))))
|
||||
(cx (+ hpad (- pos display-start)))
|
||||
(cy (- h 6)))
|
||||
(cl-tty.backend:cursor-move fb cx cy)
|
||||
(cl-tty.backend:cursor-style fb :block :blink t)
|
||||
(cl-tty.backend:cursor-show fb)))
|
||||
#+END_SRC
|
||||
|
||||
* Implementation — v0.7.0 additions
|
||||
@@ -624,10 +645,13 @@ and current sidebar mode."
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :status-bg)))
|
||||
|
||||
(test test-new-theme-keys
|
||||
"v0.10.0: theme has new :bg, :bg-panel, :bg-element, :bg-input, :text-muted keys."
|
||||
"v0.10.0: theme has all zone keys."
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :bg))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :bg-panel))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :bg-element))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :bg-input))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :agent-border))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :thinking-bg))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :symbolic-border))
|
||||
(is (getf passepartout.channel-tui::*tui-theme* :text-muted)))
|
||||
#+END_SRC
|
||||
|
||||
Reference in New Issue
Block a user