KI-AGENT: Chat in eigene Kommunikationsseite auslagern
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
const { $api } = useNuxtApp()
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
const status = ref(null)
|
||||
const identity = ref(null)
|
||||
@@ -10,22 +9,11 @@ const generalRoom = ref(null)
|
||||
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
|
||||
let matrixMessagesRequestActive = false
|
||||
let matrixMembersRequestActive = false
|
||||
|
||||
const statusItems = computed(() => [
|
||||
{
|
||||
@@ -60,56 +48,10 @@ const statusItems = computed(() => [
|
||||
}
|
||||
])
|
||||
|
||||
const elementBaseUrl = computed(() =>
|
||||
String(runtimeConfig.public.matrixElementUrl || "http://localhost:8080").replace(/\/+$/, "")
|
||||
)
|
||||
|
||||
const activeMatrixTarget = computed(() => {
|
||||
if (generalRoom.value?.exists) return generalRoom.value
|
||||
if (tenantSpace.value?.exists) return tenantSpace.value
|
||||
return null
|
||||
})
|
||||
|
||||
const activeMatrixTargetId = computed(() =>
|
||||
activeMatrixTarget.value?.roomId || activeMatrixTarget.value?.alias || ""
|
||||
)
|
||||
|
||||
const embeddedElementUrl = computed(() => {
|
||||
if (!activeMatrixTargetId.value) return `${elementBaseUrl.value}/`
|
||||
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 {
|
||||
@@ -126,9 +68,6 @@ const loadMatrixInfo = async () => {
|
||||
generalRoom.value = generalRoomRes
|
||||
lastUpdated.value = new Date()
|
||||
|
||||
if (generalRoomRes?.exists && canUseMatrixChat.value) {
|
||||
await loadGeneralChat({ silent: true, includeMembers: true })
|
||||
}
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Matrix-Status konnte nicht geladen werden",
|
||||
@@ -227,7 +166,6 @@ const provisionGeneralRoom = async () => {
|
||||
color: "success"
|
||||
})
|
||||
|
||||
await loadGeneralChat({ includeMembers: true })
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Allgemeiner Chat konnte nicht erstellt werden",
|
||||
@@ -238,132 +176,6 @@ const provisionGeneralRoom = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadGeneralMessages = async ({ silent = false } = {}) => {
|
||||
if (!canUseMatrixChat.value) return
|
||||
if (matrixMessagesRequestActive) return
|
||||
|
||||
matrixMessagesRequestActive = true
|
||||
if (!silent) matrixMessagesLoading.value = true
|
||||
|
||||
try {
|
||||
const res = await $api("/api/communication/matrix/rooms/general/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) {
|
||||
if (!silent) {
|
||||
toast.add({
|
||||
title: "Matrix-Nachrichten konnten nicht geladen werden",
|
||||
color: "error"
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
matrixMessagesRequestActive = false
|
||||
if (!silent) matrixMessagesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadGeneralMembers = async ({ silent = false } = {}) => {
|
||||
if (!canUseMatrixChat.value) return
|
||||
if (matrixMembersRequestActive) return
|
||||
|
||||
matrixMembersRequestActive = true
|
||||
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 {
|
||||
matrixMembersRequestActive = false
|
||||
if (!silent) matrixMembersLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadGeneralChat = async ({ silent = false, includeMembers = false } = {}) => {
|
||||
await loadGeneralMessages({ silent })
|
||||
|
||||
if (includeMembers) {
|
||||
await 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", {
|
||||
method: "POST",
|
||||
body: { text }
|
||||
})
|
||||
|
||||
matrixMessages.value = matrixMessages.value.map((item) =>
|
||||
item.id === optimisticId ? message : item
|
||||
)
|
||||
} 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"
|
||||
})
|
||||
} finally {
|
||||
matrixMessageSending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const startMatrixAutoRefresh = () => {
|
||||
if (matrixRefreshInterval) return
|
||||
|
||||
matrixAutoRefreshActive.value = true
|
||||
matrixRefreshInterval = window.setInterval(() => {
|
||||
if (!document.hidden && canUseMatrixChat.value) {
|
||||
loadGeneralChat({ silent: true })
|
||||
}
|
||||
}, 15000)
|
||||
}
|
||||
|
||||
const stopMatrixAutoRefresh = () => {
|
||||
if (!matrixRefreshInterval) return
|
||||
|
||||
window.clearInterval(matrixRefreshInterval)
|
||||
matrixRefreshInterval = null
|
||||
matrixAutoRefreshActive.value = false
|
||||
}
|
||||
|
||||
const formatDateTime = (value) => {
|
||||
if (!value) return "-"
|
||||
|
||||
@@ -373,26 +185,9 @@ const formatDateTime = (value) => {
|
||||
}).format(value)
|
||||
}
|
||||
|
||||
const formatMessageTime = (timestamp) => {
|
||||
if (!timestamp) return ""
|
||||
|
||||
return new Intl.DateTimeFormat("de-DE", {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short"
|
||||
}).format(new Date(timestamp))
|
||||
}
|
||||
|
||||
watch(
|
||||
() => matrixMessages.value.length,
|
||||
() => scrollMessagesToBottom()
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadMatrixInfo()
|
||||
startMatrixAutoRefresh()
|
||||
})
|
||||
|
||||
onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -579,7 +374,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-plus"
|
||||
:loading="tenantSpaceProvisioning"
|
||||
@@ -646,7 +441,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-plus"
|
||||
:loading="generalRoomProvisioning"
|
||||
@@ -655,6 +450,15 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
>
|
||||
Allgemeinen Chat erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="generalRoom?.exists"
|
||||
to="/communication/chat"
|
||||
icon="i-heroicons-chat-bubble-left-right"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
>
|
||||
Zum Chat
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
@@ -686,120 +490,6 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<UCard :ui="{ root: 'rounded-lg overflow-hidden', body: 'p-0 sm:p-0' }">
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
:loading="matrixMessagesLoading"
|
||||
:disabled="!canUseMatrixChat"
|
||||
@click="loadGeneralChat"
|
||||
>
|
||||
Nachrichten laden
|
||||
</UButton>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-top-right-on-square"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
:to="embeddedElementUrl"
|
||||
target="_blank"
|
||||
>
|
||||
Element öffnen
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex h-[640px] min-h-[520px] flex-col bg-muted">
|
||||
<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"
|
||||
>
|
||||
Noch keine Nachrichten im allgemeinen Chat.
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="message in matrixMessages"
|
||||
:key="message.id"
|
||||
class="flex"
|
||||
:class="message.own ? 'justify-end' : 'justify-start'"
|
||||
>
|
||||
<div
|
||||
class="max-w-[78%] rounded-lg px-3 py-2 shadow-sm"
|
||||
: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.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 }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="flex gap-2 border-t border-default bg-default p-3"
|
||||
@submit.prevent="sendMatrixMessage"
|
||||
>
|
||||
<UInput
|
||||
v-model="matrixMessageDraft"
|
||||
class="min-w-0 flex-1"
|
||||
placeholder="Nachricht schreiben"
|
||||
:disabled="matrixMessageSending || !canUseMatrixChat"
|
||||
@keydown.enter.exact.prevent="sendMatrixMessage"
|
||||
/>
|
||||
<UButton
|
||||
type="submit"
|
||||
icon="i-heroicons-paper-airplane"
|
||||
:loading="matrixMessageSending"
|
||||
:disabled="!matrixMessageDraft.trim() || !canUseMatrixChat"
|
||||
>
|
||||
Senden
|
||||
</UButton>
|
||||
</form>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user