feat(renderer): recipe & event card renderers (M10.4, M10.5)

- RecipeCard: ingredients checklist, numbered steps, scale slider
- EventCard: date chip, countdown timer, ICS download, Google Calendar
- Extract <recipe_ext> and <event_ext> tags from AI responses
- Both render inline in chat messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-03 23:55:02 +00:00
co-authored by Claude Opus 4.6
parent aec87a347c
commit c070e76515
4 changed files with 362 additions and 0 deletions
@@ -184,6 +184,22 @@
/>
</div>
<div v-if="inlineRecipes.length > 0" class="mt-3 space-y-2" @click.stop>
<RecipeCard
v-for="(recipe, i) in inlineRecipes"
:key="`recipe-${i}`"
:recipe="recipe"
/>
</div>
<div v-if="inlineEvents.length > 0" class="mt-3 space-y-2" @click.stop>
<EventCard
v-for="(event, i) in inlineEvents"
:key="`event-${i}`"
:event="event"
/>
</div>
<div v-if="inlineNewsLinks.length > 0" class="mt-3 space-y-1" @click.stop>
<NewsCard
v-for="(link, i) in inlineNewsLinks"
@@ -325,6 +341,9 @@ import NewsCard from '@/components/content/NewsCard.vue'
import NostrEmbed from '@/components/chat/NostrEmbed.vue'
import CashuToken from '@/components/chat/CashuToken.vue'
import { extractCashuTokens } from '@/utils/cashu'
import { extractRecipes, extractEvents } from '@/composables/contentExtraction'
import RecipeCard from '@/components/renderers/RecipeCard.vue'
import EventCard from '@/components/renderers/EventCard.vue'
const props = withDefaults(
defineProps<{
@@ -417,6 +436,16 @@ const cashuTokens = computed(() => {
return extractCashuTokens(props.message.content)
})
const inlineRecipes = computed(() => {
if (isUser.value) return []
return extractRecipes(props.message.content)
})
const inlineEvents = computed(() => {
if (isUser.value) return []
return extractEvents(props.message.content)
})
const isCodeResponse = computed(() =>
!isUser.value && props.triggeringQuery.trim().toLowerCase() === '/code'
)