KI-AGENT: Call UX im Chat verbessern

This commit is contained in:
2026-05-20 20:32:02 +02:00
parent 1a5c69fcfb
commit 2278dfa714

View File

@@ -146,6 +146,18 @@ const matrixCallDurationLabel = computed(() => {
return `${minutes}:${seconds}` return `${minutes}:${seconds}`
}) })
const matrixCallPrimaryTile = computed(() =>
matrixCallTiles.value.find((tile) => tile.screenSharing) ||
matrixCallTiles.value.find((tile) => !tile.local && tile.videoTrack) ||
matrixCallTiles.value.find((tile) => tile.videoTrack) ||
matrixCallTiles.value[0] ||
null
)
const matrixCallSecondaryTiles = computed(() =>
matrixCallTiles.value.filter((tile) => tile.id !== matrixCallPrimaryTile.value?.id)
)
const roomCreateKeyPreview = computed(() => const roomCreateKeyPreview = computed(() =>
normalizeRoomKey(roomCreateForm.value.key || roomCreateForm.value.name) normalizeRoomKey(roomCreateForm.value.key || roomCreateForm.value.name)
) )
@@ -396,6 +408,9 @@ const replyPreview = (message) => {
return message.body || message.attachment?.fileName || "Nachricht" return message.body || message.attachment?.fileName || "Nachricht"
} }
const participantInitial = (tile) =>
(tile?.name || tile?.identity || "?").slice(0, 1).toUpperCase()
const setReplyTarget = (message) => { const setReplyTarget = (message) => {
matrixEditingMessage.value = null matrixEditingMessage.value = null
matrixReplyTarget.value = { matrixReplyTarget.value = {
@@ -1766,15 +1781,43 @@ onBeforeUnmount(() => {
v-if="matrixLiveKitRoom && matrixCallConnected" v-if="matrixLiveKitRoom && matrixCallConnected"
class="flex shrink-0 flex-wrap items-center justify-between gap-3 border-b border-primary/20 bg-primary/10 px-4 py-3" class="flex shrink-0 flex-wrap items-center justify-between gap-3 border-b border-primary/20 bg-primary/10 px-4 py-3"
> >
<div class="min-w-0"> <button
type="button"
class="min-w-0 text-left"
@click="matrixCallOpen = true"
>
<p class="truncate text-sm font-medium text-highlighted"> <p class="truncate text-sm font-medium text-highlighted">
{{ matrixCallTitle }} läuft in {{ activeRoom.name }} {{ matrixCallTitle }} läuft in {{ activeRoom.name }}
</p> </p>
<p class="truncate text-xs text-muted"> <p class="truncate text-xs text-muted">
{{ matrixCallParticipantCount }} Teilnehmer · {{ matrixCallDurationLabel }} · {{ matrixCallStateLabel }} {{ matrixCallParticipantCount }} Teilnehmer · {{ matrixCallDurationLabel }} · {{ matrixCallStateLabel }}
</p> </p>
</div> </button>
<div class="flex items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<UButton
:icon="matrixCallMicEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone-slash'"
color="neutral"
:variant="matrixCallMicEnabled ? 'soft' : 'outline'"
size="sm"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallMic"
/>
<UButton
:icon="matrixCallCameraEnabled ? 'i-heroicons-video-camera' : 'i-heroicons-video-camera-slash'"
color="neutral"
:variant="matrixCallCameraEnabled ? 'soft' : 'outline'"
size="sm"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallCamera"
/>
<UButton
:icon="matrixCallScreenShareEnabled ? 'i-heroicons-computer-desktop' : 'i-heroicons-arrow-up-tray'"
color="neutral"
:variant="matrixCallScreenShareEnabled ? 'soft' : 'outline'"
size="sm"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallScreenShare"
/>
<UButton <UButton
icon="i-heroicons-arrow-top-right-on-square" icon="i-heroicons-arrow-top-right-on-square"
color="primary" color="primary"
@@ -2232,44 +2275,17 @@ onBeforeUnmount(() => {
class="h-[100dvh]" class="h-[100dvh]"
> >
<template #content> <template #content>
<div class="flex h-[100dvh] flex-col bg-default"> <div class="flex h-[100dvh] flex-col bg-inverted text-inverted">
<header class="flex shrink-0 items-center justify-between gap-3 border-b border-default px-4 py-3"> <header class="flex shrink-0 items-center justify-between gap-3 border-b border-white/10 bg-black px-4 py-3">
<div class="min-w-0"> <div class="min-w-0">
<h3 class="truncate text-base font-semibold text-highlighted"> <h3 class="truncate text-base font-semibold text-white">
{{ matrixCallTitle }} · {{ activeRoom.name }} {{ matrixCallTitle }} · {{ activeRoom.name }}
</h3> </h3>
<p class="truncate text-xs text-muted"> <p class="truncate text-xs text-white/60">
{{ activeRoomMatrixAddress }} {{ matrixCallParticipantCount }} Teilnehmer · {{ matrixCallDurationLabel }} · {{ matrixCallStateLabel }}
</p> </p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UButton
icon="i-heroicons-microphone"
color="neutral"
:variant="matrixCallMicEnabled ? 'soft' : 'outline'"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallMic"
>
{{ matrixCallMicEnabled ? "Mikro an" : "Mikro aus" }}
</UButton>
<UButton
icon="i-heroicons-video-camera"
color="neutral"
:variant="matrixCallCameraEnabled ? 'soft' : 'outline'"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallCamera"
>
{{ matrixCallCameraEnabled ? "Kamera an" : "Kamera aus" }}
</UButton>
<UButton
icon="i-heroicons-computer-desktop"
color="neutral"
:variant="matrixCallScreenShareEnabled ? 'soft' : 'outline'"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallScreenShare"
>
{{ matrixCallScreenShareEnabled ? "Freigabe an" : "Freigeben" }}
</UButton>
<UButton <UButton
icon="i-heroicons-arrows-pointing-in" icon="i-heroicons-arrows-pointing-in"
color="neutral" color="neutral"
@@ -2296,10 +2312,10 @@ onBeforeUnmount(() => {
</div> </div>
</header> </header>
<div class="min-h-0 flex-1 bg-muted"> <div class="min-h-0 flex-1 bg-black">
<div <div
v-if="matrixCallLoading" v-if="matrixCallLoading"
class="flex h-full items-center justify-center text-sm text-muted" class="flex h-full items-center justify-center text-sm text-white/70"
> >
Besprechung wird gestartet... Besprechung wird gestartet...
</div> </div>
@@ -2319,59 +2335,133 @@ onBeforeUnmount(() => {
v-else-if="canStartMatrixCall" v-else-if="canStartMatrixCall"
class="flex h-full flex-col" class="flex h-full flex-col"
> >
<div class="flex shrink-0 items-center justify-between border-b border-default bg-default px-4 py-2 text-xs text-muted"> <div class="flex min-h-0 flex-1 flex-col gap-3 p-3 lg:flex-row">
<span>{{ matrixCallParticipantCount }} Teilnehmer · {{ matrixCallDurationLabel }}</span> <div class="relative min-h-0 flex-1 overflow-hidden rounded-lg bg-zinc-950">
<span>{{ matrixCallStateLabel }}</span> <template v-if="matrixCallPrimaryTile">
</div> <video
<div class="grid min-h-0 flex-1 auto-rows-fr gap-3 overflow-y-auto p-3 sm:grid-cols-2 xl:grid-cols-3"> v-if="matrixCallPrimaryTile.videoTrack"
<div :ref="(element) => setMatrixCallVideoElement(matrixCallPrimaryTile.id, element)"
v-for="tile in matrixCallTiles" class="h-full min-h-[60dvh] w-full"
:key="tile.id" :class="matrixCallPrimaryTile.screenSharing ? 'object-contain bg-black' : 'object-cover'"
class="relative min-h-52 overflow-hidden rounded-lg bg-inverted text-inverted" autoplay
:class="tile.speaking ? 'ring-2 ring-primary' : ''" playsinline
> :muted="matrixCallPrimaryTile.local"
<video />
v-if="tile.videoTrack" <div
:ref="(element) => setMatrixCallVideoElement(tile.id, element)" v-else
class="h-full w-full" class="flex h-full min-h-[60dvh] items-center justify-center bg-zinc-900"
:class="tile.screenSharing ? 'object-contain bg-black' : 'object-cover'" >
autoplay <span class="flex size-24 items-center justify-center rounded-lg bg-primary/15 text-3xl font-semibold text-primary">
playsinline {{ participantInitial(matrixCallPrimaryTile) }}
:muted="tile.local" </span>
/> </div>
<div class="absolute inset-x-0 bottom-0 flex items-center justify-between bg-black/65 px-4 py-3 text-sm text-white">
<span class="truncate font-medium">
{{ matrixCallPrimaryTile.name }}
<span v-if="matrixCallPrimaryTile.screenSharing" class="ml-1 text-xs text-white/70">teilt Bildschirm</span>
</span>
<div class="flex items-center gap-2 text-white/80">
<UIcon
v-if="matrixCallPrimaryTile.screenSharing"
name="i-heroicons-computer-desktop"
class="size-4"
/>
<UIcon
v-if="!matrixCallPrimaryTile.screenSharing"
:name="matrixCallPrimaryTile.microphoneEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone-slash'"
class="size-4"
/>
<UIcon
v-if="!matrixCallPrimaryTile.screenSharing"
:name="matrixCallPrimaryTile.cameraEnabled ? 'i-heroicons-video-camera' : 'i-heroicons-video-camera-slash'"
class="size-4"
/>
</div>
</div>
</template>
<div <div
v-else v-else
class="flex h-full min-h-52 items-center justify-center bg-elevated text-highlighted" class="flex h-full min-h-[60dvh] items-center justify-center text-sm text-white/60"
> >
<span class="flex size-16 items-center justify-center rounded-lg bg-primary/10 text-xl font-semibold text-primary"> Warte auf Medien...
{{ (tile.name || tile.identity || "?").slice(0, 1).toUpperCase() }}
</span>
</div>
<div class="absolute inset-x-0 bottom-0 flex items-center justify-between bg-black/55 px-3 py-2 text-sm text-white">
<span class="truncate">
{{ tile.name }}
<span v-if="tile.screenSharing" class="ml-1 text-xs opacity-80">teilt Bildschirm</span>
</span>
<div class="flex items-center gap-2">
<UIcon
v-if="tile.screenSharing"
name="i-heroicons-computer-desktop"
class="size-4"
/>
<UIcon
v-if="!tile.screenSharing"
:name="tile.microphoneEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone'"
class="size-4"
:class="tile.microphoneEnabled ? 'opacity-100' : 'opacity-35'"
/>
<UIcon
v-if="!tile.screenSharing"
:name="tile.cameraEnabled ? 'i-heroicons-video-camera' : 'i-heroicons-video-camera-slash'"
class="size-4"
/>
</div>
</div> </div>
</div> </div>
<div
v-if="matrixCallSecondaryTiles.length"
class="grid max-h-48 shrink-0 grid-cols-2 gap-2 overflow-y-auto lg:max-h-none lg:w-72 lg:grid-cols-1"
>
<button
v-for="tile in matrixCallSecondaryTiles"
:key="tile.id"
type="button"
class="relative min-h-28 overflow-hidden rounded-lg bg-zinc-900 text-left"
:class="tile.speaking ? 'ring-2 ring-primary' : ''"
>
<video
v-if="tile.videoTrack"
:ref="(element) => setMatrixCallVideoElement(tile.id, element)"
class="h-full min-h-28 w-full"
:class="tile.screenSharing ? 'object-contain bg-black' : 'object-cover'"
autoplay
playsinline
:muted="tile.local"
/>
<div
v-else
class="flex h-full min-h-28 items-center justify-center"
>
<span class="flex size-12 items-center justify-center rounded-lg bg-primary/15 text-lg font-semibold text-primary">
{{ participantInitial(tile) }}
</span>
</div>
<div class="absolute inset-x-0 bottom-0 flex items-center justify-between bg-black/60 px-2 py-1 text-xs text-white">
<span class="truncate">{{ tile.name }}</span>
<UIcon
:name="tile.screenSharing ? 'i-heroicons-computer-desktop' : (tile.microphoneEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone-slash')"
class="size-3.5 shrink-0"
/>
</div>
</button>
</div>
</div>
<div class="flex shrink-0 flex-wrap items-center justify-center gap-2 border-t border-white/10 bg-black px-4 py-3">
<UButton
:icon="matrixCallMicEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone-slash'"
color="neutral"
:variant="matrixCallMicEnabled ? 'soft' : 'outline'"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallMic"
>
{{ matrixCallMicEnabled ? "Mikro an" : "Mikro aus" }}
</UButton>
<UButton
:icon="matrixCallCameraEnabled ? 'i-heroicons-video-camera' : 'i-heroicons-video-camera-slash'"
color="neutral"
:variant="matrixCallCameraEnabled ? 'soft' : 'outline'"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallCamera"
>
{{ matrixCallCameraEnabled ? "Kamera an" : "Kamera aus" }}
</UButton>
<UButton
:icon="matrixCallScreenShareEnabled ? 'i-heroicons-computer-desktop' : 'i-heroicons-arrow-up-tray'"
color="neutral"
:variant="matrixCallScreenShareEnabled ? 'soft' : 'outline'"
:disabled="!matrixLiveKitRoom"
@click="toggleMatrixCallScreenShare"
>
{{ matrixCallScreenShareEnabled ? "Freigabe beenden" : "Bildschirm teilen" }}
</UButton>
<UButton
icon="i-heroicons-phone-x-mark"
color="error"
variant="solid"
@click="closeMatrixCall"
>
Auflegen
</UButton>
</div> </div>
<div ref="matrixCallAudioContainer" class="hidden" /> <div ref="matrixCallAudioContainer" class="hidden" />
</div> </div>