fix(frontend): harden the streaming ui and extract copy and constants
This commit is contained in:
parent
036ef3cc6f
commit
a3af8f45cc
36 changed files with 1662 additions and 226 deletions
|
|
@ -1,40 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { messages } from '../lib/messages'
|
||||
import type { AssistantTurn, EventLogEntry } from '../lib/models'
|
||||
|
||||
const props = defineProps<{
|
||||
latest: AssistantTurn | undefined
|
||||
log: EventLogEntry[]
|
||||
opener: HTMLElement | null
|
||||
}>()
|
||||
const emit = defineEmits<{ close: []; reset: [] }>()
|
||||
|
||||
const panel = ref<HTMLElement | null>(null)
|
||||
const closeButton = ref<HTMLButtonElement | null>(null)
|
||||
let returnFocus: HTMLElement | null = null
|
||||
|
||||
function latestValue(value: string | null | undefined): string {
|
||||
return value ?? messages.devPanelWaiting
|
||||
}
|
||||
|
||||
const rows = computed(() => [
|
||||
{ label: 'request id', value: props.latest?.requestId ?? 'waiting' },
|
||||
{ label: messages.devPanelRequestId, value: latestValue(props.latest?.requestId) },
|
||||
{
|
||||
label: 'candidate count',
|
||||
value: props.latest?.candidateCount?.toString() ?? 'waiting',
|
||||
label: messages.devPanelCandidateCount,
|
||||
value: latestValue(props.latest?.candidateCount?.toString()),
|
||||
},
|
||||
{
|
||||
label: 'track count',
|
||||
value:
|
||||
props.latest?.completion?.track_count.toString() ??
|
||||
props.latest?.tracks.length.toString() ??
|
||||
'waiting',
|
||||
label: messages.devPanelTrackCount,
|
||||
value: latestValue(
|
||||
props.latest?.completion?.track_count.toString() ?? props.latest?.tracks.length.toString(),
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'total ms',
|
||||
value: props.latest?.completion?.total_ms.toString() ?? 'waiting',
|
||||
label: messages.devPanelTotalMilliseconds,
|
||||
value: latestValue(props.latest?.completion?.total_ms.toString()),
|
||||
},
|
||||
])
|
||||
const entries = computed(() =>
|
||||
props.log.length
|
||||
? [...props.log].reverse()
|
||||
: [{ timestamp: '--:--:--', type: 'idle', detail: 'waiting for a request' }],
|
||||
)
|
||||
const entries = computed(() => {
|
||||
if (props.log.length) return [...props.log].reverse()
|
||||
return [
|
||||
{
|
||||
timestamp: '--:--:--',
|
||||
type: messages.devPanelIdle,
|
||||
detail: messages.devPanelWaitingForRequest,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
function focusableElements(): HTMLElement[] {
|
||||
if (!panel.value) return []
|
||||
|
|
@ -67,12 +76,15 @@ function onKeydown(event: KeyboardEvent): void {
|
|||
}
|
||||
|
||||
onMounted(async () => {
|
||||
returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null
|
||||
await nextTick()
|
||||
closeButton.value?.focus()
|
||||
})
|
||||
|
||||
onUnmounted(() => returnFocus?.focus())
|
||||
onUnmounted(() => {
|
||||
const fallback = document.querySelector<HTMLElement>('[data-dev-panel-opener]')
|
||||
const target = props.opener?.isConnected ? props.opener : fallback
|
||||
target?.focus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -86,21 +98,21 @@ onUnmounted(() => returnFocus?.focus())
|
|||
@keydown="onKeydown"
|
||||
>
|
||||
<div class="bar">
|
||||
<h2 id="dev-panel-title" class="caps">Dev panel</h2>
|
||||
<h2 id="dev-panel-title" class="caps">{{ messages.devPanelTitle }}</h2>
|
||||
<button
|
||||
ref="closeButton"
|
||||
class="close"
|
||||
type="button"
|
||||
aria-label="Close dev panel"
|
||||
:aria-label="messages.devPanelCloseLabel"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<span aria-hidden="true">X</span>
|
||||
<span aria-hidden="true">{{ messages.devPanelCloseSymbol }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="body">
|
||||
<section>
|
||||
<h3 class="caps">Latest request</h3>
|
||||
<p class="caption">Only counters supplied by the recommendation contract are shown.</p>
|
||||
<h3 class="caps">{{ messages.devPanelLatestRequest }}</h3>
|
||||
<p class="caption">{{ messages.devPanelContractCaption }}</p>
|
||||
<div class="table">
|
||||
<div v-for="row in rows" :key="row.label" class="row">
|
||||
<span class="key">{{ row.label }}</span
|
||||
|
|
@ -109,11 +121,13 @@ onUnmounted(() => returnFocus?.focus())
|
|||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h3 class="caps section-heading">Conversation</h3>
|
||||
<button class="reset" type="button" @click="$emit('reset')">Clear conversation</button>
|
||||
<h3 class="caps section-heading">{{ messages.devPanelConversation }}</h3>
|
||||
<button class="reset" type="button" @click="$emit('reset')">
|
||||
{{ messages.devPanelClearConversation }}
|
||||
</button>
|
||||
</section>
|
||||
<section>
|
||||
<h3 class="caps section-heading">Event stream</h3>
|
||||
<h3 class="caps section-heading">{{ messages.devPanelEventStream }}</h3>
|
||||
<div class="log">
|
||||
<div v-for="(entry, index) in entries" :key="index" class="entry">
|
||||
<span class="time">{{ entry.timestamp }}</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue