feat: web UI chat render, panels, presence + analytics

This commit is contained in:
hh committed 2026-05-31 19:41:37 +02:00
1 parent 75425d1bee
commit ed469ba8dd
83 files changed
+6034 -136

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
import { searchMessages } from "$lib/api/endpoints";
import type { SearchHit } from "$lib/api/types";
import { chats } from "$lib/stores/chats.svelte";
const DEBOUNCE_MS = 250;
const MIN_LENGTH = 1;
function createSearch() {
let active = $state(false);
let query = $state("");
let messageHits = $state<SearchHit[]>([]);
let loading = $state(false);
let timer: ReturnType<typeof setTimeout> | null = null;
let seq = 0;
const trimmed = $derived(query.trim());
const chatHits = $derived.by(() => {
const needle = trimmed.toLowerCase();
if (needle.length < MIN_LENGTH) {
return [];
}
return chats.list.filter((chat) =>
(chat.title ?? "").toLowerCase().includes(needle)
);
});
async function run(value: string) {
const current = ++seq;
try {
const hits = await searchMessages(value);
if (current === seq) {
messageHits = hits;
}
} catch {
if (current === seq) {
messageHits = [];
}
} finally {
if (current === seq) {
loading = false;
}
}
}
function schedule() {
if (timer) {
clearTimeout(timer);
}
const value = trimmed;
if (value.length < MIN_LENGTH) {
seq++;
messageHits = [];
loading = false;
return;
}
loading = true;
timer = setTimeout(() => {
run(value).catch(() => undefined);
}, DEBOUNCE_MS);
}
return {
get active() {
return active;
},
get query() {
return query;
},
get trimmed() {
return trimmed;
},
get loading() {
return loading;
},
get messageHits() {
return messageHits;
},
get chatHits() {
return chatHits;
},
open() {
active = true;
},
setQuery(value: string) {
query = value;
schedule();
},
close() {
active = false;
query = "";
messageHits = [];
loading = false;
seq++;
if (timer) {
clearTimeout(timer);
}
},
};
}
export const search = createSearch();