mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Implement graceful recipe error handling with filename display (#4363)
Signed-off-by: Jon Andersen <jon.andersen.se@gmail.com>
This commit is contained in:
@@ -372,67 +372,90 @@ Parameters you can use:
|
||||
}
|
||||
};
|
||||
|
||||
// Render a recipe item
|
||||
const RecipeItem = ({ savedRecipe }: { savedRecipe: SavedRecipe }) => (
|
||||
<Card className="py-2 px-4 mb-2 bg-background-default border-none hover:bg-background-muted cursor-pointer transition-all duration-150">
|
||||
<div className="flex justify-between items-start gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-base truncate max-w-[50vw]">{savedRecipe.recipe.title}</h3>
|
||||
{savedRecipe.isGlobal ? (
|
||||
<Globe className="w-4 h-4 text-text-muted flex-shrink-0" />
|
||||
) : (
|
||||
<Folder className="w-4 h-4 text-text-muted flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-text-muted text-sm mb-2 line-clamp-2">
|
||||
{savedRecipe.recipe.description}
|
||||
</p>
|
||||
<div className="flex items-center text-xs text-text-muted">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{savedRecipe.lastModified.toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
// Render a recipe item with error handling
|
||||
const RecipeItem = ({ savedRecipe }: { savedRecipe: SavedRecipe }) => {
|
||||
try {
|
||||
return (
|
||||
<Card className="py-2 px-4 mb-2 bg-background-default border-none hover:bg-background-muted cursor-pointer transition-all duration-150">
|
||||
<div className="flex justify-between items-start gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-base truncate max-w-[50vw]">{savedRecipe.recipe.title}</h3>
|
||||
{savedRecipe.isGlobal ? (
|
||||
<Globe className="w-4 h-4 text-text-muted flex-shrink-0" />
|
||||
) : (
|
||||
<Folder className="w-4 h-4 text-text-muted flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-text-muted text-sm mb-2 line-clamp-2">
|
||||
{savedRecipe.recipe.description}
|
||||
</p>
|
||||
<div className="flex items-center text-xs text-text-muted">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{savedRecipe.lastModified.toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleLoadRecipe(savedRecipe);
|
||||
}}
|
||||
size="sm"
|
||||
className="h-8"
|
||||
>
|
||||
<Bot className="w-4 h-4 mr-1" />
|
||||
Use
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePreviewRecipe(savedRecipe);
|
||||
}}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8"
|
||||
>
|
||||
<FileText className="w-4 h-4 mr-1" />
|
||||
Preview
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteRecipe(savedRecipe);
|
||||
}}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleLoadRecipe(savedRecipe);
|
||||
}}
|
||||
size="sm"
|
||||
className="h-8"
|
||||
>
|
||||
<Bot className="w-4 h-4 mr-1" />
|
||||
Use
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePreviewRecipe(savedRecipe);
|
||||
}}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8"
|
||||
>
|
||||
<FileText className="w-4 h-4 mr-1" />
|
||||
Preview
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteRecipe(savedRecipe);
|
||||
}}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
} catch (error) {
|
||||
// Error row showing failed to read file with filename and error details
|
||||
return (
|
||||
<Card className="py-2 px-4 mb-2 bg-red-50 border border-red-200 dark:bg-red-900/20 dark:border-red-800">
|
||||
<div className="flex justify-between items-start gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<AlertCircle className="w-4 h-4 text-red-500 flex-shrink-0" />
|
||||
<h3 className="text-base text-red-700 dark:text-red-300">
|
||||
Failed to read file: {savedRecipe.filename}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-red-600 dark:text-red-400 text-sm">
|
||||
{error instanceof Error ? error.message : 'Unknown error'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Render skeleton loader for recipe items
|
||||
const RecipeSkeleton = () => (
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface SavedRecipe {
|
||||
isGlobal: boolean;
|
||||
lastModified: Date;
|
||||
isArchived?: boolean;
|
||||
filename: string; // The actual filename used
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +67,7 @@ async function loadRecipeFromFile(
|
||||
return {
|
||||
...recipeData,
|
||||
isGlobal: isGlobal,
|
||||
filename: recipeName,
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load recipe from ${filePath}:`, error);
|
||||
@@ -112,6 +114,7 @@ export async function saveRecipe(recipe: Recipe, options: SaveRecipeOptions): Pr
|
||||
// Create saved recipe object
|
||||
const savedRecipe: SavedRecipe = {
|
||||
name: sanitizedName,
|
||||
filename: sanitizedName,
|
||||
recipe: recipe,
|
||||
isGlobal: global,
|
||||
lastModified: new Date(),
|
||||
|
||||
Reference in New Issue
Block a user