KI-AGENT: Chaträume im Frontend verwalten
This commit is contained in:
@@ -10,8 +10,15 @@ const matrixMessages = ref([])
|
|||||||
const matrixMembers = ref([])
|
const matrixMembers = ref([])
|
||||||
const matrixMessageDraft = ref("")
|
const matrixMessageDraft = ref("")
|
||||||
const matrixMessagesViewport = ref(null)
|
const matrixMessagesViewport = ref(null)
|
||||||
|
const roomCreateOpen = ref(false)
|
||||||
|
const roomCreateForm = ref({
|
||||||
|
name: "",
|
||||||
|
key: "",
|
||||||
|
topic: ""
|
||||||
|
})
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const roomProvisioning = ref(false)
|
const roomProvisioning = ref(false)
|
||||||
|
const roomCreating = ref(false)
|
||||||
const matrixMessagesLoading = ref(false)
|
const matrixMessagesLoading = ref(false)
|
||||||
const matrixMembersLoading = ref(false)
|
const matrixMembersLoading = ref(false)
|
||||||
const matrixMessageSending = ref(false)
|
const matrixMessageSending = ref(false)
|
||||||
@@ -40,6 +47,10 @@ const activeRoomEndpoint = computed(() =>
|
|||||||
`/api/communication/matrix/rooms/${encodeURIComponent(activeRoomKey.value)}`
|
`/api/communication/matrix/rooms/${encodeURIComponent(activeRoomKey.value)}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const roomCreateKeyPreview = computed(() =>
|
||||||
|
normalizeRoomKey(roomCreateForm.value.key || roomCreateForm.value.name)
|
||||||
|
)
|
||||||
|
|
||||||
const rooms = computed(() => [
|
const rooms = computed(() => [
|
||||||
...matrixRooms.value.map((room) => ({
|
...matrixRooms.value.map((room) => ({
|
||||||
...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) => {
|
const setActiveRoom = async (room) => {
|
||||||
if (room.disabled || room.key === activeRoomKey.value) return
|
if (room.disabled || room.key === activeRoomKey.value) return
|
||||||
|
|
||||||
@@ -70,6 +97,13 @@ const setActiveRoom = async (room) => {
|
|||||||
await loadRoomChat({ includeMembers: true })
|
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 mergeMatrixMessages = (incomingMessages) => {
|
||||||
const byId = new Map()
|
const byId = new Map()
|
||||||
|
|
||||||
@@ -131,10 +165,7 @@ const provisionActiveRoom = async () => {
|
|||||||
method: "POST"
|
method: "POST"
|
||||||
})
|
})
|
||||||
|
|
||||||
const roomWasKnown = matrixRooms.value.some((room) => room.key === res.key)
|
upsertRoom({ ...res, exists: true })
|
||||||
matrixRooms.value = roomWasKnown
|
|
||||||
? matrixRooms.value.map((room) => room.key === res.key ? { ...room, ...res, exists: true } : room)
|
|
||||||
: [...matrixRooms.value, { ...res, exists: true }]
|
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
title: res.alreadyExisted ? "Chatraum ist bereit" : "Chatraum erstellt",
|
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 } = {}) => {
|
const loadRoomMessages = async ({ silent = false } = {}) => {
|
||||||
if (!canUseMatrixChat.value || !activeRoom.value?.exists) return
|
if (!canUseMatrixChat.value || !activeRoom.value?.exists) return
|
||||||
if (matrixMessagesRequestActive) return
|
if (matrixMessagesRequestActive) return
|
||||||
@@ -333,13 +416,74 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="flex-1 overflow-y-auto p-3">
|
||||||
<button
|
<button
|
||||||
v-for="room in rooms"
|
v-for="room in rooms"
|
||||||
:key="room.key"
|
:key="room.key"
|
||||||
type="button"
|
type="button"
|
||||||
class="mb-1 flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition"
|
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"
|
:disabled="room.disabled"
|
||||||
@click="setActiveRoom(room)"
|
@click="setActiveRoom(room)"
|
||||||
>
|
>
|
||||||
@@ -358,6 +502,14 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
>
|
>
|
||||||
aktiv
|
aktiv
|
||||||
</UBadge>
|
</UBadge>
|
||||||
|
<UBadge
|
||||||
|
v-else-if="!room.disabled"
|
||||||
|
color="neutral"
|
||||||
|
variant="soft"
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
offen
|
||||||
|
</UBadge>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -404,6 +556,14 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
/>
|
/>
|
||||||
|
<UButton
|
||||||
|
class="lg:hidden"
|
||||||
|
icon="i-heroicons-plus"
|
||||||
|
color="primary"
|
||||||
|
variant="soft"
|
||||||
|
:disabled="!canUseMatrixChat"
|
||||||
|
@click="roomCreateOpen = !roomCreateOpen"
|
||||||
|
/>
|
||||||
<UButton
|
<UButton
|
||||||
icon="i-heroicons-arrow-path"
|
icon="i-heroicons-arrow-path"
|
||||||
color="neutral"
|
color="neutral"
|
||||||
@@ -417,6 +577,67 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</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
|
<div
|
||||||
v-if="status && (!status.reachable || !status.provisioningConfigured)"
|
v-if="status && (!status.reachable || !status.provisioningConfigured)"
|
||||||
class="border-b border-default bg-default px-4 py-3"
|
class="border-b border-default bg-default px-4 py-3"
|
||||||
@@ -438,16 +659,16 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
icon="i-heroicons-chat-bubble-left-right"
|
icon="i-heroicons-chat-bubble-left-right"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
title="Allgemeiner Chat ist noch nicht eingerichtet"
|
:title="`${activeRoom.name} ist noch nicht in Matrix angelegt`"
|
||||||
description="Lege den mandantenweiten Startraum an, um FEDEO als Chatplattform zu nutzen."
|
description="Die Raum-Metadaten sind in FEDEO vorhanden. Lege jetzt den Matrix-Raum dazu an."
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<UButton
|
<UButton
|
||||||
icon="i-heroicons-plus"
|
icon="i-heroicons-plus"
|
||||||
:loading="roomProvisioning"
|
:loading="roomProvisioning"
|
||||||
@click="provisionActiveRoom"
|
@click="provisionActiveRoom"
|
||||||
>
|
>
|
||||||
Chat erstellen
|
Matrix-Raum erstellen
|
||||||
</UButton>
|
</UButton>
|
||||||
</template>
|
</template>
|
||||||
</UAlert>
|
</UAlert>
|
||||||
@@ -461,7 +682,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
v-if="!matrixMessages.length && !matrixMessagesLoading"
|
v-if="!matrixMessages.length && !matrixMessagesLoading"
|
||||||
class="flex h-full min-h-64 items-center justify-center text-sm text-muted"
|
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>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user