feat: TurnEndDivider with prismarine floor, elevated FAB with bubbles
- Add TurnEndDivider component with pixel art ocean reef divider - Parser merges stop_hook_summary + turn_duration into single turn_end - Prismarine-inspired mosaic floor with SVG pattern and crystal highlights - Animated duration badge with underwater glow effect - Move transcript FAB to bottom-right, add elevated multi-layer shadow - Add occasional bubble particles rising from FAB button - Prevent long-touch selection on FAB (contextmenu + touch-callout) - FAB stays fixed on mobile when terminal sheet opens
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { useTranscriptDebug } from '@/composables/transcript-debug'
|
||||
import { useVoiceInput } from '@/composables/useVoiceInput'
|
||||
import { ChatContainer, AquaticBackground, AgentBadge } from '@/components/transcript-debug'
|
||||
import { ChatContainer, AquaticBackground, AgentBadge, NewSessionModal } from '@/components/transcript-debug'
|
||||
import type { AgentName } from '@/types/transcript-debug'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -65,6 +65,7 @@ const agents: { id: AgentName; label: string }[] = [
|
||||
]
|
||||
|
||||
const showSelector = ref(false)
|
||||
const showNewSessionModal = ref(false)
|
||||
const chatRef = ref<InstanceType<typeof ChatContainer> | null>(null)
|
||||
let initialized = false
|
||||
|
||||
@@ -416,7 +417,23 @@ function handleSend(message: string) {
|
||||
}
|
||||
|
||||
function handleCreateSession() {
|
||||
createNewSession()
|
||||
showNewSessionModal.value = true
|
||||
}
|
||||
|
||||
async function handleModalCreateNew(agent: AgentName, initialPrompt: string) {
|
||||
showNewSessionModal.value = false
|
||||
if (agent !== selectedAgent.value) {
|
||||
await switchAgent(agent)
|
||||
}
|
||||
createNewSession(initialPrompt || undefined)
|
||||
}
|
||||
|
||||
async function handleModalResume(sessionId: string, agent: AgentName) {
|
||||
showNewSessionModal.value = false
|
||||
if (agent !== selectedAgent.value) {
|
||||
await switchAgent(agent)
|
||||
}
|
||||
selectSession(sessionId)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -432,20 +449,20 @@ watch(isOpen, async (open) => {
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// KEYBOARD SHORTCUTS
|
||||
// KEYBOARD SHORTCUTS (Ctrl+1..5 terminal switch, Ctrl+/- zoom)
|
||||
// ============================================================================
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
function handleGlobalKeydown(e: KeyboardEvent) {
|
||||
if (!e.ctrlKey) return
|
||||
|
||||
// Ctrl+1..5 → switch to terminal by index
|
||||
const num = parseInt(e.key)
|
||||
if (num >= 1 && num <= 5) {
|
||||
const slot = openTerminals.value[num - 1]
|
||||
if (!slot) return
|
||||
const terminal = openTerminals.value[num - 1]
|
||||
if (!terminal) return
|
||||
e.preventDefault()
|
||||
if (!isOpen.value) isOpen.value = true
|
||||
switchToTerminal(slot.sessionId)
|
||||
switchToTerminal(terminal.sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -467,7 +484,7 @@ function handleKeydown(e: KeyboardEvent) {
|
||||
onMounted(async () => {
|
||||
checkMobile()
|
||||
window.addEventListener('resize', checkMobile)
|
||||
document.addEventListener('keydown', handleKeydown)
|
||||
document.addEventListener('keydown', handleGlobalKeydown)
|
||||
oceanLifeTimer = setInterval(tickOceanLife, 20000)
|
||||
tickOceanLife()
|
||||
await voice.init()
|
||||
@@ -477,7 +494,7 @@ onBeforeUnmount(() => {
|
||||
if (oceanLifeTimer) clearInterval(oceanLifeTimer)
|
||||
disconnectRealtime()
|
||||
voice.cleanup()
|
||||
document.removeEventListener('keydown', handleKeydown)
|
||||
document.removeEventListener('keydown', handleGlobalKeydown)
|
||||
document.removeEventListener('mousemove', onDrag)
|
||||
document.removeEventListener('mouseup', stopDrag)
|
||||
document.removeEventListener('mousemove', onResize)
|
||||
@@ -686,6 +703,15 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<NewSessionModal
|
||||
:visible="showNewSessionModal"
|
||||
:agents="agents"
|
||||
:current-agent="selectedAgent"
|
||||
@close="showNewSessionModal = false"
|
||||
@create-new="handleModalCreateNew"
|
||||
@resume="handleModalResume"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
@@ -1217,8 +1243,6 @@ onBeforeUnmount(() => {
|
||||
color: rgba(255,255,255,0.85);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
field-sizing: content;
|
||||
}
|
||||
|
||||
/* Send button: pixel art daytime ocean, no border */
|
||||
|
||||
Reference in New Issue
Block a user