with thumbnail!

This commit is contained in:
Giancarmine Salucci
2025-11-30 21:56:21 +01:00
parent 23583f54c6
commit 167cd1f4bb
5 changed files with 152 additions and 14 deletions

View File

@@ -3,8 +3,8 @@ import { createLLM } from '$lib/server/llm';
import { json } from '@sveltejs/kit';
import fs, { writeFileSync } from 'fs';
import { zodResponseFormat } from 'openai/helpers/zod';
import { z } from 'zod';
import path from 'path';
import { z } from 'zod';
const RecipeSchema = z.object({
name: z.string(),
@@ -15,7 +15,8 @@ const RecipeSchema = z.object({
amount: z.string(),
unit: z.string()
})).nullable(),
steps: z.array(z.string()).nullable()
steps: z.array(z.string()).nullable(),
image: z.string().nullable().optional()
});
@@ -34,18 +35,43 @@ export async function POST({ request }) {
const context = await createBrowserContext(authPath);
const page = await context.newPage();
// Set a fixed viewport size (Instagram feed width)
await page.setViewportSize({ width: 1080, height: 1920 });
let bodyText = '';
let thumbnail: string | null = null;
try {
await page.goto(url, { waitUntil: 'domcontentloaded' });
// Extract HTML from the page
bodyText = (await page.evaluate(() => document.body.innerText)).replace(/^(?:.*\n){6}/, '').split('More posts from')[0].trim();
// Cleaning steps
// 1. Remove @tags and #hashtags
bodyText = bodyText.replace(/@\w+/g, '').replace(/#\w+/g, '');
writeFileSync(path.resolve('debug_page.txt'), bodyText); // Save for debugging, overwriting if exists
const videoBounds = await page.evaluate(() => {
const video = document.querySelector('video');
if (!video) return null;
const rect = video.getBoundingClientRect();
return {
x: Math.max(0, rect.left),
y: Math.max(0, rect.top),
width: Math.min(rect.width, window.innerWidth),
height: Math.min(rect.height, window.innerHeight)
};
});
if (videoBounds && videoBounds.width > 0 && videoBounds.height > 0) {
const screenshotBuffer = await page.screenshot({
type: 'jpeg',
quality: 85,
clip: videoBounds
});
thumbnail = `data:image/jpeg;base64,${screenshotBuffer.toString('base64')}`;
} else {
console.warn('Video element not found or has no size, taking full page screenshot');
const screenshotBuffer = await page.screenshot({ type: 'jpeg', quality: 85 });
thumbnail = `data:image/jpeg;base64,${screenshotBuffer.toString('base64')}`;
}
} catch (e) {
console.error('Scraping error:', e);
return json({ error: 'Failed to scrape URL' }, { status: 500 });
@@ -122,10 +148,15 @@ Extract ONLY what's explicitly in the text. Be accurate and literal.
} else {
recipe.description = `Link: ${url}`;
}
// Add thumbnail to recipe
if (thumbnail) {
recipe.image = thumbnail;
}
return json({ recipe });
return json({ recipe, bodyText });
} catch (e) {
console.error('LLM error:', e);
return json({ error: 'Failed to parse recipe' }, { status: 500 });
return json({ error: 'Failed to parse recipe', bodyText }, { status: 500 });
}
}

View File

@@ -1,5 +1,5 @@
import { json } from '@sveltejs/kit';
import { uploadRecipeWithIngredientsDTO } from '$lib/server/tandoor';
import { uploadRecipeWithIngredientsDTO, uploadRecipeImage } from '$lib/server/tandoor';
export async function POST({ request }) {
const { recipe } = await request.json();
@@ -15,10 +15,20 @@ export async function POST({ request }) {
return json({ error: result.error || 'Failed to upload recipe' }, { status: 500 });
}
// Upload image if available
let imageStatus = null;
if (result.recipeId && result.imageUrl) {
imageStatus = await uploadRecipeImage(result.recipeId, result.imageUrl);
if (!imageStatus.success) {
console.warn('Image upload failed, but recipe created:', imageStatus.error);
}
}
return json({
success: true,
message: 'Recipe successfully imported to Tandoor',
recipeId: result.recipeId
recipeId: result.recipeId,
imageUpload: imageStatus?.success ? 'successful' : 'failed'
});
} catch (error) {
console.error('Tandoor upload error:', error);

View File

@@ -4,6 +4,7 @@
let status = $state('idle');
let logs = $state<string[]>([]);
let recipe = $state<any>(null);
let bodyText = $state<string>('');
let tandoorEnabled = $state(false);
let tandoorImporting = $state(false);
let tandoorError = $state<string | null>(null);
@@ -51,10 +52,12 @@
if (data.recipe) {
recipe = data.recipe;
bodyText = data.bodyText || '';
status = 'done';
logs = [...logs, 'Recipe extraction successful'];
} else {
logs = [...logs, 'Error: ' + JSON.stringify(data)];
bodyText = data.bodyText || '';
logs = [...logs, 'Error: ' + (data.error || JSON.stringify(data))];
status = 'error';
}
} catch(e) {
@@ -63,6 +66,14 @@
}
}
async function retry() {
recipe = null;
bodyText = '';
status = 'idle';
logs = [...logs, 'Retrying extraction...'];
await process();
}
async function importToTandoor() {
if (!recipe) return;
@@ -116,11 +127,21 @@
<div class="animate-pulse text-blue-600">Extracting data...</div>
{/if}
{#if bodyText}
<details class="border rounded p-2 bg-white text-sm">
<summary class="cursor-pointer font-semibold">📝 View Extracted Text</summary>
<div class="mt-2 pt-2 border-t whitespace-pre-wrap break-word max-h-48 overflow-y-auto text-xs">
{bodyText}
</div>
</details>
{/if}
{#if recipe}
<div class="border rounded p-4 bg-green-50 space-y-2">
<h2 class="font-bold text-xl">{recipe.name}</h2>
<p class="text-sm">{recipe.description}</p>
<p class="text-muted"><strong>Servings:</strong> {recipe.servings}</p>
<h3 class="font-bold mt-2">Ingredients</h3>
<ul class="list-disc pl-5 text-sm">
{#each recipe.ingredients as ing}
@@ -151,6 +172,31 @@
</button>
</div>
{/if}
<button
onclick={retry}
class="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600 w-full mt-2"
>
🔄 Retry Extraction
</button>
</div>
{/if}
{#if status === 'error' && bodyText}
<div class="border rounded p-4 bg-yellow-50 space-y-2">
<h3 class="font-bold text-lg">Extraction Error - Raw Text Available</h3>
<details class="border rounded p-2 bg-white text-sm">
<summary class="cursor-pointer font-semibold">📝 View Extracted Text</summary>
<div class="mt-2 pt-2 border-t whitespace-pre-wrap break-word max-h-48 overflow-y-auto text-xs">
{bodyText}
</div>
</details>
<button
onclick={retry}
class="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600 w-full mt-2"
>
🔄 Retry Extraction
</button>
</div>
{/if}