KI-AGENT: Chaträume im Frontend verwalten
This commit is contained in:
@@ -10,8 +10,15 @@ const matrixMessages = ref([])
|
||||
const matrixMembers = ref([])
|
||||
const matrixMessageDraft = ref("")
|
||||
const matrixMessagesViewport = ref(null)
|
||||
const roomCreateOpen = ref(false)
|
||||
const roomCreateForm = ref({
|
||||
name: "",
|
||||
key: "",
|
||||
topic: ""
|
||||
})
|
||||
const loading = ref(false)
|
||||
const roomProvisioning = ref(false)
|
||||
const roomCreating = ref(false)
|
||||
const matrixMessagesLoading = ref(false)
|
||||
const matrixMembersLoading = ref(false)
|
||||
const matrixMessageSending = ref(false)
|
||||
@@ -40,6 +47,10 @@ const activeRoomEndpoint = computed(() =>
|
||||
`/api/communication/matrix/rooms/${encodeURIComponent(activeRoomKey.value)}`
|
||||
)
|
||||
|
||||
const roomCreateKeyPreview = computed(() =>
|
||||
normalizeRoomKey(roomCreateForm.value.key || roomCreateForm.value.name)
|
||||
)
|
||||
|
||||
const rooms = computed(() => [
|
||||
...matrixRooms.value.map((room) => ({
|
||||
...room,
|
||||
@@ -61,6 +72,22 @@ const rooms = computed(() => [
|
||||
}
|
||||
])
|
||||
|
||||
const normalizeRoomKey = (value) => {
|
||||
const normalized = String(value || "")
|
||||
.toLowerCase()
|
||||
.normalize("NFKD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.replace(/ä/g, "a")
|
||||
.replace(/ö/g, "o")
|
||||
.replace(/ü/g, "u")
|
||||
.replace(/ß/g, "ss")
|
||||
.replace(/[^a-z0-9._=-]+/g, "_")
|
||||
.replace(/_+/g, "_")
|
||||
.replace(/^[._=-]+|[._=-]+$/g, "")
|
||||
|
||||
return normalized || "raum"
|
||||
}
|
||||
|
||||
const setActiveRoom = async (room) => {
|
||||
if (room.disabled || room.key === activeRoomKey.value) return
|
||||
|
||||
@@ -70,6 +97,13 @@ const setActiveRoom = async (room) => {
|
||||
await loadRoomChat({ includeMembers: true })
|
||||
}
|
||||
|
||||
const upsertRoom = (room) => {
|
||||
const roomWasKnown = matrixRooms.value.some((item) => item.key === room.key)
|
||||
matrixRooms.value = roomWasKnown
|
||||
? matrixRooms.value.map((item) => item.key === room.key ? { ...item, ...room } : item)
|
||||
: [...matrixRooms.value, room]
|
||||
}
|
||||
|
||||
const mergeMatrixMessages = (incomingMessages) => {
|
||||
const byId = new Map()
|
||||
|
||||
@@ -131,10 +165,7 @@ const provisionActiveRoom = async () => {
|
||||
method: "POST"
|
||||
})
|
||||
|
||||
const roomWasKnown = matrixRooms.value.some((room) => room.key === res.key)
|
||||
matrixRooms.value = roomWasKnown
|
||||
? matrixRooms.value.map((room) => room.key === res.key ? { ...room, ...res, exists: true } : room)
|
||||
: [...matrixRooms.value, { ...res, exists: true }]
|
||||
upsertRoom({ ...res, exists: true })
|
||||
|
||||
toast.add({
|
||||
title: res.alreadyExisted ? "Chatraum ist bereit" : "Chatraum erstellt",
|
||||
@@ -152,6 +183,58 @@ const provisionActiveRoom = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const createRoom = async () => {
|
||||
const name = roomCreateForm.value.name.trim()
|
||||
const key = roomCreateKeyPreview.value
|
||||
const topic = roomCreateForm.value.topic.trim()
|
||||
|
||||
if (!name) {
|
||||
toast.add({
|
||||
title: "Bitte gib einen Raumnamen ein",
|
||||
color: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
roomCreating.value = true
|
||||
try {
|
||||
const room = await $api("/api/communication/matrix/rooms", {
|
||||
method: "POST",
|
||||
body: {
|
||||
key,
|
||||
name,
|
||||
topic: topic || undefined,
|
||||
type: "room"
|
||||
}
|
||||
})
|
||||
|
||||
upsertRoom({ ...room, exists: true })
|
||||
activeRoomKey.value = room.key
|
||||
matrixMessages.value = []
|
||||
matrixMembers.value = []
|
||||
roomCreateForm.value = {
|
||||
name: "",
|
||||
key: "",
|
||||
topic: ""
|
||||
}
|
||||
roomCreateOpen.value = false
|
||||
|
||||
toast.add({
|
||||
title: room.alreadyExisted ? "Chatraum ist bereit" : "Chatraum erstellt",
|
||||
color: "success"
|
||||
})
|
||||
|
||||
await loadRoomChat({ includeMembers: true })
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Chatraum konnte nicht erstellt werden",
|
||||
color: "error"
|
||||
})
|
||||
} finally {
|
||||
roomCreating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadRoomMessages = async ({ silent = false } = {}) => {
|
||||
if (!canUseMatrixChat.value || !activeRoom.value?.exists) return
|
||||
if (matrixMessagesRequestActive) return
|
||||
@@ -333,13 +416,74 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-b border-default p-3">
|
||||
<UButton
|
||||
icon="i-heroicons-plus"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
block
|
||||
:disabled="!canUseMatrixChat"
|
||||
@click="roomCreateOpen = !roomCreateOpen"
|
||||
>
|
||||
Raum erstellen
|
||||
</UButton>
|
||||
|
||||
<form
|
||||
v-if="roomCreateOpen"
|
||||
class="mt-3 space-y-3 rounded-md border border-default bg-muted p-3"
|
||||
@submit.prevent="createRoom"
|
||||
>
|
||||
<UInput
|
||||
v-model="roomCreateForm.name"
|
||||
placeholder="Raumname"
|
||||
:disabled="roomCreating"
|
||||
/>
|
||||
<UInput
|
||||
v-model="roomCreateForm.key"
|
||||
placeholder="Raum-Key optional"
|
||||
:disabled="roomCreating"
|
||||
/>
|
||||
<UTextarea
|
||||
v-model="roomCreateForm.topic"
|
||||
placeholder="Thema optional"
|
||||
:rows="2"
|
||||
:disabled="roomCreating"
|
||||
/>
|
||||
<p class="truncate text-xs text-muted">
|
||||
Key: {{ roomCreateKeyPreview }}
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<UButton
|
||||
type="button"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
:disabled="roomCreating"
|
||||
@click="roomCreateOpen = false"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
type="submit"
|
||||
icon="i-heroicons-check"
|
||||
:loading="roomCreating"
|
||||
:disabled="!roomCreateForm.name.trim()"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-3">
|
||||
<button
|
||||
v-for="room in rooms"
|
||||
:key="room.key"
|
||||
type="button"
|
||||
class="mb-1 flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition"
|
||||
:class="room.disabled ? 'cursor-not-allowed opacity-50' : 'bg-muted text-highlighted'"
|
||||
:class="[
|
||||
room.disabled ? 'cursor-not-allowed opacity-50' : 'text-highlighted hover:bg-muted',
|
||||
room.key === activeRoomKey ? 'bg-muted ring-1 ring-primary/20' : ''
|
||||
]"
|
||||
:disabled="room.disabled"
|
||||
@click="setActiveRoom(room)"
|
||||
>
|
||||
@@ -358,6 +502,14 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
>
|
||||
aktiv
|
||||
</UBadge>
|
||||
<UBadge
|
||||
v-else-if="!room.disabled"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
size="xs"
|
||||
>
|
||||
offen
|
||||
</UBadge>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -404,6 +556,14 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
/>
|
||||
<UButton
|
||||
class="lg:hidden"
|
||||
icon="i-heroicons-plus"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
:disabled="!canUseMatrixChat"
|
||||
@click="roomCreateOpen = !roomCreateOpen"
|
||||
/>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
color="neutral"
|
||||
@@ -417,6 +577,67 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="border-b border-default bg-default p-3 lg:hidden">
|
||||
<div class="flex gap-2 overflow-x-auto pb-1">
|
||||
<button
|
||||
v-for="room in rooms"
|
||||
:key="room.key"
|
||||
type="button"
|
||||
class="shrink-0 rounded-md border border-default px-3 py-2 text-left text-sm"
|
||||
:class="room.key === activeRoomKey ? 'bg-muted text-highlighted' : 'text-muted'"
|
||||
:disabled="room.disabled"
|
||||
@click="setActiveRoom(room)"
|
||||
>
|
||||
{{ room.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
v-if="roomCreateOpen"
|
||||
class="mt-3 grid gap-2 rounded-md border border-default bg-muted p-3"
|
||||
@submit.prevent="createRoom"
|
||||
>
|
||||
<UInput
|
||||
v-model="roomCreateForm.name"
|
||||
placeholder="Raumname"
|
||||
:disabled="roomCreating"
|
||||
/>
|
||||
<UInput
|
||||
v-model="roomCreateForm.key"
|
||||
placeholder="Raum-Key optional"
|
||||
:disabled="roomCreating"
|
||||
/>
|
||||
<UTextarea
|
||||
v-model="roomCreateForm.topic"
|
||||
placeholder="Thema optional"
|
||||
:rows="2"
|
||||
:disabled="roomCreating"
|
||||
/>
|
||||
<p class="truncate text-xs text-muted">
|
||||
Key: {{ roomCreateKeyPreview }}
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<UButton
|
||||
type="button"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
:disabled="roomCreating"
|
||||
@click="roomCreateOpen = false"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
type="submit"
|
||||
icon="i-heroicons-check"
|
||||
:loading="roomCreating"
|
||||
:disabled="!roomCreateForm.name.trim()"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="status && (!status.reachable || !status.provisioningConfigured)"
|
||||
class="border-b border-default bg-default px-4 py-3"
|
||||
@@ -438,16 +659,16 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
icon="i-heroicons-chat-bubble-left-right"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
title="Allgemeiner Chat ist noch nicht eingerichtet"
|
||||
description="Lege den mandantenweiten Startraum an, um FEDEO als Chatplattform zu nutzen."
|
||||
:title="`${activeRoom.name} ist noch nicht in Matrix angelegt`"
|
||||
description="Die Raum-Metadaten sind in FEDEO vorhanden. Lege jetzt den Matrix-Raum dazu an."
|
||||
>
|
||||
<template #actions>
|
||||
<UButton
|
||||
icon="i-heroicons-plus"
|
||||
:loading="roomProvisioning"
|
||||
@click="provisionActiveRoom"
|
||||
@click="provisionActiveRoom"
|
||||
>
|
||||
Chat erstellen
|
||||
Matrix-Raum erstellen
|
||||
</UButton>
|
||||
</template>
|
||||
</UAlert>
|
||||
@@ -461,7 +682,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
||||
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.
|
||||
Noch keine Nachrichten in diesem Chat.
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user