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:
@@ -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))
|
||||
|
||||
|
||||
@@ -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))))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -171,18 +171,95 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
|
||||
(refresh win))
|
||||
#+end_src
|
||||
|
||||
** Config panel rendering
|
||||
** Config panel
|
||||
#+begin_src lisp
|
||||
(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)))
|
||||
#+end_src
|
||||
|
||||
@@ -226,6 +303,11 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
|
||||
(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)
|
||||
@@ -260,31 +342,22 @@ The TUI Client is a Croatoan-based ncurses chat interface for Passepartout. It c
|
||||
(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
|
||||
|
||||
Reference in New Issue
Block a user