You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#145 — Ask Splitzy: personal spending chatbot (self-hosted LLM)
What to build
The Angular web chat UI: a service that streams SSE responses from POST /api/chat/stream, a chat component (message list, input, streaming append, loading/error/refusal states), and an entry point from the dashboard. Consumes the full contract established by #148 (history + streaming).
Implementation Steps
Endpoint + types — Add CHAT_STREAM: '/api/Chat/stream' to environment.endpoints in UI/src/environments/environment.ts (and any other environment files present, e.g. environment.prod.ts). Add ChatTurn, ChatRequest, and a client-side ChatMessage (role, content, streaming flag) type to UI/src/app/splitz/splitz.model.ts.
Chat service — Create UI/src/app/splitz/services/chat.service.ts following the DI/auth pattern in UI/src/app/splitz/services/splitz.service.ts. Since EventSource cannot send custom headers, use fetch() with Authorization: Bearer <token> (via SplitzService.getToken()/TokenStorageService) and read the response body with a ReadableStream reader, parsing SSE frames and emitting tokens (e.g. via an Observable or async generator) as they arrive.
Chat component — Create UI/src/app/splitz/dashboard/chat/chat.component.ts / .html / .css: message list (user + assistant turns), input box, appends streamed tokens to the in-progress assistant message, loading indicator while awaiting the first token, and a distinct visual style for refusal responses vs. data answers. Maintain the trimmed turn history client-side and send it with each request per Multi-turn conversation context (stateless history) #148's contract.
Entry point — Add a chat entry point (button/icon) to the dashboard (UI/src/app/splitz/dashboard/) that opens the chat component, and a route if the component is a full page rather than a widget.
Manual verification — Run the Angular dev server, ask a balance question, a category-spend question, a follow-up ("and last month?"), and an out-of-scope question; confirm streaming renders token-by-token and all four response types display correctly, per this repo's practice of manually verifying UI changes in-browser before considering the work done.
Angular 20, standalone components, Tailwind CSS. Existing service pattern: SplitzService (UI/src/app/splitz/services/splitz.service.ts) using HttpClient + BehaviorSubject for auth state, TokenStorageService for token retrieval.
No existing SSE/streaming consumer in this codebase — this is the first one; base it on standard fetch + ReadableStream, no new library needed.
Dependencies in scope
Reuse: @angular/common/http is present but not sufficient for authenticated SSE (no header support in EventSource), so this slice uses native fetch directly, consistent with browser-standard streaming — no new npm package required.
New dependency additions allowed for this slice: no.
Architecture alignment
Follow existing services/*.service.ts + standalone component conventions already used under UI/src/app/splitz/.
Auth token retrieval must go through SplitzService/TokenStorageService, not a new token mechanism.
create-git-issue provides routing hints only; it must not assign concrete agent/model names.
run-with-it remains the final runtime routing authority.
Parent
#145 — Ask Splitzy: personal spending chatbot (self-hosted LLM)
What to build
The Angular web chat UI: a service that streams SSE responses from
POST /api/chat/stream, a chat component (message list, input, streaming append, loading/error/refusal states), and an entry point from the dashboard. Consumes the full contract established by #148 (history + streaming).Implementation Steps
CHAT_STREAM: '/api/Chat/stream'toenvironment.endpointsinUI/src/environments/environment.ts(and any other environment files present, e.g.environment.prod.ts). AddChatTurn,ChatRequest, and a client-sideChatMessage(role, content, streaming flag) type toUI/src/app/splitz/splitz.model.ts.UI/src/app/splitz/services/chat.service.tsfollowing the DI/auth pattern inUI/src/app/splitz/services/splitz.service.ts. SinceEventSourcecannot send custom headers, usefetch()withAuthorization: Bearer <token>(viaSplitzService.getToken()/TokenStorageService) and read the response body with aReadableStreamreader, parsing SSE frames and emitting tokens (e.g. via anObservableor async generator) as they arrive.UI/src/app/splitz/dashboard/chat/chat.component.ts/.html/.css: message list (user + assistant turns), input box, appends streamed tokens to the in-progress assistant message, loading indicator while awaiting the first token, and a distinct visual style for refusal responses vs. data answers. Maintain the trimmed turn history client-side and send it with each request per Multi-turn conversation context (stateless history) #148's contract.UI/src/app/splitz/dashboard/) that opens the chat component, and a route if the component is a full page rather than a widget.Agent Routing
Technical Context Snapshot
Current stack in scope
SplitzService(UI/src/app/splitz/services/splitz.service.ts) usingHttpClient+BehaviorSubjectfor auth state,TokenStorageServicefor token retrieval.fetch+ReadableStream, no new library needed.Dependencies in scope
@angular/common/httpis present but not sufficient for authenticated SSE (no header support inEventSource), so this slice uses nativefetchdirectly, consistent with browser-standard streaming — no new npm package required.Architecture alignment
services/*.service.ts+ standalone component conventions already used underUI/src/app/splitz/.SplitzService/TokenStorageService, not a new token mechanism.create-git-issueprovides routing hints only; it must not assign concrete agent/model names.run-with-itremains the final runtime routing authority.Integration touchpoints
POST /api/chat/streamas defined by Chat foundation: streaming balance intent + router skeleton #146/Multi-turn conversation context (stateless history) #148 — no backend changes in this slice.Acceptance criteria
Blocked by