fix: make recipe crawling work

This commit is contained in:
Max Richter
2025-11-12 15:41:30 +01:00
parent 92126882b6
commit 7ad08daf80
19 changed files with 44 additions and 55 deletions

View File

@@ -37,17 +37,17 @@ async function processCreateRecipeFromUrl(
let recipe: z.infer<typeof recipeSchema> | undefined = undefined;
if (jsonLds.length > 0) {
for (const jsonLd of jsonLds) {
recipe = parseJsonLdToRecipeSchema(jsonLd.textContent || "");
if (recipe) break;
if (jsonLd.textContent) {
recipe = parseJsonLdToRecipeSchema(jsonLd.textContent);
if (recipe) break;
}
}
}
if (!recipe) {
const res = await openai.extractRecipe(result.markdown);
if (!res || "errorMessages" in res) {
const errorMessage = res?.errorMessages?.[0] ||
"could not extract recipe";
streamResponse.error(`failed to extract recipe: ${errorMessage}`);
if (!res || res === "none") {
streamResponse.error(`failed to extract recipe: ${res}`);
return;
}
recipe = res;
@@ -72,9 +72,7 @@ async function processCreateRecipeFromUrl(
if (newRecipe?.image && newRecipe.image.length > 5) {
const extension = fileExtension(newRecipe.image);
const finalPath = `resources/recipes/images/${
safeFileName(id)
}_cover.${extension}`;
const finalPath = `recipes/images/${safeFileName(id)}_cover.${extension}`;
streamResponse.info("downloading image");
try {
streamResponse.info("downloading image");
@@ -82,7 +80,7 @@ async function processCreateRecipeFromUrl(
streamResponse.info("saving image");
const buffer = await res.arrayBuffer();
await createResource(finalPath, buffer);
newRecipe.image = finalPath;
newRecipe.image = `resources/${finalPath}`;
} catch (err) {
console.log("Failed to save image", err);
}

View File

@@ -1,5 +1,4 @@
import recipeSchema from "@lib/recipeSchema.ts";
import { parseIngredients } from "@lib/parseIngredient.ts";
import recipeSchema, { Recipe } from "@lib/recipeSchema.ts";
export function parseJsonLdToRecipeSchema(jsonLdContent: string) {
try {
@@ -19,12 +18,7 @@ export function parseJsonLdToRecipeSchema(jsonLdContent: string) {
return;
}
// Map and parse ingredients into the new schema
const ingredients = parseIngredients(
data?.recipeIngredient?.join("\n") || "",
);
const instructions = Array.isArray(data.recipeInstructions)
const recipeInstructions = Array.isArray(data.recipeInstructions)
? data.recipeInstructions.map((instr: unknown) => {
if (!instr) return "";
if (typeof instr === "string") return instr;
@@ -36,43 +30,41 @@ export function parseJsonLdToRecipeSchema(jsonLdContent: string) {
: [];
// Parse servings
const servings = parseServings(data.recipeYield);
const recipeYield = parseServings(data.recipeYield);
// Parse times
const prepTime = parseDuration(data.prepTime);
const cookTime = parseDuration(data.cookTime);
const totalTime = parseDuration(data.totalTime);
// Extract tags
const tags = data.keywords
const keywords = data.keywords
? Array.isArray(data.keywords)
? data.keywords
: data.keywords.split(",").map((tag: string) => tag.trim())
: [];
// Build the recipe object
const recipe = {
const recipe: Recipe = {
_type: "Recipe",
title: data.name || "Unnamed Recipe",
name: data.name || "Unnamed Recipe",
image: pickImage(image || data.image || ""),
author: Array.isArray(data.author)
? data.author.map((a: { name: string }) => a.name).join(", ")
: data.author?.name || "",
author: {
"_type": "Person",
name: Array.isArray(data.author)
? data.author.map((a: { name: string }) => a.name).join(", ")
: data.author?.name || "",
},
description: data.description || "",
ingredients,
instructions,
servings,
prepTime,
cookTime,
recipeIngredient: data.recipeIngredient,
recipeInstructions,
recipeYield,
totalTime,
tags,
notes: data.notes || [],
keywords,
};
// Validate against the schema
return recipeSchema.parse(recipe);
} catch (error) {
console.error("Invalid JSON-LD content or parsing error:", error);
console.log("Invalid JSON-LD content or parsing error:", error);
return undefined;
}
}

View File

@@ -49,8 +49,9 @@ export default function Greet(
<RedirectSearchHandler />
<KMenu type="main" context={{ type: "articles" }} />
<Grid>
{articles?.map((doc) => (
{articles?.map((doc, i) => (
<ResourceCard
key={doc.name || i}
sublink="articles"
res={doc}
/>

View File

@@ -12,14 +12,12 @@ export default function Home(props: PageProps) {
<RedirectSearchHandler />
<KMenu type="main" context={false} />
<MainLayout url={props.url}>
<h1 class="text-4xl mb-4 mt-3 text-white flex gap-2">
<img src="/favicon.png" class="w-8 h-8 inline" />
Resources
</h1>
<div class="flex flex-wrap items-center gap-4">
{Object.values(resources).filter((v) => v.link !== "/").map((m) => {
return (
<Card
splotch
key={m.link}
title={`${m.name}`}
backgroundSize={80}
image={`${

View File

@@ -52,7 +52,7 @@ export default async function MovieIndex(
</header>
<Grid>
{movies?.map((doc, i) => {
return <ResourceCard key={i} res={doc} />;
return <ResourceCard key={doc.name || i} res={doc} />;
})}
</Grid>
</MainLayout>

View File

@@ -50,7 +50,9 @@ export default function Greet(
</header>
<Grid>
{series?.map((doc, i) => {
return <ResourceCard key={i} sublink="series" res={doc} />;
return (
<ResourceCard key={doc.name || i} sublink="series" res={doc} />
);
})}
</Grid>
</MainLayout>