diff --git a/docs/roadmaps/not-started/LOCAL_AI_CONSOLIDATION_ROADMAP.md b/docs/roadmaps/not-started/LOCAL_AI_CONSOLIDATION_ROADMAP.md index 68e212a6..522992f3 100644 --- a/docs/roadmaps/not-started/LOCAL_AI_CONSOLIDATION_ROADMAP.md +++ b/docs/roadmaps/not-started/LOCAL_AI_CONSOLIDATION_ROADMAP.md @@ -27,6 +27,127 @@ Both repos have a **chat workspace** that talks to Ollama, stores conversations, --- +## Execution Checklist + +> **Track progress here.** Check each box as you go. Each `COMMIT` line is a commit point — verify before committing. +> Update the `Status` header at the top of this file as you progress. + +### Phase 1 — Remove Workspace from LLM Lab (~2 hours) + +- [ ] **1.0** Pre-flight: `cd dashboard && pnpm test && pnpm run build` (establish green baseline) +- [ ] **1.1** Delete `dashboard/src/app/(workspace)/` directory (22 files: 6 routes + 16 components) +- [ ] **1.2** Delete `dashboard/src/app/api/ollama/title/` directory (1 workspace-only route) +- [ ] **1.3** Delete workspace lib files: `db.ts`, `agents.ts`, `quick-actions.ts`, `scheduled-tasks.ts`, `cron.ts`, `migrate.ts` +- [ ] **1.3a** Trim `types.ts`: move `Attachment` + `ModelDefaults` to shared section (lines 1–77), then delete everything from line 78 onward +- [ ] **1.4** Delete workspace tests: `__tests__/agents.test.ts`, `__tests__/cron.test.ts` +- [ ] **1.5** Create `dashboard/src/app/page.tsx` → `redirect('/mission-control')` +- [ ] **1.6a** Remove npm deps from `package.json`: `idb`, `cron-parser`, `fuse.js` +- [ ] **1.6b** Run `pnpm install` to update lockfile +- [ ] **1.7** Verify: `pnpm run typecheck && pnpm test && pnpm run build` +- [ ] COMMIT → `refactor(dashboard): remove chat workspace — LocalMemGPT is the chat surface` +- [ ] **1.8a** Delete `e2e/chat.spec.ts` +- [ ] **1.8b** Update `e2e/health.spec.ts` — remove `/c/test-id` test +- [ ] **1.8c** Update `e2e/accessibility.spec.ts` — remove `/compare`, `/tts`, `/whisper` from routes +- [ ] **1.8d** Update `e2e/visual-regression.spec.ts` — remove `workspace` and `compare` from ROUTES +- [ ] **1.8e** Update `e2e/navigation.spec.ts` — `/` now redirects to `/mission-control` +- [ ] **1.8f** Update `e2e/models.spec.ts` — `/` now redirects +- [ ] COMMIT → `test(e2e): update specs after workspace removal` +- [ ] **1.9** Update `AGENTS.md`, `README.md`, `CLAUDE.md`, `.windsurfrules`, `.cursorrules` +- [ ] COMMIT → `docs: update agent docs after workspace removal` + +### Phase 2 — Cross-Links (~30 minutes) + +- [ ] **2.1** Add "Chat with models →" card/banner in Mission Control (LLM Lab repo, env: `LOCALMEMGPT_URL`) +- [ ] **2.1a** Add `LOCALMEMGPT_URL` to `.env.example` +- [ ] COMMIT → `feat(dashboard): add LocalMemGPT cross-link in Mission Control` +- [ ] **2.2** Add "Model Management →" link in Settings panel (LocalMemGPT repo, env: `LOCALLLMLAB_URL`) +- [ ] **2.2a** Add `LOCALLLMLAB_URL` to `.env.example` +- [ ] COMMIT → `feat(web): add LLM Lab cross-link in settings` + +### Phase 3 (Optional) — Port Features to LocalMemGPT + +> Only after Phase 1+2 are complete and you've identified what's actually missed. + +- [ ] **3.1** Conversation Attachments (P1, ~1 day) +- [ ] **3.2** Model Ratings (P1, ~2 hours) +- [ ] **3.3** Projects / Folders (P1, ~4 hours) +- [ ] **3.4** Custom Agents (P2, ~4 hours) +- [ ] **3.5** Quick Actions (P2, ~6 hours) +- [ ] **3.6** Voice Input — Whisper STT (P2, ~4 hours) +- [ ] **3.7** Orchestrations (P3, ~1 day) +- [ ] **3.8** Scheduled Tasks (P3, ~4 hours) + +### Phase 4 — DRY Extractions (~3–5 days, independent of Phases 1–3) + +- [ ] **4.1** Create `@bytelyst/ollama-client` package in `learning_ai_common_plat/packages/` +- [ ] **4.1a** Migrate `learning_ai_local_memory_gpt/backend/src/lib/ollama.ts` → import from package +- [ ] **4.1b** Simplify LLM Lab `api/ollama/*` routes + `ollama-config.ts` → import from package +- [ ] **4.1c** Publish to Gitea registry +- [ ] COMMIT → `feat(ollama-client): shared Ollama API client package` +- [ ] **4.2** Add per-request helpers to `@bytelyst/fastify-sse` (startSSE, sendSSEData, endSSE) +- [ ] **4.2a** Migrate LocalMemGPT `lib/sse-helpers.ts` → import from package +- [ ] COMMIT → `feat(fastify-sse): add per-request SSE helpers` +- [ ] **4.3** Create `@bytelyst/use-theme` package (configurable storage key + DOM strategy) +- [ ] **4.3a** Migrate 6 web apps to import from package +- [ ] COMMIT → `feat(use-theme): shared React theme toggle hook` +- [ ] **4.4** Create `@bytelyst/use-keyboard-shortcuts` package (data-driven ShortcutDef[]) +- [ ] **4.4a** Migrate 5 web apps to import from package +- [ ] COMMIT → `feat(use-keyboard-shortcuts): shared React keyboard shortcuts hook` + +--- + +## Safety Matrix — Delete vs Keep + +> **Verified by import analysis.** No Mission Control or API route code imports any file in the DELETE column. + +### Files to DELETE (workspace-only) + +| File / Directory | Verified By | +| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `(workspace)/` — 6 routes + 16 components (22 files) | No imports from `(mission-control)/` or `api/` | +| `api/ollama/title/` — 1 route | Only imported by `ConversationView.tsx` (deleted) | +| `lib/db.ts` — IndexedDB client | Only imported by workspace components | +| `lib/agents.ts` — agent definitions | Only imported by workspace components | +| `lib/quick-actions.ts` — quick action defs | Only imported by workspace components | +| `lib/scheduled-tasks.ts` — task scheduler | Only imported by workspace components | +| `lib/cron.ts` — cron parser | Only imported by `TaskEditor.tsx`, `TaskRunner.tsx` (deleted) | +| `lib/migrate.ts` — IndexedDB migration | Only imported by `db.ts` (deleted) | +| `types.ts` lines 78–118, 127–162, 170–221 | Workspace types: Conversation, Message, Agent, AgentTool, QuickAction, Project, ScheduledTask, Orchestration | +| `__tests__/agents.test.ts`, `__tests__/cron.test.ts` | Test deleted lib files | +| `e2e/chat.spec.ts` | Tests workspace chat flow | +| npm deps: `idb`, `cron-parser`, `fuse.js` | Only used by deleted files | + +### Files to KEEP (Mission Control + shared infrastructure) + +| File / Directory | Used By (verified) | +| ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `(mission-control)/` — page.tsx + components | Core product — model ops, benchmarks, hardware | +| `api/ollama/route.ts` — model list/pull/delete | Mission Control model management | +| `api/ollama/pull/` — pull with progress | Mission Control model pull UI | +| `api/ollama/chat/` — chat proxy | Mission Control model testing (page.tsx:719) | +| `api/ollama/stream/` — streaming proxy | Mission Control comparison/benchmarks (page.tsx:331,538) | +| `api/ollama/logs/` — log viewer | Mission Control log viewer (page.tsx:297) | +| `api/tts/` — TTS engine | Mission Control TTS panel (page.tsx:179) | +| `api/whisper/` — Whisper status | Mission Control Whisper card (page.tsx:165) | +| `api/whisper/transcribe/` — transcription test | Mission Control test button (page.tsx:313) | +| `api/system/`, `system/memory/`, `system/exec/` | Mission Control hardware stats | +| `api/health/`, `api/extraction/` | Infrastructure | +| `lib/llm-router.ts` | `api/ollama/chat/route.ts` | +| `lib/ollama-config.ts` | All `api/ollama/*` routes | +| `lib/product-config.ts` | Root layout | +| `lib/router.ts` | `api/ollama/chat/route.ts` (imports Attachment + ModelDefaults) | +| `lib/format.ts` | Mission Control page.tsx + `RamBudgetBar.tsx` | +| `lib/use-theme.ts` | Mission Control page.tsx | +| `lib/use-keyboard-shortcuts.ts` | Shared keyboard shortcuts | +| `types.ts` lines 1–77 + Attachment (119–126) + ModelDefaults (163–169) | Mission Control types + `router.ts` dependency chain | +| `src/components/` — 6 shared components | Mission Control page.tsx imports all 6 | +| `__tests__/` — 5 test files (format, ollama-config, product-config, use-keyboard-shortcuts, use-theme) | Tests for kept lib files | +| `e2e/` — 6 specs (updated, not deleted) | Mission Control + navigation + accessibility | +| `globals.css` | All shared styles (verified: no workspace-specific tokens) | +| `docs/`, `tts/`, `scripts/`, `experiments/`, `chat-history/` | Non-dashboard assets | + +--- + ## Review Findings > **Pass 1:** 13 bugs/gaps identified during systematic codebase verification (2026-03-29). @@ -216,15 +337,15 @@ These are **optional ports** — only if they prove valuable: #### 1.1 Delete workspace route group -Delete the entire `(workspace)/` directory: +- [ ] Delete the entire `(workspace)/` directory: ``` -dashboard/src/app/(workspace)/ # 22 items — layout, pages, 16 components +dashboard/src/app/(workspace)/ # 22 files — layout, 5 pages, 16 components ``` #### 1.2 Delete workspace-specific API routes -Delete **only** the route that is workspace-only: +- [ ] Delete **only** the route that is workspace-only: ``` dashboard/src/app/api/ollama/title/ # Auto-title generation (workspace-only) @@ -241,7 +362,7 @@ dashboard/src/app/api/ollama/title/ # Auto-title generation (workspace-only) #### 1.3 Clean up lib files -Delete workspace-only lib files: +- [ ] Delete workspace-only lib files: ``` dashboard/src/app/lib/db.ts # IndexedDB (conversations, messages, agents, etc.) @@ -252,10 +373,16 @@ dashboard/src/app/lib/cron.ts # Cron parser dashboard/src/app/lib/migrate.ts # v3→v4 migration ``` -Remove workspace types from `types.ts` (lines 78–221: Conversation, Message, Agent, QuickAction, Project, ScheduledTask, Orchestration). Keep lines 1–77 (OllamaModel, RunningModel, OllamaData, WhisperModel, WhisperData, SystemData, Toast, PullProgress, StreamMetrics). **[F9, R8] Also keep `Attachment` (lines 119–126) and `ModelDefaults` (lines 163–169)** — both are imported by `router.ts` (line 1: `import type { Attachment, ModelDefaults } from './types'`) which is used by `api/ollama/chat/route.ts` (shared). +- [ ] Trim `types.ts` — **[F9, R8] do this carefully:** + 1. Copy `Attachment` (lines 119–126) and `ModelDefaults` (lines 163–169) into the shared section (after line 77) + 2. Delete everything from the original line 78 (`// --- v4 Workspace Types ---`) to end of file + 3. **Why:** `router.ts` line 1 does `import type { Attachment, ModelDefaults } from './types'` → used by `api/ollama/chat/route.ts` (Mission Control) + 4. **Verify:** `grep -r 'from.*types' dashboard/src/app/lib/router.ts` should still resolve #### 1.4 Delete workspace-specific tests +- [ ] Delete workspace test files: + ``` dashboard/src/app/lib/__tests__/agents.test.ts dashboard/src/app/lib/__tests__/cron.test.ts @@ -265,7 +392,7 @@ dashboard/src/app/lib/__tests__/cron.test.ts **[F3] CRITICAL:** The workspace route group `(workspace)/page.tsx` is the current root page (`/`). There is no separate `src/app/page.tsx`. After deleting the workspace, `GET /` returns 404. -**Required:** Create `dashboard/src/app/page.tsx` that redirects to `/mission-control`: +- [ ] Create `dashboard/src/app/page.tsx` that redirects to `/mission-control`: ```tsx import { redirect } from 'next/navigation'; @@ -274,8 +401,8 @@ export default function Home() { } ``` -- Verify `layout.tsx` root has no workspace-specific imports (confirmed: it only imports fonts, design tokens, product-config, and Providers — no workspace references) -- Update any shared components that reference workspace routes +- [ ] Verify `layout.tsx` root has no workspace-specific imports (confirmed: it only imports fonts, design tokens, product-config, and Providers — no workspace references) +- [ ] Grep for any remaining imports of deleted files: `grep -r 'from.*\(workspace\)\|from.*db\|from.*agents\|from.*quick-actions\|from.*scheduled-tasks\|from.*cron\|from.*migrate' dashboard/src/` #### 1.6 ~~Update globals.css~~ — NO ACTION NEEDED [F8] @@ -283,27 +410,38 @@ export default function Home() { #### 1.6a Remove workspace-only npm dependencies [F7] -Remove from `dashboard/package.json`: - -- `idb` — IndexedDB client, only used by `db.ts` (workspace) -- `cron-parser` — only used by `cron.ts` and `TaskEditor.tsx` / `TaskRunner.tsx` (workspace) -- `fuse.js` — only used by `CommandPalette.tsx` (workspace) +- [ ] Remove from `dashboard/package.json`: + - `idb` — IndexedDB client, only used by `db.ts` (workspace) + - `cron-parser` — only used by `cron.ts` and `TaskEditor.tsx` / `TaskRunner.tsx` (workspace) + - `fuse.js` — only used by `CommandPalette.tsx` (workspace) +- [ ] Run `pnpm install` to update lockfile #### 1.7 Verify +- [ ] Run and confirm all pass: + ```bash cd dashboard && pnpm run typecheck && pnpm test && pnpm run build ``` -**Commit:** `refactor(dashboard): remove chat workspace — LocalMemGPT is the chat surface` +- [ ] **COMMIT:** `refactor(dashboard): remove chat workspace — LocalMemGPT is the chat surface` -#### 1.8 Update documentation +#### 1.8 Update E2E specs -- Update `AGENTS.md` — remove workspace references, update repo layout, adjust test count -- Update `README.md` — remove chat workspace description, add note pointing to Local Memory GPT for chat -- Update `CLAUDE.md`, `.windsurfrules`, `.cursorrules` — align with new scope +- [ ] Delete `e2e/chat.spec.ts` — workspace chat flow +- [ ] Update `e2e/health.spec.ts` — remove `Conversation Routes` test navigating to `/c/test-id` +- [ ] Update `e2e/accessibility.spec.ts` — remove `/compare`, `/tts`, `/whisper` from routes array +- [ ] Update `e2e/visual-regression.spec.ts` — remove `workspace` and `compare` from ROUTES +- [ ] Update `e2e/navigation.spec.ts` — `/` now redirects to `/mission-control` +- [ ] Update `e2e/models.spec.ts` — `/` now redirects +- [ ] **COMMIT:** `test(e2e): update specs after workspace removal` -**Commit:** `docs: update agent docs after workspace removal` +#### 1.9 Update documentation + +- [ ] Update `AGENTS.md` — remove workspace references, update repo layout, adjust test count +- [ ] Update `README.md` — remove chat workspace description, add note pointing to Local Memory GPT for chat +- [ ] Update `CLAUDE.md`, `.windsurfrules`, `.cursorrules` — align with new scope +- [ ] **COMMIT:** `docs: update agent docs after workspace removal` --- @@ -315,22 +453,20 @@ cd dashboard && pnpm run typecheck && pnpm test && pnpm run build #### 2.1 Local LLM Lab → Local Memory GPT link -Add a prominent link in Mission Control dashboard pointing to Local Memory GPT: - -- "Chat with your models →" card or banner -- Links to `http://localhost:3070` (LocalMemGPT web default port) -- Configurable via env var: `LOCALMEMGPT_URL` - -**Commit:** `feat(dashboard): add LocalMemGPT cross-link in Mission Control` +- [ ] Add a prominent link in Mission Control dashboard pointing to Local Memory GPT: + - "Chat with your models →" card or banner + - Links to `http://localhost:3070` (LocalMemGPT web default port) + - Configurable via env var: `LOCALMEMGPT_URL` +- [ ] Add `LOCALMEMGPT_URL` to `.env.example` +- [ ] **COMMIT:** `feat(dashboard): add LocalMemGPT cross-link in Mission Control` #### 2.2 Local Memory GPT → Local LLM Lab link -Add a link in Settings panel: - -- "Model Management →" pointing to `http://localhost:3000` (LLM Lab dashboard) -- Configurable via env var: `LOCALLLMLAB_URL` - -**Commit:** `feat(web): add LLM Lab cross-link in settings` +- [ ] Add a link in Settings panel: + - "Model Management →" pointing to `http://localhost:3000` (LLM Lab dashboard) + - Configurable via env var: `LOCALLLMLAB_URL` +- [ ] Add `LOCALLLMLAB_URL` to `.env.example` +- [ ] **COMMIT:** `feat(web): add LLM Lab cross-link in settings` --- @@ -346,10 +482,11 @@ Only pursue these if the features are missed after Phase 1. LocalMemGPT currently has no attachment support. LLM Lab supports image/file/audio/url attachments. -- Add `attachments` field to messages table (SQLite JSON column) -- Update chat SSE handler to pass images to Ollama vision models -- Update `MessageBubble.tsx` to render attachments -- Update `ChatView.tsx` input to support file upload +- [ ] Add `attachments` field to messages table (SQLite JSON column) +- [ ] Update chat SSE handler to pass images to Ollama vision models +- [ ] Update `MessageBubble.tsx` to render attachments +- [ ] Update `ChatView.tsx` input to support file upload +- [ ] **COMMIT:** `feat(chat): add attachment support for multimodal input` **Effort:** ~1 day @@ -357,9 +494,10 @@ LocalMemGPT currently has no attachment support. LLM Lab supports image/file/aud LLM Lab has per-message thumbs up/down ratings with IndexedDB storage. -- Add `rating` column to messages table -- Add `PATCH /api/messages/:id/rate` endpoint -- Update `MessageBubble.tsx` with rating buttons +- [ ] Add `rating` column to messages table +- [ ] Add `PATCH /api/messages/:id/rate` endpoint +- [ ] Update `MessageBubble.tsx` with rating buttons +- [ ] **COMMIT:** `feat(messages): add per-message model ratings` **Effort:** ~2 hours @@ -367,10 +505,11 @@ LLM Lab has per-message thumbs up/down ratings with IndexedDB storage. LLM Lab groups conversations into projects. LocalMemGPT has no grouping. -- Add `folders` table to SQLite -- Add `folderId` column to conversations table -- Add folder CRUD endpoints -- Update Sidebar to show folder tree +- [ ] Add `folders` table to SQLite +- [ ] Add `folderId` column to conversations table +- [ ] Add folder CRUD endpoints +- [ ] Update Sidebar to show folder tree +- [ ] **COMMIT:** `feat(conversations): add folder grouping` **Effort:** ~4 hours @@ -378,10 +517,11 @@ LLM Lab groups conversations into projects. LocalMemGPT has no grouping. LLM Lab has 6 built-in agents + custom agent editor. LocalMemGPT supports per-conversation system prompts but no reusable agent definitions. -- Add `agents` table to SQLite -- Add agent CRUD endpoints -- Add agent picker in new conversation flow -- Port 6 built-in agent definitions +- [ ] Add `agents` table to SQLite +- [ ] Add agent CRUD endpoints +- [ ] Add agent picker in new conversation flow +- [ ] Port 6 built-in agent definitions +- [ ] **COMMIT:** `feat(agents): add reusable agent definitions with built-in presets` **Effort:** ~4 hours @@ -389,10 +529,11 @@ LLM Lab has 6 built-in agents + custom agent editor. LocalMemGPT supports per-co LLM Lab has 30+ built-in quick actions (code review, explain, summarize, etc.). -- Add `quick_actions` table to SQLite -- Add quick action CRUD + execute endpoints -- Add Cmd+K command palette component -- Port built-in quick action definitions +- [ ] Add `quick_actions` table to SQLite +- [ ] Add quick action CRUD + execute endpoints +- [ ] Add Cmd+K command palette component +- [ ] Port built-in quick action definitions +- [ ] **COMMIT:** `feat(quick-actions): add command palette with 30+ built-in actions` **Effort:** ~6 hours @@ -400,9 +541,10 @@ LLM Lab has 30+ built-in quick actions (code review, explain, summarize, etc.). LLM Lab has whisper.cpp integration for speech-to-text. -- Add `POST /api/whisper/transcribe` endpoint (shell out to whisper binary) -- Add microphone button in ChatView input bar -- Detect whisper binary availability +- [ ] Add `POST /api/whisper/transcribe` endpoint (shell out to whisper binary) +- [ ] Add microphone button in ChatView input bar +- [ ] Detect whisper binary availability +- [ ] **COMMIT:** `feat(voice): add Whisper STT voice input` **Effort:** ~4 hours @@ -410,9 +552,10 @@ LLM Lab has whisper.cpp integration for speech-to-text. LLM Lab supports chain/race/vote multi-model orchestrations. LocalMemGPT has compare (parallel) but not chain/vote. -- Add `orchestrations` table -- Add orchestration CRUD + execute endpoints -- Add orchestration editor UI +- [ ] Add `orchestrations` table +- [ ] Add orchestration CRUD + execute endpoints +- [ ] Add orchestration editor UI +- [ ] **COMMIT:** `feat(orchestrations): add chain/race/vote multi-model pipelines` **Effort:** ~1 day @@ -420,14 +563,257 @@ LLM Lab supports chain/race/vote multi-model orchestrations. LocalMemGPT has com LLM Lab has cron-based scheduled prompts. Very niche feature. -- Add `scheduled_tasks` table -- Add task scheduler (setInterval-based in backend) -- Add task editor UI +- [ ] Add `scheduled_tasks` table +- [ ] Add task scheduler (setInterval-based in backend) +- [ ] Add task editor UI +- [ ] **COMMIT:** `feat(tasks): add cron-based scheduled prompts` **Effort:** ~4 hours --- +### Phase 4 — DRY Refactoring & Common Platform Extractions + +> **Independent of Phases 1–3** — can be executed before, after, or in parallel. +> +> **Effort:** ~3–5 days total +> **Risk:** Low-Medium — new shared packages, consumers migrate incrementally +> **Repo:** `learning_ai_common_plat` (new packages) + both local AI repos + ecosystem-wide + +Code audit identified **7 extraction candidates** — duplicated patterns across the two local AI repos and the broader ecosystem. + +#### 4.1 `@bytelyst/ollama-client` — Ollama API Client (NEW PACKAGE) + +**Problem:** Both repos implement nearly identical Ollama API interaction code with duplicated NDJSON stream parsing. + +| Repo | File | Functions | ~Lines | +| ----------- | ---------------------------------------------- | ----------------------------------------------------------------------------- | ------ | +| LocalMemGPT | `backend/src/lib/ollama.ts` | `listOllamaModels()`, `checkOllamaHealth()`, `streamChat()`, `getEmbedding()` | 117 | +| LLM Lab | `dashboard/src/app/api/ollama/route.ts` | `fetchOllama()`, model list/pull/delete/show/load/unload | 128 | +| LLM Lab | `dashboard/src/app/api/ollama/stream/route.ts` | raw streaming proxy | 37 | +| LLM Lab | `dashboard/src/app/api/ollama/chat/route.ts` | chat proxy with llm-router | 109 | +| LLM Lab | `dashboard/src/app/lib/ollama-config.ts` | `OLLAMA_URL` env resolution + WSL2 gateway detection | 40 | + +**Shared NDJSON stream parsing pattern** (copy-pasted 5+ times across both repos): + +```ts +const reader = res.body.getReader(); +const decoder = new TextDecoder(); +let buffer = ''; +while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n'); + buffer = lines.pop() ?? ''; + for (const line of lines) { + if (!line.trim()) continue; + try { + yield JSON.parse(line); + } catch { + /* skip */ + } + } +} +``` + +**Proposed package:** `packages/ollama-client/` + +``` +@bytelyst/ollama-client +├── src/ +│ ├── config.ts # resolveOllamaUrl(env) — OLLAMA_URL/OLLAMA_HOST + WSL2 detection +│ ├── client.ts # OllamaClient class: tags, ps, show, pull, load, unload, delete, version +│ ├── stream.ts # streamChat(), streamGenerate() — AsyncGenerator +│ ├── embed.ts # getEmbedding() +│ ├── health.ts # checkHealth() with timeout +│ ├── ndjson.ts # parseNdjsonStream() — reusable NDJSON async generator +│ └── types.ts # OllamaModel, OllamaChatMessage, OllamaStreamChunk, etc. +└── package.json +``` + +**Consumers:** + +- [ ] `learning_ai_local_memory_gpt/backend` — replace `lib/ollama.ts` (117 lines deleted) +- [ ] `learning_ai_local_llms/dashboard` — replace `lib/ollama-config.ts` + 4 API route files simplified +- [ ] `@bytelyst/llm-router` — could consume `@bytelyst/ollama-client` for its Ollama provider + +**Effort:** ~1 day +**Priority:** P0 — eliminates the largest duplication + +- [ ] **COMMIT:** `feat(ollama-client): shared Ollama API client package` + +--- + +#### 4.2 Extend `@bytelyst/fastify-sse` — Per-Request SSE Utilities [R4] + +**Problem:** `@bytelyst/fastify-sse` provides `SSEHub` (broadcast to multiple clients) but NOT per-request SSE helpers. LocalMemGPT reinvented these as `sse-helpers.ts` (26 lines). + +| Existing | What it does | +| ---------------------------- | -------------------------------------------------------------------------------------- | +| `@bytelyst/fastify-sse` | `SSEHub` — multi-client broadcast pattern (hub/spoke) | +| LocalMemGPT `sse-helpers.ts` | `startSSE()`, `sendSSEData()`, `sendSSEEvent()`, `endSSE()` — single-request streaming | + +**Proposed:** Add per-request helpers to `@bytelyst/fastify-sse`: + +```ts +// New exports in @bytelyst/fastify-sse +export { startSSE, sendSSEData, sendSSEEvent, endSSE } from './per-request.js'; +``` + +**Consumers:** + +- [ ] `learning_ai_local_memory_gpt/backend` — replace `lib/sse-helpers.ts` with import from package +- [ ] `learning_ai_trails/backend` — already uses `@bytelyst/fastify-sse` hub, could use per-request too + +**Effort:** ~2 hours +**Priority:** P1 + +- [ ] **COMMIT:** `feat(fastify-sse): add per-request SSE helpers` + +--- + +#### 4.3 `@bytelyst/use-theme` — React Theme Toggle Hook (NEW PACKAGE) + +**Problem:** Near-identical `useTheme()` hook duplicated across **6 web apps** — only the storage key and class-application strategy differ. + +| Repo | File | Storage Key | Lines | +| ----------- | ------------------------------------ | ----------- | ----- | +| LocalMemGPT | `web/src/lib/use-theme.ts` | `lmg-theme` | 49 | +| LLM Lab | `dashboard/src/app/lib/use-theme.ts` | `llm-theme` | 48 | +| ChronoMind | `web/src/lib/use-theme.ts` | `cm-theme` | ~50 | +| FlowMonk | `web/src/lib/use-theme.ts` | `fm-theme` | ~50 | +| NomGap | `web/src/lib/use-theme.ts` | `ng-theme` | ~50 | +| ActionTrail | `web/src/lib/use-theme.ts` [R5] | `at-theme` | ~50 | + +**Proposed package:** `packages/use-theme/` + +```ts +export function useTheme(options?: { storageKey?: string; attribute?: 'class' | 'data-theme' }) { + // Configurable storage key (default: `${PRODUCT_ID}-theme`) + // Configurable DOM application (classList vs data-attribute) + // Returns { theme, setTheme, toggleTheme } +} +``` + +**Effort:** ~2 hours +**Priority:** P2 — saves ~300 total lines across 6 repos + +- [ ] **COMMIT:** `feat(use-theme): shared React theme toggle hook` + +--- + +#### 4.4 `@bytelyst/use-keyboard-shortcuts` — Keyboard Shortcuts Hook (NEW PACKAGE) + +**Problem:** Near-identical `useKeyboardShortcuts()` hook duplicated across **5+ web apps**, each with slight variations in handler names. + +| Repo | File | +| ----------- | ------------------------------------------------------------ | +| LocalMemGPT | `web/src/lib/use-keyboard-shortcuts.ts` (53 lines) | +| LLM Lab | `dashboard/src/app/lib/use-keyboard-shortcuts.ts` (57 lines) | +| ChronoMind | `web/src/lib/use-keyboard-shortcuts.ts` | +| ActionTrail | `web/src/lib/use-keyboard-shortcuts.ts` | +| NoteLett | `web/src/lib/use-keyboard-shortcuts.ts` | + +**Proposed package:** `packages/use-keyboard-shortcuts/` + +```ts +export function useKeyboardShortcuts(shortcuts: ShortcutDef[]) { + // Each ShortcutDef: { key, meta?, shift?, handler, allowInInput? } + // Data-driven instead of hardcoded if/else chains +} +``` + +**Effort:** ~3 hours +**Priority:** P2 — saves ~250 lines, improves consistency + +- [ ] **COMMIT:** `feat(use-keyboard-shortcuts): shared React keyboard shortcuts hook` + +--- + +#### 4.5 Ollama Model Types — Deduplicate into `@bytelyst/ollama-client` types + +**Problem:** `OllamaModel` interface is defined independently in both repos with slightly different shapes. + +| Repo | Definition | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| LocalMemGPT `ollama.ts` | `{ name, size, digest, modified_at, details?: { parameter_size, quantization_level, family } }` | +| LLM Lab `types.ts` | `{ name, model, modified_at, size, digest, details: { families, family, format, parameter_size, quantization_level } }` | + +**Resolution:** Canonical `OllamaModel` type in `@bytelyst/ollama-client/types.ts` — matches Ollama's actual API response. Both repos import from there. + +**Effort:** included in 4.1 +**Priority:** P0 (part of 4.1) + +--- + +#### 4.6 LLM Lab `format.ts` Utilities — Promote Reusable Functions + +**Problem:** `format.ts` (96 lines) contains both LLM Lab-specific functions AND general-purpose utilities: + +| Function | Scope | Extraction Target | +| ------------------------- | ------------------------------------------------------ | --------------------------------------------------- | +| `formatBytes()` | **Reusable** — any repo needs byte formatting | `@bytelyst/ollama-client` or new `@bytelyst/format` | +| `estimateRam()` | LLM-infrastructure-specific | Keep in LLM Lab | +| `checkMemoryFit()` | LLM-infrastructure-specific | Keep in LLM Lab | +| `getModelBadges()` | LLM-infrastructure-specific | Keep in LLM Lab | +| `estimateTokens()` | **Reusable** — any LLM app needs token estimation | `@bytelyst/ollama-client` | +| `getModelContextWindow()` | **Reusable** — any LLM app needs context window lookup | `@bytelyst/ollama-client` | +| `formatUptime()` | **Reusable** — monitoring/ops dashboards | `@bytelyst/ollama-client` | + +**Effort:** ~1 hour (as part of 4.1) +**Priority:** P1 + +--- + +#### 4.7 Client-Side SSE/NDJSON Stream Parser (Web) + +**Problem:** The client-side SSE parsing pattern is copy-pasted 3 times in LocalMemGPT's `web/src/lib/api.ts` (`streamChat`, `streamCompare`, plus a `parseSSEBody` in tests). LLM Lab has 3 more copies in `ConversationView.tsx`, `compare/page.tsx`, and `mission-control/page.tsx`. **[R6] Note:** After Phase 1, `ConversationView.tsx` and `compare/page.tsx` are deleted — LLM Lab reduces to 1 copy (Mission Control). Total post-Phase-1 copies: 4 (3 LocalMemGPT + 1 LLM Lab). + +**Proposed:** Export from `@bytelyst/ollama-client`: + +```ts +// Client-side SSE stream consumer +export function consumeSSEStream( + response: Response, + onData: (data: Record) => void, + onDone: () => void +): Promise; + +// Client-side NDJSON stream consumer +export function consumeNdjsonStream( + response: Response, + onChunk: (chunk: T) => void +): Promise; +``` + +**Effort:** ~2 hours (as part of 4.1) +**Priority:** P1 — eliminates 6 copy-pasted stream parsers + +--- + +#### Phase 4 Summary + +| # | Package | Action | Lines Saved | Repos Affected | Priority | +| --- | ---------------------------------- | ---------- | ----------- | ----------------------- | -------- | +| 4.1 | `@bytelyst/ollama-client` | **NEW** | ~350 | 2 local AI + llm-router | P0 | +| 4.2 | `@bytelyst/fastify-sse` | **EXTEND** | ~26 | LocalMemGPT + future | P1 | +| 4.3 | `@bytelyst/use-theme` | **NEW** | ~300 | 6 web apps | P2 | +| 4.4 | `@bytelyst/use-keyboard-shortcuts` | **NEW** | ~250 | 5 web apps | P2 | +| 4.5 | (types in 4.1) | — | included | 2 local AI | P0 | +| 4.6 | (utils in 4.1) | — | ~40 | 2 local AI | P1 | +| 4.7 | (client parsers in 4.1) | — | ~180 | 2 local AI | P1 | + +**Total estimated savings:** ~1,150 lines of duplicated code across the ecosystem. + +**Recommended execution order:** + +1. **4.1** `@bytelyst/ollama-client` (biggest win, unblocks both repos) +2. **4.2** Extend `@bytelyst/fastify-sse` (small, quick) +3. **4.3 + 4.4** React hooks (lower priority, wider ecosystem impact — could batch with next DRY sweep) + +--- + ## File Inventory Summary ### Phase 1 Deletions (Local LLM Lab) @@ -511,240 +897,6 @@ LLM Lab has cron-based scheduled prompts. Very niche feature. --- -## Phase 4 — DRY Refactoring & Common Platform Extractions - -> **Note:** [R7] This phase was added as an addendum during the DRY audit (2026-03-29). It is independent of Phases 1-3 and can be executed before, after, or in parallel. -> -> **Effort:** ~3–5 days total -> **Risk:** Low-Medium — new shared packages, consumers migrate incrementally -> **Repo:** `learning_ai_common_plat` (new packages) + both local AI repos + ecosystem-wide - -Code audit identified **7 extraction candidates** — duplicated patterns across the two local AI repos and the broader ecosystem. - -### 4.1 `@bytelyst/ollama-client` — Ollama API Client (NEW PACKAGE) - -**Problem:** Both repos implement nearly identical Ollama API interaction code with duplicated NDJSON stream parsing. - -| Repo | File | Functions | ~Lines | -| ----------- | ---------------------------------------------- | ----------------------------------------------------------------------------- | ------ | -| LocalMemGPT | `backend/src/lib/ollama.ts` | `listOllamaModels()`, `checkOllamaHealth()`, `streamChat()`, `getEmbedding()` | 117 | -| LLM Lab | `dashboard/src/app/api/ollama/route.ts` | `fetchOllama()`, model list/pull/delete/show/load/unload | 128 | -| LLM Lab | `dashboard/src/app/api/ollama/stream/route.ts` | raw streaming proxy | 37 | -| LLM Lab | `dashboard/src/app/api/ollama/chat/route.ts` | chat proxy with llm-router | 109 | -| LLM Lab | `dashboard/src/app/lib/ollama-config.ts` | `OLLAMA_URL` env resolution + WSL2 gateway detection | 40 | - -**Shared NDJSON stream parsing pattern** (copy-pasted 5+ times across both repos): - -```ts -const reader = res.body.getReader(); -const decoder = new TextDecoder(); -let buffer = ''; -while (true) { - const { done, value } = await reader.read(); - if (done) break; - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split('\n'); - buffer = lines.pop() ?? ''; - for (const line of lines) { - if (!line.trim()) continue; - try { - yield JSON.parse(line); - } catch { - /* skip */ - } - } -} -``` - -**Proposed package:** `packages/ollama-client/` - -``` -@bytelyst/ollama-client -├── src/ -│ ├── config.ts # resolveOllamaUrl(env) — OLLAMA_URL/OLLAMA_HOST + WSL2 detection -│ ├── client.ts # OllamaClient class: tags, ps, show, pull, load, unload, delete, version -│ ├── stream.ts # streamChat(), streamGenerate() — AsyncGenerator -│ ├── embed.ts # getEmbedding() -│ ├── health.ts # checkHealth() with timeout -│ ├── ndjson.ts # parseNdjsonStream() — reusable NDJSON async generator -│ └── types.ts # OllamaModel, OllamaChatMessage, OllamaStreamChunk, etc. -└── package.json -``` - -**Consumers:** - -- `learning_ai_local_memory_gpt/backend` — replace `lib/ollama.ts` (117 lines deleted) -- `learning_ai_local_llms/dashboard` — replace `lib/ollama-config.ts` + 4 API route files simplified -- `@bytelyst/llm-router` — could consume `@bytelyst/ollama-client` for its Ollama provider - -**Effort:** ~1 day -**Priority:** P0 — eliminates the largest duplication - ---- - -### 4.2 Extend `@bytelyst/fastify-sse` — Per-Request SSE Utilities [R4] - -**Problem:** `@bytelyst/fastify-sse` provides `SSEHub` (broadcast to multiple clients) but NOT per-request SSE helpers. LocalMemGPT reinvented these as `sse-helpers.ts` (26 lines). - -| Existing | What it does | -| ---------------------------- | -------------------------------------------------------------------------------------- | -| `@bytelyst/fastify-sse` | `SSEHub` — multi-client broadcast pattern (hub/spoke) | -| LocalMemGPT `sse-helpers.ts` | `startSSE()`, `sendSSEData()`, `sendSSEEvent()`, `endSSE()` — single-request streaming | - -**Proposed:** Add per-request helpers to `@bytelyst/fastify-sse`: - -```ts -// New exports in @bytelyst/fastify-sse -export { startSSE, sendSSEData, sendSSEEvent, endSSE } from './per-request.js'; -``` - -**Consumers:** - -- `learning_ai_local_memory_gpt/backend` — replace `lib/sse-helpers.ts` with import from package -- `learning_ai_trails/backend` — already uses `@bytelyst/fastify-sse` hub, could use per-request too - -**Effort:** ~2 hours -**Priority:** P1 - ---- - -### 4.3 `@bytelyst/use-theme` — React Theme Toggle Hook (NEW PACKAGE) - -**Problem:** Near-identical `useTheme()` hook duplicated across **6 web apps** — only the storage key and class-application strategy differ. - -| Repo | File | Storage Key | Lines | -| ----------- | ------------------------------------ | ----------- | ----- | -| LocalMemGPT | `web/src/lib/use-theme.ts` | `lmg-theme` | 49 | -| LLM Lab | `dashboard/src/app/lib/use-theme.ts` | `llm-theme` | 48 | -| ChronoMind | `web/src/lib/use-theme.ts` | `cm-theme` | ~50 | -| FlowMonk | `web/src/lib/use-theme.ts` | `fm-theme` | ~50 | -| NomGap | `web/src/lib/use-theme.ts` | `ng-theme` | ~50 | -| ActionTrail | `web/src/lib/use-theme.ts` [R5] | `at-theme` | ~50 | - -**Proposed package:** `packages/use-theme/` - -```ts -export function useTheme(options?: { storageKey?: string; attribute?: 'class' | 'data-theme' }) { - // Configurable storage key (default: `${PRODUCT_ID}-theme`) - // Configurable DOM application (classList vs data-attribute) - // Returns { theme, setTheme, toggleTheme } -} -``` - -**Effort:** ~2 hours -**Priority:** P2 — saves ~300 total lines across 6 repos - ---- - -### 4.4 `@bytelyst/use-keyboard-shortcuts` — Keyboard Shortcuts Hook (NEW PACKAGE) - -**Problem:** Near-identical `useKeyboardShortcuts()` hook duplicated across **5+ web apps**, each with slight variations in handler names. - -| Repo | File | -| ----------- | ------------------------------------------------------------ | -| LocalMemGPT | `web/src/lib/use-keyboard-shortcuts.ts` (53 lines) | -| LLM Lab | `dashboard/src/app/lib/use-keyboard-shortcuts.ts` (57 lines) | -| ChronoMind | `web/src/lib/use-keyboard-shortcuts.ts` | -| ActionTrail | `web/src/lib/use-keyboard-shortcuts.ts` | -| NoteLett | `web/src/lib/use-keyboard-shortcuts.ts` | - -**Proposed package:** `packages/use-keyboard-shortcuts/` - -```ts -export function useKeyboardShortcuts(shortcuts: ShortcutDef[]) { - // Each ShortcutDef: { key, meta?, shift?, handler, allowInInput? } - // Data-driven instead of hardcoded if/else chains -} -``` - -**Effort:** ~3 hours -**Priority:** P2 — saves ~250 lines, improves consistency - ---- - -### 4.5 Ollama Model Types — Deduplicate into `@bytelyst/ollama-client` types - -**Problem:** `OllamaModel` interface is defined independently in both repos with slightly different shapes. - -| Repo | Definition | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| LocalMemGPT `ollama.ts` | `{ name, size, digest, modified_at, details?: { parameter_size, quantization_level, family } }` | -| LLM Lab `types.ts` | `{ name, model, modified_at, size, digest, details: { families, family, format, parameter_size, quantization_level } }` | - -**Resolution:** Canonical `OllamaModel` type in `@bytelyst/ollama-client/types.ts` — matches Ollama's actual API response. Both repos import from there. - -**Effort:** included in 4.1 -**Priority:** P0 (part of 4.1) - ---- - -### 4.6 LLM Lab `format.ts` Utilities — Promote Reusable Functions - -**Problem:** `format.ts` (96 lines) contains both LLM Lab-specific functions AND general-purpose utilities: - -| Function | Scope | Extraction Target | -| ------------------------- | ------------------------------------------------------ | --------------------------------------------------- | -| `formatBytes()` | **Reusable** — any repo needs byte formatting | `@bytelyst/ollama-client` or new `@bytelyst/format` | -| `estimateRam()` | LLM-infrastructure-specific | Keep in LLM Lab | -| `checkMemoryFit()` | LLM-infrastructure-specific | Keep in LLM Lab | -| `getModelBadges()` | LLM-infrastructure-specific | Keep in LLM Lab | -| `estimateTokens()` | **Reusable** — any LLM app needs token estimation | `@bytelyst/ollama-client` | -| `getModelContextWindow()` | **Reusable** — any LLM app needs context window lookup | `@bytelyst/ollama-client` | -| `formatUptime()` | **Reusable** — monitoring/ops dashboards | `@bytelyst/ollama-client` | - -**Effort:** ~1 hour (as part of 4.1) -**Priority:** P1 - ---- - -### 4.7 Client-Side SSE/NDJSON Stream Parser (Web) - -**Problem:** The client-side SSE parsing pattern is copy-pasted 3 times in LocalMemGPT's `web/src/lib/api.ts` (`streamChat`, `streamCompare`, plus a `parseSSEBody` in tests). LLM Lab has 3 more copies in `ConversationView.tsx`, `compare/page.tsx`, and `mission-control/page.tsx`. **[R6] Note:** After Phase 1, `ConversationView.tsx` and `compare/page.tsx` are deleted — LLM Lab reduces to 1 copy (Mission Control). Total post-Phase-1 copies: 4 (3 LocalMemGPT + 1 LLM Lab). - -**Proposed:** Export from `@bytelyst/ollama-client`: - -```ts -// Client-side SSE stream consumer -export function consumeSSEStream( - response: Response, - onData: (data: Record) => void, - onDone: () => void -): Promise; - -// Client-side NDJSON stream consumer -export function consumeNdjsonStream( - response: Response, - onChunk: (chunk: T) => void -): Promise; -``` - -**Effort:** ~2 hours (as part of 4.1) -**Priority:** P1 — eliminates 6 copy-pasted stream parsers - ---- - -### Phase 4 Summary - -| # | Package | Action | Lines Saved | Repos Affected | Priority | -| --- | ---------------------------------- | ---------- | ----------- | ----------------------- | -------- | -| 4.1 | `@bytelyst/ollama-client` | **NEW** | ~350 | 2 local AI + llm-router | P0 | -| 4.2 | `@bytelyst/fastify-sse` | **EXTEND** | ~26 | LocalMemGPT + future | P1 | -| 4.3 | `@bytelyst/use-theme` | **NEW** | ~300 | 6 web apps | P2 | -| 4.4 | `@bytelyst/use-keyboard-shortcuts` | **NEW** | ~250 | 5 web apps | P2 | -| 4.5 | (types in 4.1) | — | included | 2 local AI | P0 | -| 4.6 | (utils in 4.1) | — | ~40 | 2 local AI | P1 | -| 4.7 | (client parsers in 4.1) | — | ~180 | 2 local AI | P1 | - -**Total estimated savings:** ~1,150 lines of duplicated code across the ecosystem. - -**Recommended execution order:** - -1. **4.1** `@bytelyst/ollama-client` (biggest win, unblocks both repos) -2. **4.2** Extend `@bytelyst/fastify-sse` (small, quick) -3. **4.3 + 4.4** React hooks (lower priority, wider ecosystem impact — could batch with next DRY sweep) - ---- - ## Open Questions 1. ~~**TTS/Whisper in Mission Control:**~~ **[F13] RESOLVED.** Mission Control uses `api/whisper` (status), `api/whisper/transcribe` (test), `api/tts` (engine status), `api/ollama/chat` (model testing), `api/ollama/stream` (benchmarks/compare), `api/ollama/logs` (log viewer). All 6 routes are kept.