AI Advisor Frontend
🖥️ AI Advisor Frontend
Section titled “🖥️ AI Advisor Frontend”The advisor UI follows the same hexagonal shape as the rest of apps/frontend: a port, a REST adapter, Zod-validated DTOs, Pinia Colada for server state, and feature-sliced components. See Hexagonal Architecture.
flowchart TD
Header["AppHeader.vue<br/>Sparkles button, Mod+/"] --> PanelState["useAdvisorPanel<br/>module singleton"]
PanelState --> Panel["AdvisorPanel.vue (mounted once in App.vue)"]
Panel --> ChatC["useAdvisorChat(port)"]
Panel --> CfgQ["useAdvisorConfigQuery<br/>enabled while open"]
ChatC --> Turn["AdvisorTurn.vue"]
Turn --> MD["AdvisorMarkdown.vue<br/>markdown-it"]
Turn --> Tools["AdvisorToolActivity.vue"]
Turn --> Outcome["AdvisorOutcome.vue"]
Panel --> Empty["AdvisorEmptyState.vue"]
ChatC --> PortF["IAdvisorPort (ADVISOR_PORT_KEY)"]
CfgQ --> PortF
PortF --> RestA["RestAdvisorAdapter"]
SettingsView["SettingsView.vue"] --> AS["AdvisorSettings.vue"]
AS --> ModelEd["ModelChainEditor + useModelChainEditor"]
AS --> ProfEd["ExecutionProfilesEditor + useExecutionProfilesEditor"]
ModelEd --> Mut["useSetModelChainMutation"]
ProfEd --> Mut2["useSetExecutionProfilesMutation"]
Mut --> PortF
Mut2 --> PortF
🔌 Port and Adapter
Section titled “🔌 Port and Adapter”IAdvisorPort (frontend) declares the streaming ask(request, signal) alongside the config methods, following the market-data precedent. RestAdvisorAdapter reads the stream by hand: the browser EventSource cannot POST and reconnects on its own, which would silently re-run (and re-bill) a finished LLM call. Every retry is an explicit user action. The adapter reports each transport failure to the errorBus exactly once; the consumer never re-toasts. Config calls use bffClient and validate with AdvisorConfigSchema / executionProfilesSchema.
🧠 State
Section titled “🧠 State”useAdvisorChatholds turns and the thread id in memory only. It exposessend,abort,retry, andnewConversation. Turn outcomes are the wire outcomes plus two local ones,transport-lostanduser-aborted.isRetryableOutcomepermits retry after a network error, stream end, HTTP 5xx or 429, a failed run whose cause is temporary (rate-limited,provider-unavailable,network,unknown), or a user stop; not after a malformed frame, a missing body, an HTTP 4xx other than 429, a rejected key, or an unknown model.- Server state (config, profiles) goes through Pinia Colada (
useAdvisorQueries.ts,useAdvisorMutations.ts); there is no global Pinia store. The config query is enabled only while the panel is open. useAdvisorPanelis a module singleton because the header button and the panel live in unrelated subtrees.
💬 Chat Panel UX
Section titled “💬 Chat Panel UX”- A right-hand sheet opened from the header button or Mod + / (Cmd on macOS, Ctrl elsewhere). Escape closes it and focus returns to the trigger.
- Answers stream as Markdown (
markdown-it): raw HTML off, images off, no auto-linking, only in-app paths stay clickable; inline code rendersfont-mono tabular-nums. - Each tool call shows an activity row (running, finished, or failed).
- The header shows the active model id and a local / cloud badge from
classifyExecutionProfile, the same rule as the backend. The active model is the one that answered the latest turn, else the first chain entry. - Below an answer: a “Figures incomplete” badge, the disclaimer footer (only when
disclaimeris true, never inline), and a mutedstepsUsed / maxStepsfooter once the run is done. - A
refusedrun shows an “Answer withheld” alert with one reformulation chip. Afailedrun shows copy per code;NO_MODEL_AVAILABLEandVAULT_LOCKEDlink to Settings, andALL_PROVIDERS_FAILEDpicks copy percause.kindnaming the provider and model (auth-rejectedpoints at the API key,model-not-foundat the model id). - Enter sends, Shift+Enter inserts a newline, IME composition is respected; Stop aborts immediately; a second send during a run is ignored. Autoscroll pauses while the user scrolls up (
useStickToBottom). Reduced-motion disables the slide and caret animation. AppToaster.vueoffsets toasts below the sheet header while the panel is open so they do not cover the close button.- The empty state offers suggested questions in two groups, “Taxes” and “Your portfolio”.
⚙️ Settings: AI Advisor
Section titled “⚙️ Settings: AI Advisor”- Model-chain editor: add, remove, reorder; provider select, model id, and a
contextWindowinput only for a localollamaentry; derived local/cloud and profile labels; a credential flag for keyed providers; an empty chain cannot be saved. - Execution-profile editor: numeric limits with the schema ceilings enforced client-side, metered per-tool budgets editable, local budgets shown read-only as derived from the declared window, reset to defaults. Form state is text, so a half-typed number is never coerced early.
- Provider API keys are entered under Settings → Vault, in the “AI Providers” group.
🌐 i18n and Design System
Section titled “🌐 i18n and Design System”- i18n: every string uses
useI18n(); keys live underadvisor.*,settings.advisor.*, andvault.category.*inen.tsandes.ts. Theinterpolatehelper fills{name}placeholders in one pass so values containing$&or{other}stay literal. - Design system: only
DESIGN.mdtokens are used;designTokens.spec.tsscans the advisor components for invented classes. Profit and loss colours never colour prose. See Institutional Light Design.