feat(frontend): assemble accessible application shell
This commit is contained in:
parent
96c69507ec
commit
036ef3cc6f
27 changed files with 1852 additions and 13 deletions
132
frontend/src/components/AssistantMessage.vue
Normal file
132
frontend/src/components/AssistantMessage.vue
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { AssistantTurn } from '../lib/models'
|
||||
import EmptyResults from './EmptyResults.vue'
|
||||
import PlaylistError from './PlaylistError.vue'
|
||||
import PlaylistSaved from './PlaylistSaved.vue'
|
||||
import RequestMetadata from './RequestMetadata.vue'
|
||||
import ResultActions from './ResultActions.vue'
|
||||
import ResultSet from './ResultSet.vue'
|
||||
import StreamError from './StreamError.vue'
|
||||
import StreamWarning from './StreamWarning.vue'
|
||||
import ThinkingIndicator from './ThinkingIndicator.vue'
|
||||
|
||||
const props = defineProps<{ turn: AssistantTurn; canSave: boolean }>()
|
||||
defineEmits<{ save: []; retry: [query: string] }>()
|
||||
|
||||
const thinkingLabel = computed(() => {
|
||||
if (props.turn.requestId === null) return 'Reading the request'
|
||||
if (props.turn.tracks.length === 0) {
|
||||
return props.turn.candidateCount === null
|
||||
? 'Checking candidates'
|
||||
: `Checking ${props.turn.candidateCount} candidates`
|
||||
}
|
||||
return 'Streaming verified tracks'
|
||||
})
|
||||
|
||||
const isEmptyResult = computed(
|
||||
() =>
|
||||
props.turn.status === 'done' &&
|
||||
props.turn.completion?.track_count === 0 &&
|
||||
props.turn.tracks.length === 0,
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="assistant">
|
||||
<div class="avatar" aria-hidden="true">d</div>
|
||||
<div class="column">
|
||||
<p v-if="turn.intentSummary" class="intent">{{ turn.intentSummary }}</p>
|
||||
|
||||
<ResultSet v-if="turn.tracks.length" :tracks="turn.tracks" />
|
||||
|
||||
<ThinkingIndicator v-if="turn.status === 'streaming'" :label="thinkingLabel" />
|
||||
|
||||
<StreamWarning
|
||||
v-for="(warning, index) in turn.warnings"
|
||||
:key="`${warning.code}-${index}`"
|
||||
:warning="warning"
|
||||
/>
|
||||
|
||||
<StreamError
|
||||
v-if="turn.error"
|
||||
:code="turn.error.code"
|
||||
:message="turn.error.message"
|
||||
@retry="$emit('retry', turn.query)"
|
||||
/>
|
||||
<StreamError
|
||||
v-else-if="turn.transportFailure"
|
||||
:code="turn.transportFailure.kind"
|
||||
:message="turn.transportFailure.message"
|
||||
@retry="$emit('retry', turn.query)"
|
||||
/>
|
||||
|
||||
<EmptyResults v-if="isEmptyResult" />
|
||||
<RequestMetadata :turn="turn" />
|
||||
|
||||
<ResultActions
|
||||
v-if="turn.status === 'done' && turn.tracks.length && turn.playlist.status !== 'saved'"
|
||||
:state="turn.playlist"
|
||||
:track-count="turn.tracks.length"
|
||||
:can-save="canSave"
|
||||
@save="$emit('save')"
|
||||
/>
|
||||
<PlaylistError
|
||||
v-if="turn.playlist.status === 'error' && turn.playlist.message"
|
||||
:message="turn.playlist.message"
|
||||
/>
|
||||
<PlaylistSaved
|
||||
v-if="turn.playlist.status === 'saved' && turn.playlist.name && turn.playlist.url"
|
||||
:name="turn.playlist.name"
|
||||
:url="turn.playlist.url"
|
||||
:track-count="turn.tracks.length"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.assistant {
|
||||
display: flex;
|
||||
gap: var(--s-5);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: grid;
|
||||
flex: none;
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin-top: 1px;
|
||||
color: var(--c-accent);
|
||||
font-family: var(--f-display);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
border: 1px solid var(--c-line);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: var(--s-7);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.intent {
|
||||
margin: 0;
|
||||
font-size: var(--t-lead);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.assistant {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue