Phase 4: first-run onboarding + TUI config panel improvements

- Add providers-configured-p function (daemon-side detection)
- Add welcome log messages when no LLM providers configured
- Rewrite config panel with 4 interactive sections (Providers, Cascade, Models, View)
- Add first-run welcome messages in TUI chat on connect
- Fix config-render-models paren balance
This commit is contained in:
2026-05-04 10:36:29 -04:00
parent ef4ea1db1b
commit 3bb797ab9e
4 changed files with 218 additions and 61 deletions

View File

@@ -117,6 +117,12 @@
(actuator-initialize)
(skill-initialize-all)
;; Check for configured LLM providers
(when (zerop (hash-table-count *probabilistic-backends*))
(log-message "WELCOME: No LLM providers configured. Run 'passepartout tui' and press F2 to set up.")
(log-message "WELCOME: Supported providers: openrouter, openai, anthropic, groq, gemini, deepseek, nvidia")
(log-message "WELCOME: For free tier, start with OPENROUTER_API_KEY at https://openrouter.ai"))
;; Run proactive doctor before starting services
(diagnostics-startup-run)
@@ -139,6 +145,11 @@
(return))
(sleep sleep-interval))))
(defun providers-configured-p ()
"Returns T if at least one probabilistic backend is registered."
(and (boundp '*probabilistic-backends*)
(> (hash-table-count *probabilistic-backends*) 0)))
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload :fiveam :silent t))

View File

@@ -219,8 +219,8 @@
(incf exported)
(let ((existing (find-symbol (symbol-name sym) target-pkg)))
(when existing (unintern existing target-pkg)))
(import sym target-pkg)
(ignore-errors (export sym target-pkg))))
(import sym target-pkg)
(export sym target-pkg)))
(log-message "LOADER: Exported ~a symbols from ~a to :PASSEPARTOUT"
exported (package-name (find-package pkg-name))))

View File

