This commit is contained in:
Timothy Jaeryang Baek
2026-07-26 21:08:49 -04:00
parent b45c020f68
commit f867825bf3
4 changed files with 127 additions and 19 deletions
@@ -57,6 +57,7 @@
items={folderOptions.map((folder) => ({ value: folder.id, label: folderName(folder) }))}
placeholder={$i18n.t('Choose folder')}
{align}
{side}
triggerClass="relative h-8 max-w-[11rem] flex items-center gap-1.5 px-2.5 py-1.5 bg-transparent rounded-2xl text-xs font-normal text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
contentClass="w-72 shadow-lg"
maxHeight="18rem"
+3
View File
@@ -952,6 +952,9 @@
if ($chatId && !$temporaryChatEnabled && hasPendingAssistantLeaf()) {
await loadChat();
}
if ($chatId && !$temporaryChatEnabled) {
updateLastReadAt($chatId);
}
}
} else if (type === 'chat:completion') {
chatCompletionEventHandler(data, message, event.chat_id);
@@ -2,7 +2,11 @@
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
const i18n: Writable<any> = getContext('i18n');
type ChatListI18n = {
t: (key: string, options?: Record<string, unknown>) => string;
};
const i18n: Writable<ChatListI18n> = getContext('i18n');
import dayjs from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
@@ -13,11 +17,24 @@
import ChevronRight from '$lib/components/icons/ChevronRight.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import { chatId } from '$lib/stores';
import { chatId, socket } from '$lib/stores';
dayjs.extend(localizedFormat);
export let chats: any[] = [];
type ChatListItem = {
id: string;
title?: string;
updated_at?: number | null;
created_at?: number | null;
last_read_at?: number | null;
active?: boolean;
time_range?: string;
user_id?: string;
owner_name?: string;
[key: string]: unknown;
};
export let chats: ChatListItem[] = [];
export let chatListLoading = false;
export let showOwnerInfo = false;
@@ -26,10 +43,10 @@
export let perPage = 10;
export let orderBy: 'title' | 'updated_at' = 'updated_at';
export let direction: 'asc' | 'desc' = 'desc';
export let onPageChange: Function = () => {};
export let onSort: Function = () => {};
export let onPageChange: (page: number) => void | Promise<void> = () => {};
export let onSort: (key: 'title' | 'updated_at') => void | Promise<void> = () => {};
let chatList: any[] | null = null;
let chatList: ChatListItem[] | null = null;
let totalPages = 1;
let pages: (number | 'ellipsis')[] = [];
@@ -48,6 +65,22 @@
onSort(key);
};
const markChatRead = (chat: ChatListItem, unread: boolean) => {
if (!unread) {
return;
}
const lastReadAt = Date.now() / 1000;
chatList = (chatList ?? []).map((item) =>
item.id === chat.id ? { ...item, last_read_at: lastReadAt } : item
);
$socket?.emit('events:chat', {
chat_id: chat.id,
data: { type: 'last_read_at' }
});
};
const buildPages = (currentPage: number, pageCount: number): (number | 'ellipsis')[] => {
if (pageCount <= 7) {
return Array.from({ length: pageCount }, (_, i) => i + 1);
@@ -177,6 +210,7 @@
class=" w-full flex justify-between items-center rounded-lg text-sm py-2 px-3 hover:bg-gray-50 dark:hover:bg-gray-850"
draggable="false"
href={`/c/${chat.id}`}
on:click={() => markChatRead(chat, unread)}
>
<div class="flex min-w-0 items-center w-full sm:basis-3/5">
{#if chat.active}
@@ -1,19 +1,27 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import type { Writable } from 'svelte/store';
import { onMount } from 'svelte';
const i18n: Writable<any> = getContext('i18n');
import { user } from '$lib/stores';
import { fade } from 'svelte/transition';
import { socket, user } from '$lib/stores';
import ChatList from './ChatList.svelte';
import FolderKnowledge from './FolderKnowledge.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import { getSharedFolderChats } from '$lib/apis/folders';
export let folder: any = null;
type FolderPlaceholderFolder = {
id?: string;
shared?: boolean;
user_id?: string;
access_grants?: unknown[];
};
type FolderChat = {
id: string;
active?: boolean;
[key: string]: unknown;
};
export let folder: FolderPlaceholderFolder | null = null;
let selectedTab = 'chats';
@@ -24,8 +32,9 @@
let direction: 'asc' | 'desc' = 'desc';
let currentFolderId: string | null = null;
let chats: any[] | null = null;
let chats: FolderChat[] | null = null;
let chatListLoading = false;
let refreshQueued = false;
$: showOwnerInfo = Boolean(
folder?.shared ||
@@ -53,9 +62,44 @@
setChatList();
};
const setChatList = async () => {
const updateChatActive = (chatId: string, active: boolean) => {
if (!chats) {
return false;
}
let found = false;
chats = chats.map((chat) => {
if (chat.id !== chatId) {
return chat;
}
found = true;
return { ...chat, active };
});
return found;
};
const refreshChatListSoon = (resetPage = false) => {
if (refreshQueued) {
if (resetPage) {
page = 1;
}
return;
}
if (resetPage) {
page = 1;
}
refreshQueued = true;
queueMicrotask(async () => {
refreshQueued = false;
await setChatList();
});
};
const setChatList = async (clear = false) => {
const folderId = folder?.id;
chats = null;
if (clear) {
chats = null;
}
if (folderId) {
// Always use the shared folder endpoint so owners also see
@@ -69,7 +113,6 @@
console.error(error);
return null;
});
chatListLoading = false;
if (res && res.chats) {
chats = res.chats;
@@ -78,16 +121,43 @@
chats = [];
totalChats = 0;
}
chatListLoading = false;
} else {
chats = [];
totalChats = 0;
chatListLoading = false;
}
};
const chatEventHandler = (event: {
chat_id?: string;
data?: { type?: string; data?: { active?: boolean } };
}) => {
if (event.data?.type === 'chat:active' && event.chat_id) {
const active = event.data.data?.active ?? false;
if (!updateChatActive(event.chat_id, active) && active) {
refreshChatListSoon(true);
}
} else if (event.data?.type === 'chat:list') {
refreshChatListSoon(true);
}
};
onMount(() => {
const socketInstance = $socket;
socketInstance?.on('events', chatEventHandler);
socketInstance?.on('connect', refreshChatListSoon);
return () => {
socketInstance?.off('events', chatEventHandler);
socketInstance?.off('connect', refreshChatListSoon);
};
});
$: if (folder?.id && folder.id !== currentFolderId) {
currentFolderId = folder.id;
page = 1;
setChatList();
setChatList(true);
}
$: if (!folder?.id && currentFolderId !== null) {