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>
42 lines
1.5 KiB
TypeScript
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);
|
|
}
|