Commit Graph

3 Commits

Author SHA1 Message Date
Keysat 5e291203a5 v1.1.0:4 — multi-config AI, background generation, ollama auto-detect, system prompt overhaul
User-feedback-driven release after testing v1.1.0:3. Nine themes:

1. Multi-config persistence
   - New AIConfigProfile table (per-user). Save N configs, toggle one
     active. Switching providers no longer wipes the previous setup.
   - UserPreferences gains activeAIConfigId; legacy single-config
     columns are mirrored from the active profile so existing reads
     keep working without conditional logic.
   - Idempotent boot migration lifts any existing single-config row
     into a default profile.

2. Ollama auto-detect
   - The "Add config" form probes /api/tags on the StartOS internal
     addresses (ollama.startos / ollama.embassy on :11434). If
     reachable: URL pre-fills, model field becomes a dropdown of
     installed models. Fixes the copy-paste UX.

3. Curated model dropdowns for major providers
   - Claude: Opus 4.7, Sonnet 4.6 (1M ctx), Haiku 4.5
   - OpenAI: GPT-5.5, 5.4, 5.4-mini, 5.4-nano
   - Gemini: 3.1-pro-preview, 2.5-pro, 2.5-flash, etc.
   - "Other (type your own)" stays for niche models.
   - Fixes "I tried gemini-3.0-pro and got 404."

4. Background generation
   - lib/ai/generationRunner.ts: detached runner with in-memory
     pub/sub bus. POST /api/ai/generate kicks it off and returns
     immediately. SSE stream attaches by id. The runner survives
     request cancellation; navigating away no longer kills it.
   - New AIGeneration columns: progressText (in-flight stream),
     durationMs (final wall-clock).
   - Generate UI shows a banner explaining background-safety.
   - History detail page polls progress + renders partial JSON
     live for cross-process resume (page refresh, new tab).

5. System prompt overhaul
   - lib/ai/systemPromptBase.ts: structural contract prepended to
     every template. Forces JSON-only output, library-exerciseId
     usage (kills "exerciseId doesn't belong to this user" errors),
     and per-resistance-exercise suggestedWeight (with-history vs
     without-history variants).
   - aiExerciseSchema + ProgramExercise gain suggestedWeight +
     suggestedWeightUnit. Starting a workout from a ProgramDay
     pre-populates SetLog.weight from the suggestion.

