feat: Animated pixel art ocean floor background for FloatingTranscriptDebug
Replace galaxy background with animated underwater scene featuring water depth gradient, pixel art sea floor (sand, seaweed, coral, starfish), rising bubbles with water sway, and swimming pixel art fish. Also update transcript-debug tool cards styling and add .claude-*/tasks/ to gitignore.
This commit is contained in:
@@ -107,6 +107,51 @@ async function copySelected() {
|
||||
setTimeout(() => (selectedCopied.value = false), 1500)
|
||||
}
|
||||
|
||||
// ── Collapse sections ──
|
||||
const collapsedSections = ref(new Set<string>())
|
||||
|
||||
// For each message, find which user message "owns" it (section leader)
|
||||
const sectionMap = computed(() => {
|
||||
const map = new Map<string, string>() // messageUuid → ownerUserUuid
|
||||
let currentUserUuid: string | null = null
|
||||
for (const msg of props.conversation.messages) {
|
||||
if (msg.kind === 'user') {
|
||||
currentUserUuid = msg.uuid
|
||||
} else if (currentUserUuid) {
|
||||
map.set(msg.uuid, currentUserUuid)
|
||||
}
|
||||
}
|
||||
return map
|
||||
})
|
||||
|
||||
// Count of non-user messages per section
|
||||
const sectionCounts = computed(() => {
|
||||
const counts = new Map<string, number>()
|
||||
let currentUserUuid: string | null = null
|
||||
for (const msg of props.conversation.messages) {
|
||||
if (msg.kind === 'user') {
|
||||
currentUserUuid = msg.uuid
|
||||
if (!counts.has(currentUserUuid)) counts.set(currentUserUuid, 0)
|
||||
} else if (currentUserUuid) {
|
||||
counts.set(currentUserUuid, (counts.get(currentUserUuid) || 0) + 1)
|
||||
}
|
||||
}
|
||||
return counts
|
||||
})
|
||||
|
||||
function toggleCollapse(userUuid: string) {
|
||||
const s = new Set(collapsedSections.value)
|
||||
if (s.has(userUuid)) s.delete(userUuid)
|
||||
else s.add(userUuid)
|
||||
collapsedSections.value = s
|
||||
}
|
||||
|
||||
function isCollapsedChild(msg: { uuid: string; kind: string }): boolean {
|
||||
if (msg.kind === 'user') return false
|
||||
const owner = sectionMap.value.get(msg.uuid)
|
||||
return !!owner && collapsedSections.value.has(owner)
|
||||
}
|
||||
|
||||
// Track messages that just resolved from optimistic → real
|
||||
// These skip the bounce animation and get a smooth transition instead
|
||||
const resolvedUuids = ref(new Set<string>())
|
||||
@@ -207,47 +252,54 @@ function formatDuration(start: string, end: string): string {
|
||||
</div>
|
||||
</div>
|
||||
<div ref="scrollContainer" class="messages-scroll">
|
||||
<div
|
||||
<template
|
||||
v-for="(msg, idx) in conversation.messages"
|
||||
:key="msg.uuid"
|
||||
:class="['message-wrapper', {
|
||||
resolved: resolvedUuids.has(msg.uuid),
|
||||
selected: selectedUuids.has(msg.uuid),
|
||||
'assistant-continuation': msg.kind === 'assistant' && idx > 0 && conversation.messages[idx - 1].kind === 'assistant'
|
||||
}]"
|
||||
>
|
||||
<!-- Select checkbox -->
|
||||
<button
|
||||
v-if="selectMode && msg.kind !== 'progress'"
|
||||
:class="['select-checkbox', { checked: selectedUuids.has(msg.uuid) }]"
|
||||
@click.stop="toggleSelect(msg.uuid)"
|
||||
:title="selectedUuids.has(msg.uuid) ? 'Deselect' : 'Select'"
|
||||
<div
|
||||
v-if="!isCollapsedChild(msg)"
|
||||
:class="['message-wrapper', {
|
||||
resolved: resolvedUuids.has(msg.uuid),
|
||||
selected: selectedUuids.has(msg.uuid),
|
||||
'assistant-continuation': msg.kind === 'assistant' && idx > 0 && conversation.messages[idx - 1].kind === 'assistant' && !isCollapsedChild(conversation.messages[idx - 1])
|
||||
}]"
|
||||
>
|
||||
<svg v-if="selectedUuids.has(msg.uuid)" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Select checkbox -->
|
||||
<button
|
||||
v-if="selectMode && msg.kind !== 'progress'"
|
||||
:class="['select-checkbox', { checked: selectedUuids.has(msg.uuid) }]"
|
||||
@click.stop="toggleSelect(msg.uuid)"
|
||||
:title="selectedUuids.has(msg.uuid) ? 'Deselect' : 'Select'"
|
||||
>
|
||||
<svg v-if="selectedUuids.has(msg.uuid)" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div :class="['message-content', { selectable: selectMode && msg.kind !== 'progress' }]" @click="selectMode && msg.kind !== 'progress' && toggleSelect(msg.uuid)">
|
||||
<UserMessageBubble
|
||||
v-if="msg.kind === 'user'"
|
||||
:message="msg"
|
||||
/>
|
||||
<AssistantMessageBubble
|
||||
v-else-if="msg.kind === 'assistant'"
|
||||
:message="msg"
|
||||
:show-header="!(idx > 0 && conversation.messages[idx - 1].kind === 'assistant')"
|
||||
/>
|
||||
<ProgressEvent
|
||||
v-else-if="msg.kind === 'progress'"
|
||||
:group="msg"
|
||||
/>
|
||||
<SystemMessage
|
||||
v-else-if="msg.kind === 'system'"
|
||||
:message="msg"
|
||||
/>
|
||||
<div :class="['message-content', { selectable: selectMode && msg.kind !== 'progress' }]" @click="selectMode && msg.kind !== 'progress' && toggleSelect(msg.uuid)">
|
||||
<UserMessageBubble
|
||||
v-if="msg.kind === 'user'"
|
||||
:message="msg"
|
||||
:collapsed="collapsedSections.has(msg.uuid)"
|
||||
:section-count="sectionCounts.get(msg.uuid) || 0"
|
||||
@toggle-collapse="toggleCollapse(msg.uuid)"
|
||||
/>
|
||||
<AssistantMessageBubble
|
||||
v-else-if="msg.kind === 'assistant'"
|
||||
:message="msg"
|
||||
:show-header="!(idx > 0 && conversation.messages[idx - 1].kind === 'assistant')"
|
||||
/>
|
||||
<ProgressEvent
|
||||
v-else-if="msg.kind === 'progress'"
|
||||
:group="msg"
|
||||
/>
|
||||
<SystemMessage
|
||||
v-else-if="msg.kind === 'system'"
|
||||
:message="msg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Selection floating bar -->
|
||||
|
||||
Reference in New Issue
Block a user