Skip to content

[In Progress] [Feat]: Chat Component #1841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add storage support
  • Loading branch information
iamfaran committed Jul 3, 2025
commit e553d52c769811ec20f5e7909872c7ac56b06572
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ import { useThreadContext, MyMessage, ThreadProvider } from "./context/ThreadCon
import { Thread } from "./assistant-ui/thread";
import { ThreadList } from "./assistant-ui/thread-list";
import { chatStorage, ThreadData as StoredThreadData } from "../utils/chatStorage";
import { useChatStorage } from "../hooks/useChatStorage";
import styled from "styled-components";




const ChatContainer = styled.div`
display: flex;
height: 500px;

.aui-thread-list-root {
width: 250px;
background-color: #333;
}

.aui-thread-root {
flex: 1;
background-color: #f0f0f0;
}

`;

// Define thread data interfaces to match ExternalStoreThreadData requirements
interface RegularThreadData {
Expand Down Expand Up @@ -45,109 +66,13 @@ function ChatWithThreads() {
const [threadList, setThreadList] = useState<ThreadData[]>([
{ threadId: "default", status: "regular", title: "New Chat" } as RegularThreadData,
]);
const [isInitialized, setIsInitialized] = useState(false);

// Load data from persistent storage on component mount
useEffect(() => {
const loadData = async () => {
try {
await chatStorage.initialize();

// Load all threads from storage
const storedThreads = await chatStorage.getAllThreads();
if (storedThreads.length > 0) {
// Convert stored threads to UI format
const uiThreads: ThreadData[] = storedThreads.map(stored => ({
threadId: stored.threadId,
status: stored.status as "regular" | "archived",
title: stored.title,
}));
setThreadList(uiThreads);

// Load messages for each thread
const threadMessages = new Map<string, MyMessage[]>();
for (const thread of storedThreads) {
const messages = await chatStorage.getMessages(thread.threadId);
threadMessages.set(thread.threadId, messages);
}

// Ensure default thread exists
if (!threadMessages.has("default")) {
threadMessages.set("default", []);
}

setThreads(threadMessages);

// Set current thread to the most recently updated one
const latestThread = storedThreads.sort((a, b) => b.updatedAt - a.updatedAt)[0];
if (latestThread) {
setCurrentThreadId(latestThread.threadId);
}
} else {
// Initialize with default thread
const defaultThread: StoredThreadData = {
threadId: "default",
status: "regular",
title: "New Chat",
createdAt: Date.now(),
updatedAt: Date.now(),
};
await chatStorage.saveThread(defaultThread);
}

setIsInitialized(true);
} catch (error) {
console.error("Failed to load chat data:", error);
setIsInitialized(true); // Continue with default state
}
};

loadData();
}, [setCurrentThreadId, setThreads]);

// Save thread data whenever threadList changes
useEffect(() => {
if (!isInitialized) return;

const saveThreads = async () => {
try {
for (const thread of threadList) {
const storedThread: StoredThreadData = {
threadId: thread.threadId,
status: thread.status,
title: thread.title,
createdAt: Date.now(), // In real app, preserve original createdAt
updatedAt: Date.now(),
};
await chatStorage.saveThread(storedThread);
}
} catch (error) {
console.error("Failed to save threads:", error);
}
};

saveThreads();
}, [threadList, isInitialized]);

// Save messages whenever threads change
useEffect(() => {
if (!isInitialized) return;

const saveMessages = async () => {
try {
for (const [threadId, messages] of threads.entries()) {
await chatStorage.saveMessages(messages, threadId);
}
} catch (error) {
console.error("Failed to save messages:", error);
}
};

saveMessages();
}, [threads, isInitialized]);



const { isInitialized } = useChatStorage({
threadList,
threads,
setThreadList,
setThreads,
setCurrentThreadId,
});
// Get messages for current thread
const currentMessages = threads.get(currentThreadId) || [];

Expand Down Expand Up @@ -349,10 +274,16 @@ function ChatWithThreads() {
},
});

