KI-AGENT: Anhänge im Chat über Matrix unterstützen

This commit is contained in:
2026-05-19 10:51:33 +02:00
parent 7caa37378b
commit 26ffc4421a
3 changed files with 370 additions and 3 deletions

View File

@@ -16,6 +16,7 @@ const matrixMessages = ref([])
const matrixMembers = ref([])
const matrixMessageDraft = ref("")
const matrixMessagesViewport = ref(null)
const matrixAttachmentInput = ref(null)
const roomCreateOpen = ref(false)
const collapsedRoomGroups = ref({})
const matrixCallOpen = ref(false)
@@ -46,6 +47,7 @@ const roomMembersSyncing = ref(false)
const matrixMessagesLoading = ref(false)
const matrixMembersLoading = ref(false)
const matrixMessageSending = ref(false)
const matrixAttachmentUploading = ref(false)
const matrixAutoRefreshActive = ref(false)
const lastUpdated = ref(null)
let matrixRefreshInterval = null
@@ -856,6 +858,67 @@ const sendMatrixMessage = async () => {
}
}
const openAttachmentPicker = () => {
if (!canUseMatrixChat.value || !activeRoom.value?.exists || matrixAttachmentUploading.value) return
matrixAttachmentInput.value?.click()
}
const uploadMatrixAttachment = async (event) => {
const file = event.target.files?.[0]
event.target.value = ""
if (!file || !canUseMatrixChat.value || !activeRoom.value?.exists) return
const optimisticId = `attachment-${Date.now()}`
const optimisticMessage = {
id: optimisticId,
sender: identity.value?.matrixUserId || "Du",
senderDisplayName: identity.value?.displayName || "Du",
body: file.name,
attachment: {
fileName: file.name,
mimeType: file.type || "application/octet-stream",
size: file.size,
isImage: file.type?.startsWith("image/"),
},
timestamp: Date.now(),
own: true,
pending: true,
failed: false
}
matrixMessages.value = [
...matrixMessages.value,
optimisticMessage
]
await scrollMessagesToBottom()
const formData = new FormData()
formData.append("file", file)
matrixAttachmentUploading.value = true
try {
const message = await $api(`${activeRoomEndpoint.value}/attachments`, {
method: "POST",
body: formData
})
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: "Anhang konnte nicht gesendet werden",
color: "error"
})
} finally {
matrixAttachmentUploading.value = false
}
}
const startMatrixAutoRefresh = () => {
if (matrixRefreshInterval) return
@@ -885,6 +948,25 @@ const formatMessageTime = (timestamp) => {
}).format(new Date(timestamp))
}
const formatAttachmentSize = (size) => {
const bytes = Number(size || 0)
if (!bytes) return ""
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / 1024 / 1024).toFixed(1)} MB`
}
const matrixMediaProxyUrl = (attachment) => {
if (!attachment?.url) return ""
const params = new URLSearchParams({
uri: attachment.url,
name: attachment.fileName || "Anhang"
})
return `/api/communication/matrix/media?${params.toString()}`
}
const formatLastUpdated = computed(() => {
if (!lastUpdated.value) return "Noch nicht aktualisiert"
@@ -1316,6 +1398,45 @@ onBeforeUnmount(() => {
<p class="whitespace-pre-wrap break-words text-sm">
{{ message.body }}
</p>
<div
v-if="message.attachment"
class="mt-2 overflow-hidden rounded-md border"
:class="message.own ? 'border-white/20' : 'border-default'"
>
<img
v-if="message.attachment.isImage && message.attachment.url"
:src="matrixMediaProxyUrl(message.attachment)"
:alt="message.attachment.fileName || message.body"
class="max-h-72 w-full object-contain"
>
<a
v-if="message.attachment.url"
:href="matrixMediaProxyUrl(message.attachment)"
target="_blank"
rel="noopener"
class="flex items-center gap-2 px-3 py-2 text-sm"
>
<UIcon name="i-heroicons-paper-clip" class="size-4 shrink-0" />
<span class="min-w-0 flex-1 truncate">
{{ message.attachment.fileName || message.body }}
</span>
<span class="shrink-0 text-xs opacity-70">
{{ formatAttachmentSize(message.attachment.size) }}
</span>
</a>
<div
v-else
class="flex items-center gap-2 px-3 py-2 text-sm"
>
<UIcon name="i-heroicons-paper-clip" class="size-4 shrink-0" />
<span class="min-w-0 flex-1 truncate">
{{ message.attachment.fileName || message.body }}
</span>
<span class="shrink-0 text-xs opacity-70">
{{ formatAttachmentSize(message.attachment.size) }}
</span>
</div>
</div>
</div>
</div>
</div>
@@ -1324,18 +1445,33 @@ onBeforeUnmount(() => {
class="flex shrink-0 gap-2 border-t border-default bg-default p-3"
@submit.prevent="sendMatrixMessage"
>
<input
ref="matrixAttachmentInput"
type="file"
class="hidden"
@change="uploadMatrixAttachment"
>
<UButton
type="button"
icon="i-heroicons-paper-clip"
color="neutral"
variant="outline"
:loading="matrixAttachmentUploading"
:disabled="matrixAttachmentUploading || !canUseMatrixChat || !activeRoom?.exists"
@click="openAttachmentPicker"
/>
<UInput
v-model="matrixMessageDraft"
class="min-w-0 flex-1"
placeholder="Nachricht schreiben"
:disabled="matrixMessageSending || !canUseMatrixChat || !activeRoom?.exists"
:disabled="matrixMessageSending || matrixAttachmentUploading || !canUseMatrixChat || !activeRoom?.exists"
@keydown.enter.exact.prevent="sendMatrixMessage"
/>
<UButton
type="submit"
icon="i-heroicons-paper-airplane"
:loading="matrixMessageSending"
:disabled="!matrixMessageDraft.trim() || !canUseMatrixChat || !activeRoom?.exists"
:disabled="!matrixMessageDraft.trim() || matrixAttachmentUploading || !canUseMatrixChat || !activeRoom?.exists"
>
Senden
</UButton>