6. Test connection improvements
   - Latency in seconds (was ms — confusing for slow Ollama).
   - Stale "✓ Connected" clears on form change.
   - Per-config Test (no need to activate first).
   - Generous maxOutputTokens for thinking models.
   - Gemini surfaces finishReason on empty response (e.g. "blocked
     by safety filter") instead of generic "empty response."
   - Test endpoint accepts a draft body so you can verify before
     saving + before activating.

7. History detail view
   - Click row → full program tree + exact prompts sent. Apply from
     here without re-generating. Pending rows poll for progress.

8. Sidebar sub-navigation
   - AI: Generate / History / Templates
   - Settings: General / Password / Sessions / AI integration /
     Export / Instance (admin) / Danger zone, with anchor scroll.

9. API key UX
   - "Key saved" indicator on saved configs (was confusing to see
     an empty input after a successful save).

Schema migrations (additive, idempotent in entrypoint):
  - AIConfigProfile table created
  - UserPreferences.activeAIConfigId
  - AIGeneration.progressText + durationMs
  - ProgramExercise.suggestedWeight + suggestedWeightUnit

Tests: 16 new (systemPromptBase, modelMenu, generationRunner). 177
total pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:09:01 -05:00
Keysat 8f149d35ab v1.1.0:3 — AI upgrades: history context, test connection, cost estimator, streaming preview
Four incremental upgrades to the AI program generator. No schema change, no /data migration.

1. History as context (the killer feature)
   - lib/ai/historyContext.ts builds a 90-day per-exercise rollup:
     frequency, recent weights, estimated 1RM (Epley), avg RPE,
     days-since-last, plus a STAGNANT flag when the heaviest weight in
     the new half doesn't beat the old half.
   - Generate page surfaces an "Include my workout history as context"
     checkbox (default on at >=10 logged workouts). When checked, the
     ~1-3 KB summary is appended to the system prompt so the model can
     recommend things like "you've stalled bench at 245 — try paused reps."
   - We deliberately don't ship raw set logs (privacy + token cost).

2. Test connection
   - POST /api/ai/test sends a tiny "say hi in 3 words" prompt and
     reports latency + first sample, or the error inline.
   - "Test connection" button next to "Save AI config" in
     Settings -> AI integration. Verifies provider/model/key/baseUrl
     without going through full program generation.

3. Cost estimator
   - lib/ai/pricing.ts ships a price table for major models
     (Claude 3.5/3.7/4/4.5, GPT-4o/5/o1/o3/o4-mini, Gemini 1.5/2.0/2.5).
     Ollama always returns 0; openai-compatible returns null.
   - Generation history shows per-row cost + a 30-day rolling total
     at the top of the page.

4. Streaming preview render
   - lib/ai/lenientJson.ts: stack-aware partial-JSON parser that
     auto-closes open strings/brackets/braces in reverse-of-opening
     order, drops dangling key:value pairs and partial keywords.
     Returns a best-effort snapshot of the program-so-far on each chunk.
   - Generate UI now renders a live "Building program..." panel that
     updates as weeks/days/exercises arrive instead of just showing
     raw text and waiting for stream end.

Tests: 26 new (ai-historyContext.test.ts, ai-lenientJson.test.ts,
ai-pricing.test.ts). 161 total pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:17:35 -05:00
Keysat 974c3eb07d v1.1.0:2 — model-agnostic AI program generation (5 providers)
Five providers behind one streaming abstraction:
  - claude              (Anthropic)
  - openai              (api.openai.com)
  - openai-compatible   (any base URL — OpenRouter / LiteLLM /
                         vLLM / Together / your own gateway)
  - gemini              (Google)
  - ollama              (self-hosted; no key; LAN URL like
                         http://ollama.embassy:11434)

The "self-hosted Ollama on Start9" angle is the killer use case —
configure Settings → AI integration with the LAN URL of your Ollama
service and no API keys ever leave your network.

Architecture
  - lib/ai/types.ts              LLMProvider streaming interface
  - lib/ai/sse.ts                shared SSE + NDJSON line iterators
  - lib/ai/providers/*.ts        5 implementations + factory
  - lib/ai/programSchema.ts      Zod schema + JSON-schema-for-prompt +
                                  parseAIProgram with markdown-fence
                                  stripping and balanced-brace JSON
                                  extraction
  - lib/ai/apply.ts              materializes parsed AIProgram into
                                  Program tree (validates exerciseIds,
                                  rejects unresolved nulls, atomic
                                  transaction, sets aiGenerated=true)

Schema
  - UserPreferences gets aiProvider/aiModel/aiBaseUrl/aiApiKey
    (plaintext — same threat model as the rest of /data). Dead
    enableClaudeAI/claudeApiKey columns from v1.0.0:1-7 stay as
    no-op fields.
  - AIPromptTemplate (userId nullable; userId=NULL = built-in)
  - AIGeneration (raw response + parsed program + status +
    appliedProgramId + token counts)
  - All compat-ALTER'd in docker_entrypoint.sh on first boot.

API
  - POST   /api/ai/generate              SSE streaming: emits
                                           generation/text/usage/complete
                                           events; persists AIGeneration
                                           row up front so failures show
                                           in history too
  - POST   /api/ai/apply                 takes user-edited AIProgram,
                                           creates Program, marks
                                           generation as applied
  - GET    /api/ai/templates             built-ins + this user's own
  - POST   /api/ai/templates             create user-owned template
  - PATCH  /api/ai/templates/[id]        edit; built-ins admin-only
  - DELETE /api/ai/templates/[id]        delete; built-ins admin-only
  - GET    /api/ai/generations           list (paginated)
  - GET    /api/ai/generations/[id]      full row
  - DELETE /api/ai/generations/[id]      delete one (Program survives)
  - GET    /api/ai/config                returns aiKeyConfigured flag,
                                           never plaintext key
  - POST   /api/ai/config                update provider config
  - DELETE /api/admin/ai/generations     admin-only "clear all" with
                                           optional userId / olderThanDays

UI
  - Settings → AI integration            provider/model/URL/key form;
                                           plaintext key warning visible
  - /main/ai                             hub page with cards
  - /main/ai/generate                    template picker + textarea +
                                           live SSE stream + cancel +
                                           ProgramPreview with inline
                                           unknown-exercise resolver +
                                           apply button + redirect to
                                           the new Program
  - /main/ai/templates                   list + create + edit + delete;
                                           per-row "show prompt" expand;
                                           built-in delete warns about
                                           reconcile re-creation
  - /main/ai/history                     list + delete; status badges;
                                           link to applied Program
  - Nav: "AI" entry between Programs and Exercises (Sparkles icon)

Built-in templates
  - prisma/aiTemplates.seed.json: 5 starter templates (hypertrophy /
    strength / endurance / recovery / custom)
  - prisma/ensurePromptTemplates.cjs: per-boot reconcile,
    INSERT-or-UPDATE keyed on (userId IS NULL AND name=...);
    user-created templates never touched

Tests
  - tests/ai-programSchema.test.ts: extractJson + parseAIProgram
    edge cases (markdown fences, balanced braces, malformed JSON,
    Zod shape rejection, unresolved-exerciseId tolerance)
  - tests/ai-apply.test.ts: materializes valid AIProgram, rejects
    cross-user exerciseIds, rejects unresolved exercises, honors
    isActive flag
  - tests/routes-ai-templates.test.ts: built-in vs user permissions,
    cross-user template isolation, /api/ai/config plaintext-key safety,
    provider enum validation
  - 123 tests across 14 files, all passing.

No data migration. Existing /data is augmented with the new columns
+ tables only.
2026-05-10 15:35:35 -05:00