improve chat ui
All checks were successful
Build Container / Build Container (push) Successful in 1m29s

This commit is contained in:
kritzl 2026-05-18 16:49:24 +02:00
commit 81923c4f8c
Signed by: kritzl
SSH key fingerprint: SHA256:5BmINP9VjZWaUk5Z+2CTut1KFhwLtd0ZynMekKbtViM
7 changed files with 170 additions and 48 deletions

View file

@ -1,4 +1,5 @@
import {createIcons, Send, Stars} from "lucide"
import {Bot, createIcons, UserRound, Send, Stars} from "lucide"
import {useTranslations} from "../i18n/utils.ts"
import removeDiacritics from "./removeDiacritics.ts"
export type ResponseDictType = {
@ -16,6 +17,8 @@ const doors = window.doors
const auth = window.auth
const doorAction = window.doorAction
const t = useTranslations(lang)
const responses =
lang === "de"
? (await import("./chatDict_de")).default
@ -25,19 +28,51 @@ const responses =
const sortedResponses = responses.sort(
(a, b) => b.priority - a.priority)
const chatAvatar = `
<div class="chat-image avatar">
<div class="w-10 rounded-full ${lang === 'uwu' ? 'bg-transparent' : 'bg-primary'} flex items-center justify-center">
${lang === 'uwu' ? '<span class="text-4xl">🥺</span>' : '<i data-lucide="bot"></i>'}
</div>
</div>
`
const chatHeader = `
<div class="chat-header">
${t("chat.dooris")}
</div>
`
const chatAvatarUser = `
<div class="chat-image avatar">
<div class="w-10 rounded-full ${lang === 'uwu' ? 'bg-transparent' : 'bg-secondary'} flex items-center justify-center">
${lang === 'uwu' ? '<span class="text-4xl">🥺</span>' : '<i data-lucide="user-round"></i>'}
</div>
</div>
`
const chatHeaderUser = `
<div class="chat-header">
${t("chat.user")}
</div>
`
const chatWait = `
<div class="chat chat-start w-full prose" data-waiting>
${chatAvatar}
${chatHeader}
<div class="chat-bubble chat-bubble-primary whitespace-normal min-h-10">
<span class="loading loading-dots loading-md"></span>
</div>
</div>
`
const templateInjectChat = `
<details class="dropdown dropdown-top dropdown-end absolute right-4 bottom-4" id="chat-root">
<summary class="btn btn-lg btn-circle btn-primary">
<details class="dropdown dropdown-top dropdown-end absolute right-2 bottom-2" id="chat-root">
<summary class="btn btn-lg btn-circle btn-primary me-2 mb-2">
<i data-lucide="stars"></i>
</summary>
<div class="dropdown-content card min-h-120 max-h-200 w-80 bg-base-300 p-4 mb-4">
<div class="w-full overflow-auto h-full prose leading-5" id="chat"></div>
<div class="chat chat-start w-full hidden" id="chat-waiting">
<div class="chat-bubble chat-bubble-primary whitespace-normal">
<span class="loading loading-dots loading-md"></span>
</div>
</div>
<div class="mt-4"></div>
<div class="dropdown-content card h-140 w-90 bg-base-300 p-4 mb-4" style="max-height: min(calc(100vh - 10rem), 50rem); max-width: calc(100vw - 1rem)">
<div class="w-full overflow-y-auto h-full prose leading-5 grow pb-4" id="chat"></div>
<div class="join mt-auto w-full">
<div class="w-full">
<label class="input join-item">
@ -79,11 +114,14 @@ function refreshChat() {
const chatContainer = document.createElement("div")
const chatBubble = document.createElement("div")
chatBubble.classList.add("chat-bubble", "chat-bubble-primary", "whitespace-normal")
chatBubble.classList.add("chat-bubble", "chat-bubble-primary", "whitespace-normal", "min-h-10", "break-[break-word]")
chatBubble.style.wordBreak = "break-word"
chatBubble.classList.toggle("shimmer", special)
chatBubble.id = id
chatBubble.dataset.content = message
chatContainer.classList.add("chat", "chat-start", "w-full")
chatContainer.classList.add("chat", "chat-start", "w-full", "overflow-visible", "hidden", "pe-10")
chatContainer.id = id
chatContainer.insertAdjacentHTML("afterbegin", chatAvatar)
chatContainer.insertAdjacentHTML("afterbegin", chatHeader)
chatContainer.append(chatBubble)
return chatContainer
@ -91,10 +129,16 @@ function refreshChat() {
const templateUser = (message: string) => `
<div class="chat chat-end w-full">
<div class="chat-bubble chat-bubble-secondary whitespace-normal">${message}</div>
<div class="chat chat-end w-full ps-10">
${chatAvatarUser}
${chatHeaderUser}
<div class="chat-bubble chat-bubble-secondary whitespace-normal" style="word-break: break-word;">${message}</div>
</div>`
const chatBubbleIds: Array<string> = []
console.log(chat)
chat.forEach(chatItem => {
switch (chatItem.type) {
case "bot":
@ -102,8 +146,7 @@ function refreshChat() {
chatItem.id = `chat-${chatId}`
chatId++
chatElement?.appendChild(templateBot(chatItem.id, chatItem.message, chatItem.special ?? false))
typeBubble(chatItem.id, () => {
})
chatBubbleIds.push(chatItem.id)
}
break
case "user":
@ -115,16 +158,35 @@ function refreshChat() {
break
}
})
chatWaitElement.classList.toggle("hidden", !chatWaiting.value)
typeBubble(chatBubbleIds)
chatElement.querySelector("[data-waiting]")?.remove()
if (chatWaiting.value) {
chatElement.insertAdjacentHTML("beforeend", chatWait)
}
chatElement.scrollTo({
top: chatElement.scrollHeight,
})
refreshIcons()
}
function typeBubble(id: string, callback: Function) {
const element = document.getElementById(id)!
function typeBubble(ids: Array<string>) {
if (!ids.length) return
console.log(ids)
const firstId = ids.slice(0, 1)[0]
const restIds = ids.slice(1)!
const chatContainer = document.getElementById(firstId)!
chatContainer.classList.remove('hidden')
const chatBubble: HTMLDivElement = chatContainer.querySelector('.chat-bubble')!
const tmpDiv = document.createElement("div")
tmpDiv.innerHTML = element.getAttribute("data-content") || ""
tmpDiv.innerHTML = chatBubble.getAttribute("data-content") || ""
const typingArray: Array<string> = []
@ -144,14 +206,14 @@ function typeBubble(id: string, callback: Function) {
function typeChar() {
if (charIndex < typingArray.length) {
text += typingArray.at(charIndex)
element.innerHTML = text
chatBubble.innerHTML = text
charIndex++
setTimeout(typeChar, typingSpeed)
chatElement.scrollTo({
top: chatElement.scrollHeight,
})
} else if (callback) {
callback()
} else if (restIds.length > 0) {
typeBubble(restIds)
}
}
@ -320,7 +382,7 @@ function getResponse(message: string) {
return {
type: "bot" as ChatMessage["type"],
message: response.message,
special: response.special
special: response.special,
}
})
@ -335,7 +397,6 @@ function getResponse(message: string) {
document.querySelector("body")?.insertAdjacentHTML("beforeend", templateInjectChat)
const chatElement: HTMLDivElement = document.querySelector("#chat")!
const chatWaitElement: HTMLDivElement = document.querySelector("#chat-waiting")!
function initChat() {
chat.push({
@ -347,14 +408,20 @@ function initChat() {
let firstOpen = true
createIcons({
nameAttr: "data-lucide",
root: document.querySelector("#chat-root")!,
icons: {
Stars,
Send,
},
})
function refreshIcons() {
createIcons({
nameAttr: "data-lucide",
root: document.querySelector("#chat-root")!,
icons: {
Stars,
Send,
UserRound,
Bot,
},
})
}
refreshIcons()
document.querySelector("#send-chat")!.addEventListener("click", sendMessage)
document.querySelector("#chat-text")!.addEventListener("keydown", (e: Event) => {