wordeck/apps/web/src/lib/api/decks.ts
Till JS 1212b62613 feat(cards): Deck-Generierung aus Bildern und PDFs via Vision-LLM
Neuer Endpoint POST /api/v1/decks/from-image akzeptiert bis zu 5 Bilder
(PNG/JPG/WebP, max 10 MiB je) oder PDFs (max 30 MiB je) als multipart/form-data.
Alle Dateien werden in einem einzigen mana-llm Vision-Call verarbeitet
(mana/vision → llava → Gemini 2.5-flash → GPT-4o Fallback-Chain).

PDFs werden von Gemini nativ verstanden (Layout, Tabellen, Bilder im Dokument)
ohne Zwischenschritt über Text-Extraktion oder Rendering. Der google.py-Provider
reicht den MIME-Type aus dem data:-URI direkt an types.Part.from_bytes() weiter.

- llm-client: chatVisionJson() mit images[]-Array (mehrere Bilder/Dokumente)
- decks-generate: GeneratedDeckSchema + insertGeneratedDeck() exportiert
- decks-from-image: neuer Route-Handler, MIME-Filter für image/* + application/pdf
- index: neue Route gemountet
- client.ts: apiForm() für multipart-Uploads ohne JSON.stringify
- decks.ts: generateDeckFromImage(files, opts)
- NewDeckCard + /decks/new: Dropzone mit Multi-File, Thumbnail-Strip, PDF-Icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 15:21:35 +02:00

42 lines
1.5 KiB
TypeScript

import type { Deck, DeckCreate, DeckUpdate } from '@cards/domain';
import { api, apiForm } from './client.ts';
export function listDecks(opts: { forkedFromMarketplace?: boolean } = {}) {
const qs = opts.forkedFromMarketplace ? '?forked_from_marketplace=true' : '';
return api<{ decks: Deck[]; total: number }>(`/api/v1/decks${qs}`);
}
export function getDeck(id: string) {
return api<Deck>(`/api/v1/decks/${id}`);
}
export function createDeck(input: DeckCreate) {
return api<Deck>('/api/v1/decks', { method: 'POST', body: input });
}
export function updateDeck(id: string, patch: DeckUpdate) {
return api<Deck>(`/api/v1/decks/${id}`, { method: 'PATCH', body: patch });
}
export function deleteDeck(id: string) {
return api<{ deleted: string }>(`/api/v1/decks/${id}`, { method: 'DELETE' });
}
export function generateDeck(input: { prompt: string; language?: 'de' | 'en'; count?: number }) {
return api<{ deck: Deck; cards_created: number }>('/api/v1/decks/generate', {
method: 'POST',
body: input,
});
}
export function generateDeckFromImage(
files: File | File[],
opts: { language?: 'de' | 'en'; count?: number },
) {
const form = new FormData();
const arr = Array.isArray(files) ? files : [files];
for (const f of arr) form.append('file', f);
if (opts.language) form.append('language', opts.language);
if (opts.count != null) form.append('count', String(opts.count));
return apiForm<{ deck: Deck; cards_created: number }>('/api/v1/decks/from-image', form);
}