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.
This commit is contained in:
@@ -172,6 +172,82 @@ if command -v sqlite3 >/dev/null 2>&1 && [ -f "$DB_PATH" ]; then
|
||||
"
|
||||
fi
|
||||
|
||||
# v1.1.0:4 added AIConfigProfile table (multi-config support) +
|
||||
# UserPreferences.activeAIConfigId pointer + AIGeneration progress/
|
||||
# duration columns + ProgramExercise suggested-weight columns.
|
||||
if ! sqlite3 "$DB_PATH" \
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='AIConfigProfile';" \
|
||||
2>/dev/null | grep -q AIConfigProfile; then
|
||||
log "creating AIConfigProfile table"
|
||||
sqlite3 "$DB_PATH" "
|
||||
CREATE TABLE AIConfigProfile (
|
||||
id TEXT PRIMARY KEY,
|
||||
userId TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
baseUrl TEXT,
|
||||
apiKey TEXT,
|
||||
createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (userId) REFERENCES User(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX AIConfigProfile_userId_idx ON AIConfigProfile(userId);
|
||||
"
|
||||
fi
|
||||
|
||||
if ! sqlite3 "$DB_PATH" "PRAGMA table_info('UserPreferences');" 2>/dev/null | grep -q "|activeAIConfigId|"; then
|
||||
log "adding UserPreferences.activeAIConfigId"
|
||||
sqlite3 "$DB_PATH" "ALTER TABLE UserPreferences ADD COLUMN activeAIConfigId TEXT;"
|
||||
fi
|
||||
|
||||
if ! sqlite3 "$DB_PATH" "PRAGMA table_info('AIGeneration');" 2>/dev/null | grep -q "|progressText|"; then
|
||||
log "adding AIGeneration.progressText"
|
||||
sqlite3 "$DB_PATH" "ALTER TABLE AIGeneration ADD COLUMN progressText TEXT;"
|
||||
fi
|
||||
|
||||
if ! sqlite3 "$DB_PATH" "PRAGMA table_info('AIGeneration');" 2>/dev/null | grep -q "|durationMs|"; then
|
||||
log "adding AIGeneration.durationMs"
|
||||
sqlite3 "$DB_PATH" "ALTER TABLE AIGeneration ADD COLUMN durationMs INTEGER;"
|
||||
fi
|
||||
|
||||
if ! sqlite3 "$DB_PATH" "PRAGMA table_info('ProgramExercise');" 2>/dev/null | grep -q "|suggestedWeight|"; then
|
||||
log "adding ProgramExercise.suggestedWeight + suggestedWeightUnit"
|
||||
sqlite3 "$DB_PATH" "ALTER TABLE ProgramExercise ADD COLUMN suggestedWeight REAL;"
|
||||
sqlite3 "$DB_PATH" "ALTER TABLE ProgramExercise ADD COLUMN suggestedWeightUnit TEXT;"
|
||||
fi
|
||||
|
||||
# v1.1.0:4 one-shot migration: lift each user's legacy single-config
|
||||
# (UserPreferences.aiProvider/aiModel/...) into a new AIConfigProfile
|
||||
# row marked active. Idempotent — only runs for users who have a
|
||||
# configured legacy config but no profiles yet.
|
||||
log "migrating any legacy single-config to AIConfigProfile (idempotent)"
|
||||
sqlite3 "$DB_PATH" "
|
||||
INSERT INTO AIConfigProfile (id, userId, name, provider, model, baseUrl, apiKey)
|
||||
SELECT
|
||||
'c' || lower(hex(randomblob(12))),
|
||||
up.userId,
|
||||
'Default (' || up.aiProvider || ')',
|
||||
up.aiProvider,
|
||||
up.aiModel,
|
||||
up.aiBaseUrl,
|
||||
up.aiApiKey
|
||||
FROM UserPreferences up
|
||||
WHERE up.aiProvider IS NOT NULL
|
||||
AND up.aiModel IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM AIConfigProfile p WHERE p.userId = up.userId);
|
||||
" 2>/dev/null || log "WARN: legacy-config migration skipped"
|
||||
|
||||
# Set activeAIConfigId for users who now have exactly one profile.
|
||||
sqlite3 "$DB_PATH" "
|
||||
UPDATE UserPreferences
|
||||
SET activeAIConfigId = (
|
||||
SELECT id FROM AIConfigProfile WHERE userId = UserPreferences.userId LIMIT 1
|
||||
)
|
||||
WHERE activeAIConfigId IS NULL
|
||||
AND (SELECT COUNT(*) FROM AIConfigProfile WHERE userId = UserPreferences.userId) = 1;
|
||||
" 2>/dev/null || true
|
||||
|
||||
if ! sqlite3 "$DB_PATH" \
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='InstanceSettings';" \
|
||||
2>/dev/null | grep -q InstanceSettings; then
|
||||
|
||||
Reference in New Issue
Block a user