KI-AGENT: Matrix-Chat live aktualisieren

This commit is contained in:
2026-05-18 17:45:56 +02:00
parent 655459a46b
commit 8b40be7909
3 changed files with 217 additions and 27 deletions

View File

@@ -11,14 +11,19 @@ const provisionResult = ref(null)
const tenantSpaceProvisionResult = ref(null)
const generalRoomProvisionResult = ref(null)
const matrixMessages = ref([])
const matrixMembers = ref([])
const matrixMessageDraft = ref("")
const matrixMessagesViewport = ref(null)
const loading = ref(false)
const provisioning = ref(false)
const tenantSpaceProvisioning = ref(false)
const generalRoomProvisioning = ref(false)
const matrixMessagesLoading = ref(false)
const matrixMembersLoading = ref(false)
const matrixMessageSending = ref(false)
const matrixAutoRefreshActive = ref(false)
const lastUpdated = ref(null)
let matrixRefreshInterval = null
const statusItems = computed(() => [
{
@@ -72,6 +77,37 @@ const embeddedElementUrl = computed(() => {
return `${elementBaseUrl.value}/#/room/${encodeURIComponent(activeMatrixTargetId.value)}`
})
const canUseMatrixChat = computed(() =>
Boolean(status.value?.reachable && status.value?.provisioningConfigured)
)
const mergeMatrixMessages = (incomingMessages) => {
const byId = new Map()
for (const message of matrixMessages.value) {
byId.set(message.id, message)
}
for (const message of incomingMessages || []) {
byId.set(message.id, {
...byId.get(message.id),
...message,
pending: false,
failed: false
})
}
matrixMessages.value = Array.from(byId.values())
.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0))
}
const scrollMessagesToBottom = async () => {
await nextTick()
if (!matrixMessagesViewport.value) return
matrixMessagesViewport.value.scrollTop = matrixMessagesViewport.value.scrollHeight
}
const loadMatrixInfo = async () => {
loading.value = true
try {
@@ -88,8 +124,8 @@ const loadMatrixInfo = async () => {
generalRoom.value = generalRoomRes
lastUpdated.value = new Date()
if (generalRoomRes?.exists) {
await loadGeneralMessages()
if (generalRoomRes?.exists && canUseMatrixChat.value) {
await loadGeneralChat({ silent: true })
}
} catch (error) {
toast.add({
@@ -189,7 +225,7 @@ const provisionGeneralRoom = async () => {
color: "success"
})
await loadGeneralMessages()
await loadGeneralChat()
} catch (error) {
toast.add({
title: "Allgemeiner Chat konnte nicht erstellt werden",
@@ -200,31 +236,81 @@ const provisionGeneralRoom = async () => {
}
}
const loadGeneralMessages = async () => {
matrixMessagesLoading.value = true
const loadGeneralMessages = async ({ silent = false } = {}) => {
if (!canUseMatrixChat.value) return
if (!silent) matrixMessagesLoading.value = true
try {
const res = await $api("/api/communication/matrix/rooms/general/messages")
matrixMessages.value = res.messages || []
mergeMatrixMessages(res.messages || [])
generalRoom.value = {
...generalRoom.value,
alias: res.alias || generalRoom.value?.alias,
exists: true,
roomId: res.roomId || generalRoom.value?.roomId
}
await scrollMessagesToBottom()
} catch (error) {
toast.add({
title: "Matrix-Nachrichten konnten nicht geladen werden",
color: "error"
})
if (!silent) {
toast.add({
title: "Matrix-Nachrichten konnten nicht geladen werden",
color: "error"
})
}
} finally {
matrixMessagesLoading.value = false
if (!silent) matrixMessagesLoading.value = false
}
}
const loadGeneralMembers = async ({ silent = false } = {}) => {
if (!canUseMatrixChat.value) return
if (!silent) matrixMembersLoading.value = true
try {
const res = await $api("/api/communication/matrix/rooms/general/members")
matrixMembers.value = res.members || []
} catch (error) {
if (!silent) {
toast.add({
title: "Matrix-Teilnehmer konnten nicht geladen werden",
color: "error"
})
}
} finally {
if (!silent) matrixMembersLoading.value = false
}
}
const loadGeneralChat = async ({ silent = false } = {}) => {
await Promise.all([
loadGeneralMessages({ silent }),
loadGeneralMembers({ silent })
])
}
const sendMatrixMessage = async () => {
const text = matrixMessageDraft.value.trim()
if (!text) return
const optimisticId = `pending-${Date.now()}`
const optimisticMessage = {
id: optimisticId,
sender: identity.value?.matrixUserId || "Du",
senderDisplayName: identity.value?.displayName || "Du",
body: text,
timestamp: Date.now(),
own: true,
pending: true,
failed: false
}
matrixMessages.value = [
...matrixMessages.value,
optimisticMessage
]
matrixMessageDraft.value = ""
await scrollMessagesToBottom()
matrixMessageSending.value = true
try {
const message = await $api("/api/communication/matrix/rooms/general/messages", {
@@ -232,12 +318,14 @@ const sendMatrixMessage = async () => {
body: { text }
})
matrixMessages.value = [
...matrixMessages.value,
message
]
matrixMessageDraft.value = ""
matrixMessages.value = matrixMessages.value.map((item) =>
item.id === optimisticId ? message : item
)
await loadGeneralMembers({ silent: true })
} catch (error) {
matrixMessages.value = matrixMessages.value.map((item) =>
item.id === optimisticId ? { ...item, pending: false, failed: true } : item
)
toast.add({
title: "Matrix-Nachricht konnte nicht gesendet werden",
color: "error"
@@ -247,6 +335,25 @@ const sendMatrixMessage = async () => {
}
}
const startMatrixAutoRefresh = () => {
if (matrixRefreshInterval) return
matrixAutoRefreshActive.value = true
matrixRefreshInterval = window.setInterval(() => {
if (!document.hidden && canUseMatrixChat.value) {
loadGeneralChat({ silent: true })
}
}, 5000)
}
const stopMatrixAutoRefresh = () => {
if (!matrixRefreshInterval) return
window.clearInterval(matrixRefreshInterval)
matrixRefreshInterval = null
matrixAutoRefreshActive.value = false
}
const formatDateTime = (value) => {
if (!value) return "-"
@@ -265,7 +372,17 @@ const formatMessageTime = (timestamp) => {
}).format(new Date(timestamp))
}
onMounted(loadMatrixInfo)
watch(
() => matrixMessages.value.length,
() => scrollMessagesToBottom()
)
onMounted(async () => {
await loadMatrixInfo()
startMatrixAutoRefresh()
})
onBeforeUnmount(stopMatrixAutoRefresh)
</script>
<template>
@@ -566,11 +683,22 @@ onMounted(loadMatrixInfo)
<div class="flex min-w-0 items-center gap-2">
<UIcon name="i-heroicons-chat-bubble-left-right" class="size-5 shrink-0 text-primary" />
<div class="min-w-0">
<h2 class="text-base font-semibold text-highlighted">
Matrix-Kommunikation
</h2>
<div class="flex flex-wrap items-center gap-2">
<h2 class="text-base font-semibold text-highlighted">
Matrix-Kommunikation
</h2>
<UBadge
v-if="matrixAutoRefreshActive"
color="success"
variant="soft"
size="xs"
>
Live
</UBadge>
</div>
<p class="mt-1 truncate text-xs text-muted">
{{ generalRoom?.name || generalRoom?.alias || "Allgemeiner Chat" }}
<span v-if="matrixMembers.length"> · {{ matrixMembers.length }} Teilnehmer</span>
</p>
</div>
</div>
@@ -581,8 +709,8 @@ onMounted(loadMatrixInfo)
color="neutral"
variant="outline"
:loading="matrixMessagesLoading"
:disabled="!status?.reachable || !status?.provisioningConfigured"
@click="loadGeneralMessages"
:disabled="!canUseMatrixChat"
@click="loadGeneralChat"
>
Nachrichten laden
</UButton>
@@ -600,7 +728,10 @@ onMounted(loadMatrixInfo)
</template>
<div class="flex h-[640px] min-h-[520px] flex-col bg-muted">
<div class="flex-1 space-y-3 overflow-y-auto p-4 sm:p-5">
<div
ref="matrixMessagesViewport"
class="flex-1 space-y-3 overflow-y-auto p-4 sm:p-5"
>
<div
v-if="!matrixMessages.length && !matrixMessagesLoading"
class="flex h-full min-h-64 items-center justify-center text-sm text-muted"
@@ -616,13 +747,19 @@ onMounted(loadMatrixInfo)
>
<div
class="max-w-[78%] rounded-lg px-3 py-2 shadow-sm"
:class="message.own ? 'bg-primary text-inverted' : 'bg-default text-highlighted'"
:class="[
message.own ? 'bg-primary text-inverted' : 'bg-default text-highlighted',
message.pending ? 'opacity-70' : '',
message.failed ? 'bg-error text-inverted' : ''
]"
>
<div class="mb-1 flex items-center gap-2 text-[11px] opacity-75">
<span class="truncate font-medium">
{{ message.own ? "Du" : message.sender }}
{{ message.own ? "Du" : message.senderDisplayName || message.sender }}
</span>
<span>{{ formatMessageTime(message.timestamp) }}</span>
<span v-if="message.pending">wird gesendet</span>
<span v-if="message.failed">nicht gesendet</span>
</div>
<p class="whitespace-pre-wrap break-words text-sm">
{{ message.body }}
@@ -639,13 +776,14 @@ onMounted(loadMatrixInfo)
v-model="matrixMessageDraft"
class="min-w-0 flex-1"
placeholder="Nachricht schreiben"
:disabled="matrixMessageSending || !status?.reachable || !status?.provisioningConfigured"
:disabled="matrixMessageSending || !canUseMatrixChat"
@keydown.enter.exact.prevent="sendMatrixMessage"
/>
<UButton
type="submit"
icon="i-heroicons-paper-airplane"
:loading="matrixMessageSending"
:disabled="!matrixMessageDraft.trim() || !status?.reachable || !status?.provisioningConfigured"
:disabled="!matrixMessageDraft.trim() || !canUseMatrixChat"
>
Senden
</UButton>