diff --git a/webui/viewer.html b/webui/viewer.html
index 1dfe75e..c69d597 100644
--- a/webui/viewer.html
+++ b/webui/viewer.html
@@ -4,7 +4,7 @@
@@ -13,6 +13,7 @@
Export Viewer
Messages
+
Account: Loading...
Dashboard
@@ -36,8 +37,17 @@
diff --git a/webui/viewer.js b/webui/viewer.js
index 4e211b0..fe62648 100644
--- a/webui/viewer.js
+++ b/webui/viewer.js
@@ -1,6 +1,8 @@
if ("scrollRestoration" in history) history.scrollRestoration = "manual";
const viewerState = {
+ accountId: null,
+ accounts: [],
channels: [],
channelId: null,
oldestMessageId: null,
@@ -42,6 +44,24 @@ function formatTime(dateStr) {
return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
}
+function chatListTime(dateStr) {
+ if (!dateStr) return "";
+ const d = new Date(dateStr.replace(" ", "T"));
+ if (isNaN(d.getTime())) return "";
+ const now = new Date();
+ if (d.toDateString() === now.toDateString()) {
+ return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
+ }
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
+}
+
+function initials(value) {
+ const source = String(value || "?").replace(/^@/, "").trim();
+ const words = source.split(/\s+/).filter(Boolean);
+ if (words.length > 1) return (words[0][0] + words[1][0]).toUpperCase();
+ return source.slice(0, 2).toUpperCase();
+}
+
function dateKey(dateStr) {
if (!dateStr) return "";
return dateStr.slice(0, 10);
@@ -75,8 +95,14 @@ function renderChannelList() {
if (!template) return;
viewerState.channels.forEach((channel) => {
const node = template.content.firstElementChild.cloneNode(true);
+ const preview =
+ channel.last_message_preview ||
+ (channel.has_database ? "No text in the last saved message" : "No local database yet");
+ node.querySelector(".viewer-channel-avatar").textContent = initials(channel.name || channel.channel_id);
node.querySelector(".viewer-channel-name").textContent = channel.name;
- node.querySelector(".viewer-channel-meta").textContent = `${channel.message_count} messages`;
+ node.querySelector(".viewer-channel-time").textContent = chatListTime(channel.last_date);
+ node.querySelector(".viewer-channel-preview").textContent = preview;
+ node.querySelector(".viewer-channel-count").textContent = String(channel.message_count || 0);
if (channel.channel_id === viewerState.channelId) {
node.classList.add("active");
}
@@ -90,6 +116,50 @@ function renderChannelList() {
});
}
+function messageEndpoint(channelId, before = "") {
+ if (viewerState.accountId) {
+ return `/api/accounts/${encodeURIComponent(viewerState.accountId)}/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`;
+ }
+ return `/api/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`;
+}
+
+function channelsEndpoint() {
+ if (viewerState.accountId) {
+ return `/api/accounts/${encodeURIComponent(viewerState.accountId)}/channels`;
+ }
+ return "/api/channels";
+}
+
+async function loadViewerAccount(params) {
+ const requested = params.get("account");
+ try {
+ const payload = await api("/api/accounts");
+ viewerState.accounts = payload.accounts || [];
+ } catch {
+ viewerState.accounts = [];
+ }
+ viewerState.accountId =
+ requested ||
+ viewerState.accounts[0]?.id ||
+ null;
+
+ const accountName =
+ viewerState.accounts.find((item) => item.id === viewerState.accountId)?.label ||
+ viewerState.accountId ||
+ "Legacy";
+ const accountEl = document.getElementById("viewer-account-name");
+ if (accountEl) accountEl.textContent = accountName;
+
+ try {
+ const authData = viewerState.accountId
+ ? await api(`/api/accounts/${encodeURIComponent(viewerState.accountId)}/auth`)
+ : await api("/api/auth");
+ viewerState.userId = authData.user_id || null;
+ } catch (e) {
+ console.warn("Could not fetch user ID:", e);
+ }
+}
+
function buildMessageNode(message, root) {
const template = document.getElementById("message-template");
const wrap = template.content.firstElementChild.cloneNode(true);
@@ -240,9 +310,7 @@ async function loadChannel(channelId, append = false) {
? `&before=${viewerState.oldestMessageId}`
: "";
try {
- const payload = await api(
- `/api/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`
- );
+ const payload = await api(messageEndpoint(channelId, before));
if (append) {
const list = document.getElementById("messages-list");
const prevScrollHeight = list.scrollHeight;
@@ -285,15 +353,9 @@ function setupInfiniteScroll() {
}
async function main() {
- try {
- const authData = await api("/api/auth");
- viewerState.userId = authData.user_id || null;
- } catch (e) {
- console.warn("Could not fetch user ID:", e);
- }
-
- viewerState.channels = await api("/api/channels");
const params = new URLSearchParams(window.location.search);
+ await loadViewerAccount(params);
+ viewerState.channels = await api(channelsEndpoint());
viewerState.channelId =
params.get("channel") ||
viewerState.channels[0]?.channel_id ||