Files
archy/packages/app/src/components/chat/ChatMessage.vue
T
Dorian 6f79f0860d feat(chat): enhance ChatMessage component with context-aware film interaction
- Updated ChatMessage component to support click interactions for film context.
- Added computed property to determine if a message has associated films.
- Refactored film ID extraction to normalize film IDs for consistency.

Made-with: Cursor
2026-03-02 16:49:47 +00:00

94 lines
2.7 KiB
Vue

<template>
<div
class="flex animate-fade-up-fast"
:class="isUser ? 'justify-end' : 'justify-start'"
:style="{ animationDelay: `${index * 30}ms` }"
>
<div
class="max-w-[85%] md:max-w-[75%] rounded-2xl px-4 py-3 transition-all duration-300"
:class="[bubbleClasses, { 'cursor-pointer': hasContext }]"
@click="handleBubbleClick"
>
<p class="text-sm leading-relaxed whitespace-pre-wrap break-words"
:class="isDark ? 'text-white/90' : 'text-gray-800'">{{ displayText }}</p>
<div v-if="inlineFilms.length > 0" class="mt-3 space-y-1">
<FilmCard
v-for="film in inlineFilms"
:key="film.id"
:film="film"
@select="handleFilmSelect"
/>
</div>
<div class="flex items-center gap-2 mt-1.5">
<span class="text-[10px] select-none"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ formattedTime }}</span>
<button
v-if="inlineFilms.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
@click="openPanel"
>
View all {{ inlineFilms.length }} films
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { Message } from '@aiui/core/types/message'
import type { Film } from '@aiui/core/types/content'
import { useTheme } from '@/composables/useTheme'
import { useContentPanel } from '@/composables/useContentPanel'
import FilmCard from '@/components/content/FilmCard.vue'
const props = defineProps<{
message: Message
index: number
}>()
const { isDark } = useTheme()
const { extractFilmIds, resolveFilms, stripFilmTags, updatePanelFromText, openFilmDetail } = useContentPanel()
const isUser = computed(() => props.message.role === 'user')
const bubbleClasses = computed(() =>
isUser.value
? 'glass-strong rounded-br-md'
: 'glass-card rounded-bl-md'
)
const inlineFilms = computed(() => {
if (isUser.value) return []
const ids = extractFilmIds(props.message.content)
return resolveFilms(ids)
})
const hasContext = computed(() => !isUser.value && inlineFilms.value.length > 0)
const displayText = computed(() => {
if (isUser.value) return props.message.content
return stripFilmTags(props.message.content)
})
const formattedTime = computed(() => {
const d = new Date(props.message.timestamp)
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
})
function handleFilmSelect(film: Film) {
updatePanelFromText(props.message.content)
openFilmDetail(film)
}
function openPanel() {
updatePanelFromText(props.message.content)
}
function handleBubbleClick() {
if (hasContext.value) openPanel()
}
</script>