if (!isInitialized) {
return <div>Loading...</div>;
}

return (
<AssistantRuntimeProvider runtime={runtime}>
<ThreadList />
<Thread />
<ChatContainer>
<ThreadList />
<Thread />
</ChatContainer>
</AssistantRuntimeProvider>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { useEffect, useState } from "react";
import { chatStorage, ThreadData as StoredThreadData } from "../utils/chatStorage";
import { MyMessage } from "../components/context/ThreadContext";

// Thread data interfaces (matching ChatWithThreads)
interface RegularThreadData {
threadId: string;
status: "regular";
title: string;
}

interface ArchivedThreadData {
threadId: string;
status: "archived";
title: string;
}

type ThreadData = RegularThreadData | ArchivedThreadData;

interface UseChatStorageParams {
threadList: ThreadData[];
threads: Map<string, MyMessage[]>;
setThreadList: React.Dispatch<React.SetStateAction<ThreadData[]>>;
setThreads: React.Dispatch<React.SetStateAction<Map<string, MyMessage[]>>>;
setCurrentThreadId: (id: string) => void;
}

export function useChatStorage({
threadList,
threads,
setThreadList,
setThreads,
setCurrentThreadId,
}: UseChatStorageParams) {
const [isInitialized, setIsInitialized] = useState(false);

// Load data from persistent storage on component mount
useEffect(() => {
const loadData = async () => {
try {
await chatStorage.initialize();

// Load all threads from storage
const storedThreads = await chatStorage.getAllThreads();
if (storedThreads.length > 0) {
// Convert stored threads to UI format
const uiThreads: ThreadData[] = storedThreads.map(stored => ({
threadId: stored.threadId,
status: stored.status as "regular" | "archived",
title: stored.title,
}));
setThreadList(uiThreads);

// Load messages for each thread
const threadMessages = new Map<string, MyMessage[]>();
for (const thread of storedThreads) {
const messages = await chatStorage.getMessages(thread.threadId);
threadMessages.set(thread.threadId, messages);
}

// Ensure default thread exists
if (!threadMessages.has("default")) {
threadMessages.set("default", []);
}

setThreads(threadMessages);

// Set current thread to the most recently updated one
const latestThread = storedThreads.sort((a, b) => b.updatedAt - a.updatedAt)[0];
if (latestThread) {
setCurrentThreadId(latestThread.threadId);
}
} else {
// Initialize with default thread
const defaultThread: StoredThreadData = {
threadId: "default",
status: "regular",
title: "New Chat",
createdAt: Date.now(),
updatedAt: Date.now(),
};
await chatStorage.saveThread(defaultThread);
}

setIsInitialized(true);
} catch (error) {
console.error("Failed to load chat data:", error);
setIsInitialized(true); // Continue with default state
}
};

loadData();
}, [setCurrentThreadId, setThreads, setThreadList]);

// Save thread data whenever threadList changes
useEffect(() => {
if (!isInitialized) return;

const saveThreads = async () => {
try {
for (const thread of threadList) {
const storedThread: StoredThreadData = {
threadId: thread.threadId,
status: thread.status,
title: thread.title,
createdAt: Date.now(), // In real app, preserve original createdAt
updatedAt: Date.now(),
};
await chatStorage.saveThread(storedThread);
}
} catch (error) {
console.error("Failed to save threads:", error);
}
};

saveThreads();
}, [threadList, isInitialized]);

// Save messages whenever threads change
useEffect(() => {
if (!isInitialized) return;

const saveMessages = async () => {
try {
for (const [threadId, messages] of threads.entries()) {
await chatStorage.saveMessages(messages, threadId);
}
} catch (error) {
console.error("Failed to save messages:", error);
}
};

saveMessages();
}, [threads, isInitialized]);

return {
isInitialized,
};
}