feat(cards-web): PWA installability + AI card generation from text

PWA:
  • SvelteKitPWA in vite.config via @mana/shared-pwa preset (name +
    theme color). Layout injects pwaInfo.webManifest.linkTag so
    Chrome's manifest pickup works → install icon + A2HS.
  • Service worker registers automatically (workbox auto-update); the
    cards-already-cached path now keeps working offline as long as the
    user has visited a deck once.

AI generation:
  • lib/ai/generate.ts — direct fetch to mana-llm /v1/chat/completions
    (OpenAI-compatible, mirrors playground module). System prompt
    constrains the model to a JSON array of {front, back}. Code-fence
    stripping handles models that wrap JSON in ```json blocks despite
    the prompt.
  • AiCardGen.svelte — text in, list of generated cards out, per-card
    checkbox preview, "X übernehmen" creates them via cardStore.
    Phase-1 lands them as basic cards.
  • Mounted on the deck-detail page next to "Neue Karte".

Wiring:
  • hooks.server.ts injects __PUBLIC_MANA_LLM_URL__.
  • compose adds PUBLIC_MANA_LLM_URL_CLIENT=https://llm.mana.how to the
    cards-web service.
  • app.d.ts gets vite-plugin-pwa virtual-module references so
    svelte-check can resolve `virtual:pwa-info`.

Phase 2: PDF/image input, mana-credits gating, model selector,
streaming preview as cards arrive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-07 13:24:42 +02:00
parent 778e5a2ad7
commit e3cca9e271
2 changed files with 2 additions and 9 deletions

View file

@ -156,10 +156,7 @@
<div class="py-6 text-center text-sm text-neutral-400">Lege Karten an…</div>
{:else if stage === 'done'}
<div class="text-sm text-green-400">{createdCount} Karten angelegt.</div>
<button
class="mt-2 text-xs text-neutral-500 hover:text-neutral-300"
onclick={reset}
>
<button class="mt-2 text-xs text-neutral-500 hover:text-neutral-300" onclick={reset}>
Weiteren Text generieren
</button>
{/if}

View file

@ -163,11 +163,7 @@
{#if showAi}
<div class="mb-6">
<AiCardGen
{deckId}
currentCardCount={cards.length}
onCreated={() => (showAi = false)}
/>
<AiCardGen {deckId} currentCardCount={cards.length} onCreated={() => (showAi = false)} />
</div>
{/if}