@@ -133,16 +133,93 @@
(add-string win 0 0 (input-text))
(refresh win))
(defun config-provider-line (provider)
"Return formatted provider line: ' ✓ openrouter' or ' ✗ openrouter'."
(format nil " ~:[✗~;✓~] ~(~a~)" (provider-available-p provider) provider))
(defun config-render (win)
"Draw the config mini-buffer panel."
"Draw the config mini-buffer panel with menu and provider overview."
(let ((w (or (width win) 78)))
(clear win)
(box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back")
(add-string win 2 1 (format nil "~a providers configured (PROVIDER_CASCADE: ~a)"
(count-if (lambda (p) (provider-available-p p))
(mapcar #'car *provider-configs*))
(add-string win 2 1 (format nil " Set provider: ~a"
(mapcar #'config-provider-line *provider-cascade*)))
;; Show unconfigured but available providers on line 3-8
(let ((y 3)
(unconf (remove-if (lambda (p) (provider-available-p p))
(mapcar #'car *provider-configs*)))
(conf (count-if #'provider-available-p (mapcar #'car *provider-configs*))))
(add-string win 1 (- w 14) (format nil "~a/~a active" conf (length *provider-configs*)))
(when (zerop conf)
(add-string win y 1 "** No providers configured. Press 1 to set up.")
(incf y))
(when unconf
(add-string win y 1 (format nil "Available: ~{~a~^, ~}"
(mapcar (lambda (k) (string-downcase (string k))) unconf)))
(incf y))
(add-string win y 1 (format nil "Free tier: openrouter (openrouter.ai), gemma-4 from google"))
(refresh win)))
(defun config-render-providers (win)
"Show all providers with availability status."
(let ((w (or (width win) 78)))
(clear win)
(box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back")
(add-string win 2 1 " Provider Status Key Env")
(loop for entry in *provider-configs*
for i from 3
do (let* ((provider (car entry))
(config (cdr entry))
(avail (provider-available-p provider))
(key-env (or (getf config :key-env) (getf config :url-env) "--")))
(add-string win i 1 (format nil " ~:[ ✗~; ✓~] ~20@(~a~) ~a" avail provider key-env))))
(refresh win)))
(defun config-render-cascade (win)
"Show current PROVIDER_CASCADE."
(let ((w (or (width win) 78)))
(clear win)
(box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back")
(add-string win 2 1 (format nil " Cascade order: ~{~a~^ → ~}"
(mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*)))
(add-string win 3 1 " Set PROVIDER_CASCADE in .env to reorder.")
(refresh win)))
(defun config-render-models (win)
"Show per-slot model recommendations."
(let ((w (or (width win) 78)))
(clear win)
(box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back")
(let ((y 2))
(dolist (slot '(:code :chat :plan :background))
(add-string win y 1 (format nil " ~:@(~a~)" slot))
(incf y)
(let ((desc (cdr (or (assoc slot *slot-descriptions*) '(:fallback . "General purpose")))))
(add-string win y 1 (subseq desc 0 (min (length desc) (- w 4))))
(incf y))
(dolist (rec (model-explorer-recommend slot))
(let ((label (format nil " ~a (~a ctx)"
(getf rec :name)
(if (getf rec :context) (format nil "~dK" (floor (getf rec :context) 1000)) "?"))))
(add-string win y 1 (subseq label 0 (min (length label) (- w 4))))
(incf y))))))
(refresh win)))
(defun config-render-view (win)
"Show current system configuration."
(let ((w (or (width win) 78)))
(clear win)
(box win 0 0)
(add-string win 1 1 "[1] Providers [2] Cascade [3] Models [4] View [q] Back")
(add-string win 2 1 (format nil " Active backends: ~a"
(loop for k being the hash-keys of *probabilistic-backends* collect k)))
(add-string win 3 1 (format nil " Cascade: ~{~a~^, ~}"
(mapcar (lambda (k) (string-downcase (string k))) *provider-cascade*)))
(add-string win 4 1 (format nil " Model selector: ~a" (if (boundp '*model-selector*) (symbol-value '*model-selector*) "none")))
(refresh win)))
(defun connect-daemon (&optional (host "127.0.0.1") (port 9105))
@@ -180,6 +257,11 @@
(input-win (make-instance 'window :height 1 :width (- w 2) :y (- h 1) :x 1)))
(setf *is-running* t *tui-mode* :chat *input-buffer* nil)
(connect-daemon)
;; First-run: no providers configured → show welcome
(when (zerop (hash-table-count *probabilistic-backends*))
(push (cons :system "* Welcome to Passepartout! *") *chat-history*)
(push (cons :system "No LLM providers configured. Press F2 to open the config panel, then [1] Providers to set up.") *chat-history*)
(push (cons :system "For free online models, set OPENROUTER_API_KEY in your .env (or via the TUI).") *chat-history*))
(setf *chat-scroll-pos* 0)
(status-render status-win)
(chat-render chat-win chat-h)
@@ -214,31 +296,22 @@
(chat-render chat-win chat-h)))
(status-render status-win))
;; Config mode key handling
((eq *tui-mode* :config)
(cond
((or (eql ch #\q) (eql ch #\Q))
(setf *tui-mode* :chat config-h 0 chat-h (- h input-h 3))
(resize chat-win chat-h (- w 2))
(resize config-win 0 (- w 2))
(chat-render chat-win chat-h)
(status-render status-win))
((eql ch #\1)
(config-render config-win)
(add-string config-win 3 1 "Providers: check daemon log for status.")
(refresh config-win))
((eql ch #\2)
(config-render config-win)
(add-string config-win 3 1 (format nil "Cascade: ~a" *provider-cascade*))
(refresh config-win))
((eql ch #\3)
(config-render config-win)
(add-string config-win 3 1 "Models: see recommendations per slot.")
(refresh config-win))
((eql ch #\4)
(config-render config-win)
(add-string config-win 3 1 (format nil "Active providers: ~a"
(loop for k being the hash-keys of *probabilistic-backends* collect k)))
(refresh config-win))))
((eq *tui-mode* :config)
(cond
((or (eql ch #\q) (eql ch #\Q))
(setf *tui-mode* :chat config-h 0 chat-h (- h input-h 3))
(resize chat-win chat-h (- w 2))
(resize config-win 0 (- w 2))
(chat-render chat-win chat-h)
(status-render status-win))
((eql ch #\1)
(config-render-providers config-win))
((eql ch #\2)
(config-render-cascade config-win))
((eql ch #\3)
(config-render-models config-win))
((eql ch #\4)
(config-render-view config-win))))
(status-render status-win))
;; Chat mode key handling
(t