Skip to content

Commit

Permalink
feat: add chat history
Browse files Browse the repository at this point in the history
  • Loading branch information
enitrat committed Feb 22, 2025
1 parent 152f000 commit 73943c9
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 81 deletions.
9 changes: 0 additions & 9 deletions API_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,6 @@ Currently, the API does not require authentication. However, this may change in
}
```

### 2. Get Chat History

- **URL**: `/chats`
- **Method**: GET
- **Description**: Retrieves the chat history (only available in non-hosted mode).
- **Response**: JSON array of chat objects.

> Note: Disabled in hosted mode
### 3. Get Suggestions

- **URL**: `/suggestions`
Expand Down
84 changes: 84 additions & 0 deletions ui/app/history/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use client';

import { useEffect, useState } from 'react';
import Link from 'next/link';
import DeleteChat from '@/components/DeleteChat';
import { StoredChat } from '@/components/ChatWindow';

const ChatHistory = () => {
const [chats, setChats] = useState<StoredChat[]>([]);
const [searchTerm, setSearchTerm] = useState('');

useEffect(() => {
if (typeof window !== 'undefined') {
const storedChats = JSON.parse(localStorage.getItem('chats') || '[]');
setChats(storedChats);
}
}, []);

// Sort chats so the most recent (by chat.createdAt) comes first.
const sortedChats = [...chats].sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
);

// Filter chats based on search term.
const filteredChats = sortedChats.filter((chat) =>
(chat.title || `Chat ${chat.id}`)
.toLowerCase()
.includes(searchTerm.toLowerCase()),
);

return (
<div className="p-4">
<div className="flex flex-col md:flex-row md:items-center md:justify-between mb-4">
<h1 className="text-2xl font-bold">Chat History</h1>
<div className="flex gap-2 mt-2 md:mt-0">
<input
type="text"
placeholder="Search chats..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="px-2 py-1 border border-light-200 dark:border-dark-200 rounded-md"
/>
<button
onClick={() => {
if (window.confirm('Clear all chat history?')) {
localStorage.removeItem('chats');
setChats([]);
}
}}
className="px-3 py-1 rounded-md bg-red-400 text-white hover:bg-red-500"
>
Clear All
</button>
</div>
</div>
{filteredChats.length === 0 ? (
<p>No chats found.</p>
) : (
<ul className="space-y-4">
{filteredChats.map((chat) => (
<li
key={chat.title?.slice(0, 30) || chat.id}
className="flex items-center justify-between p-4 bg-light-secondary dark:bg-dark-secondary rounded-lg border border-light-200 dark:border-dark-200"
>
<div>
<Link href={`/c/${chat.id}`} className="text-lg font-medium">
{chat.title || `Chat ${chat.id}`}
</Link>
{chat.createdAt && (
<p className="text-sm text-gray-500">
{new Date(chat.createdAt).toLocaleString()}
</p>
)}
</div>
<DeleteChat chatId={chat.id} chats={chats} setChats={setChats} />
</li>
))}
</ul>
)}
</div>
);
};

export default ChatHistory;
48 changes: 7 additions & 41 deletions ui/app/library/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,22 @@ import { formatTimeDifference } from '@/lib/utils';
import { BookOpenText, ClockIcon, Delete, ScanEye } from 'lucide-react';
import Link from 'next/link';
import { useEffect, useState } from 'react';

export interface Chat {
id: string;
title: string;
createdAt: string;
focusMode: string;
}

const Page = () => {
const [chats, setChats] = useState<Chat[]>([]);
const [chats, setChats] = useState<StoredChat[]>([]);
const [loading, setLoading] = useState(true);

const isHostedModel = process.env.NEXT_PUBLIC_HOSTED_MODE === 'true';

useEffect(() => {
const fetchChats = async () => {
if (isHostedModel) {
const storedChats = JSON.parse(
localStorage.getItem('chats') || '[]',
) as StoredChat[];
setChats(
storedChats.map(
(chat): Chat => ({
id: chat.id,
title: chat.messages[0].content,
createdAt: chat.createdAt.toString(),
focusMode: chat.focusMode,
}),
),
);
setLoading(false);
return;
}
setLoading(true);

const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/chats`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

const data = await res.json();

setChats(data?.chats ?? []);
const storedChats = JSON.parse(
localStorage.getItem('chats') || '[]',
) as StoredChat[];
setChats(storedChats);
setLoading(false);
return;
};

fetchChats();
}, [isHostedModel]);
}, []);

return loading ? (
<div className="flex flex-row items-center justify-center min-h-screen">
Expand Down
2 changes: 2 additions & 0 deletions ui/components/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ const loadMessages = async (

export type StoredChat = {
id: string;
title: string;
messages: Message[];
focusMode: string;
createdAt: Date;
Expand All @@ -319,6 +320,7 @@ const saveMessagesToLocalStorage = (

const updatedChat: StoredChat = {
id: chatId,
title: messages[0].content,
messages,
focusMode,
createdAt: new Date(),
Expand Down
37 changes: 6 additions & 31 deletions ui/components/DeleteChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,38 +26,13 @@ const DeleteChat = ({
const [loading, setLoading] = useState(false);

const handleDelete = async () => {
if (process.env.NEXT_PUBLIC_HOSTED_MODE === 'true') {
deleteChatFromLocalStorage(chatId);
const newChats = chats.filter((chat) => chat.id !== chatId);
setChats(newChats);
setConfirmationDialogOpen(false);
return;
}
setLoading(true);
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/chats/${chatId}`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
},
);

if (res.status != 200) {
throw new Error('Failed to delete chat');
}

const newChats = chats.filter((chat) => chat.id !== chatId);

setChats(newChats);
} catch (err: any) {
toast.error(err.message);
} finally {
setConfirmationDialogOpen(false);
setLoading(false);
}
deleteChatFromLocalStorage(chatId);
const newChats = chats.filter((chat) => chat.id !== chatId);
setChats(newChats);
setConfirmationDialogOpen(false);
setLoading(false);
return;
};

return (
Expand Down
6 changes: 6 additions & 0 deletions ui/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => {
active: segments.length === 0 || segments.includes('c'),
label: 'Home',
},
{
icon: BookOpenText,
href: '/history',
active: segments.includes('history'),
label: 'History',
},
];

return (
Expand Down

0 comments on commit 73943c9

Please sign in to comment.