refactor(style): update chat layout and scrolling
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
} from '../client';
|
||||
import { Chat, Message, MessageChunk, Model, Settings } from '../types';
|
||||
import { applyFilter } from '../utils';
|
||||
import { createAutoScroll, AutoScroll } from '../utils/autoScroll';
|
||||
|
||||
const CHAT_ROUTE = '#/chats';
|
||||
const MODEL_KEY = 'aethera-chat-model';
|
||||
@@ -44,11 +45,14 @@ Alpine.data('chatManager', () => ({
|
||||
error: '',
|
||||
|
||||
selectedChatID: null as string | null,
|
||||
chatListOpen: typeof window !== 'undefined' && window.matchMedia('(min-width: 768px)').matches,
|
||||
loading: false,
|
||||
activeStreamChatID: null as string | null,
|
||||
|
||||
_autoScroll: null as AutoScroll | null,
|
||||
|
||||
async init() {
|
||||
this._autoScroll = createAutoScroll();
|
||||
|
||||
// Acquire Data
|
||||
this._models = await getModels();
|
||||
this.settings = await getSettings();
|
||||
@@ -58,6 +62,7 @@ Alpine.data('chatManager', () => ({
|
||||
// Route Chat
|
||||
const chatID = window.location.hash.split('/')[2];
|
||||
if (chatID) await this.selectChat(chatID);
|
||||
this._autoScroll.scrollToBottom();
|
||||
},
|
||||
|
||||
async loadChats() {
|
||||
@@ -82,7 +87,6 @@ Alpine.data('chatManager', () => ({
|
||||
if (this.selectedChatID == chatId) {
|
||||
const newIndex = Math.min(chatIndex, this.chats.length - 1);
|
||||
this.selectChat(this.chats[newIndex]?.id);
|
||||
if (!this.selectedChatID) this.chatListOpen = false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error deleting conversation:', err);
|
||||
@@ -144,6 +148,7 @@ Alpine.data('chatManager', () => ({
|
||||
created_at: new Date().toISOString(),
|
||||
});
|
||||
currentChat.message_count += 1;
|
||||
this._autoScroll?.scrollToBottom('smooth');
|
||||
|
||||
try {
|
||||
await sendMessage(
|
||||
@@ -192,6 +197,8 @@ Alpine.data('chatManager', () => ({
|
||||
if (chunk.user_message) this.upsertMessage(currentChat, chunk.user_message);
|
||||
if (chunk.assistant_message)
|
||||
this.upsertMessage(currentChat, chunk.assistant_message);
|
||||
|
||||
this._autoScroll?.maybeScrollToBottom();
|
||||
},
|
||||
|
||||
upsertMessage(chat: Chat, message: Message) {
|
||||
@@ -222,8 +229,10 @@ Alpine.data('chatManager', () => ({
|
||||
|
||||
// Load Messages
|
||||
this.selectedChatID = chatID;
|
||||
if (!this.selectedChatID) this.chatListOpen = false;
|
||||
else this.loadChatMessages();
|
||||
if (this.selectedChatID) {
|
||||
await this.loadChatMessages();
|
||||
this._autoScroll?.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
async loadChatMessages() {
|
||||
@@ -285,7 +294,11 @@ Alpine.data('chatManager', () => ({
|
||||
const currentChat =
|
||||
this.chats.find((c) => c.id === this.selectedChatID) ?? null;
|
||||
if (!currentChat) return [];
|
||||
return [...currentChat.messages].reverse();
|
||||
return currentChat.messages;
|
||||
},
|
||||
|
||||
get chatGroups(): { label: string; chats: Chat[] }[] {
|
||||
return groupChatsByDay(this.chats);
|
||||
},
|
||||
|
||||
renderMarkdown(content: string) {
|
||||
@@ -293,6 +306,47 @@ Alpine.data('chatManager', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
function startOfDay(d: Date): number {
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
||||
}
|
||||
|
||||
function groupChatsByDay(
|
||||
chats: Chat[],
|
||||
): { label: string; chats: Chat[] }[] {
|
||||
const now = new Date();
|
||||
const today = startOfDay(now);
|
||||
const yesterday = today - 86_400_000;
|
||||
|
||||
const groups: { key: number; label: string; chats: Chat[] }[] = [];
|
||||
let current: { key: number; label: string; chats: Chat[] } | null = null;
|
||||
|
||||
for (const chat of chats) {
|
||||
const created = new Date(chat.created_at);
|
||||
const day = startOfDay(created);
|
||||
|
||||
if (!current || current.key !== day) {
|
||||
let label: string;
|
||||
if (day === today) label = 'Today';
|
||||
else if (day === yesterday) label = 'Yesterday';
|
||||
else {
|
||||
const opts: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
};
|
||||
if (created.getFullYear() !== now.getFullYear())
|
||||
opts.year = 'numeric';
|
||||
label = created.toLocaleDateString(undefined, opts);
|
||||
}
|
||||
current = { key: day, label, chats: [] };
|
||||
groups.push(current);
|
||||
}
|
||||
|
||||
current.chats.push(chat);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
function parseError(err: unknown): string {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user