Skip to content

Commit 7c87fc4

Browse files
committed
move conversation more menu to sidebar
1 parent c8641ef commit 7c87fc4

File tree

3 files changed

+101
-74
lines changed

3 files changed

+101
-74
lines changed

tools/server/webui/src/components/ChatMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,10 @@ function ThoughtProcess({
285285
className="loading loading-spinner loading-md mr-2"
286286
style={{ verticalAlign: 'middle' }}
287287
></span>
288-
<b>Thinking</b>
288+
Thinking
289289
</span>
290290
) : (
291-
<b>Thought Process</b>
291+
<>Thought Process</>
292292
)}
293293
</div>
294294
</div>

tools/server/webui/src/components/Header.tsx

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,9 @@ import { useAppContext } from '../utils/app.context';
44
import { classNames } from '../utils/misc';
55
import daisyuiThemes from 'daisyui/theme/object';
66
import { THEMES } from '../Config';
7-
import { useNavigate } from 'react-router';
8-
import {
9-
Cog8ToothIcon,
10-
SunIcon,
11-
EllipsisVerticalIcon,
12-
Bars3Icon,
13-
} from '@heroicons/react/24/outline';
7+
import { Cog8ToothIcon, SunIcon, Bars3Icon } from '@heroicons/react/24/outline';
148

159
export default function Header() {
16-
const navigate = useNavigate();
1710
const [selectedTheme, setSelectedTheme] = useState(StorageUtils.getTheme());
1811
const { setShowSettings } = useAppContext();
1912

@@ -30,33 +23,6 @@ export default function Header() {
3023
);
3124
}, [selectedTheme]);
3225

33-
const { isGenerating, viewingChat } = useAppContext();
34-
const isCurrConvGenerating = isGenerating(viewingChat?.conv.id ?? '');
35-
36-
const removeConversation = () => {
37-
if (isCurrConvGenerating || !viewingChat) return;
38-
const convId = viewingChat?.conv.id;
39-
if (window.confirm('Are you sure to delete this conversation?')) {
40-
StorageUtils.remove(convId);
41-
navigate('/');
42-
}
43-
};
44-
45-
const downloadConversation = () => {
46-
if (isCurrConvGenerating || !viewingChat) return;
47-
const convId = viewingChat?.conv.id;
48-
const conversationJson = JSON.stringify(viewingChat, null, 2);
49-
const blob = new Blob([conversationJson], { type: 'application/json' });
50-
const url = URL.createObjectURL(blob);
51-
const a = document.createElement('a');
52-
a.href = url;
53-
a.download = `conversation_${convId}.json`;
54-
document.body.appendChild(a);
55-
a.click();
56-
document.body.removeChild(a);
57-
URL.revokeObjectURL(url);
58-
};
59-
6026
return (
6127
<div className="flex flex-row items-center pt-6 pb-6 sticky top-0 z-10 bg-base-100">
6228
{/* open sidebar button */}
@@ -68,32 +34,6 @@ export default function Header() {
6834

6935
{/* action buttons (top right) */}
7036
<div className="flex items-center">
71-
{viewingChat && (
72-
<div className="dropdown dropdown-end">
73-
{/* "..." button */}
74-
<button
75-
tabIndex={0}
76-
role="button"
77-
className="btn m-1"
78-
disabled={isCurrConvGenerating}
79-
>
80-
<EllipsisVerticalIcon className="w-5 h-5" />
81-
</button>
82-
{/* dropdown menu */}
83-
<ul
84-
tabIndex={0}
85-
className="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"
86-
>
87-
<li onClick={downloadConversation}>
88-
<a>Download</a>
89-
</li>
90-
<li className="text-error" onClick={removeConversation}>
91-
<a>Delete</a>
92-
</li>
93-
</ul>
94-
</div>
95-
)}
96-
9737
<div className="tooltip tooltip-bottom" data-tip="Settings">
9838
<button className="btn" onClick={() => setShowSettings(true)}>
9939
{/* settings button */}

tools/server/webui/src/components/Sidebar.tsx

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ import { classNames } from '../utils/misc';
33
import { Conversation } from '../utils/types';
44
import StorageUtils from '../utils/storage';
55
import { useNavigate, useParams } from 'react-router';
6-
import { XMarkIcon } from '@heroicons/react/24/outline';
6+
import { EllipsisVerticalIcon, XMarkIcon } from '@heroicons/react/24/outline';
7+
import { BtnWithTooltips } from '../utils/common';
8+
import { useAppContext } from '../utils/app.context';
9+
import toast from 'react-hot-toast';
710

811
export default function Sidebar() {
912
const params = useParams();
1013
const navigate = useNavigate();
1114

15+
const { isGenerating } = useAppContext();
16+
1217
const [conversations, setConversations] = useState<Conversation[]>([]);
1318
const [currConv, setCurrConv] = useState<Conversation | null>(null);
1419

@@ -63,17 +68,45 @@ export default function Sidebar() {
6368
+ New conversation
6469
</div>
6570
{conversations.map((conv) => (
66-
<div
71+
<ConversationItem
6772
key={conv.id}
68-
className={classNames({
69-
'btn btn-ghost justify-start font-normal': true,
70-
'btn-soft': conv.id === currConv?.id,
71-
})}
72-
onClick={() => navigate(`/chat/${conv.id}`)}
73-
dir="auto"
74-
>
75-
<span className="truncate">{conv.name}</span>
76-
</div>
73+
conv={conv}
74+
isCurrConv={currConv?.id === conv.id}
75+
onSelect={() => {
76+
navigate(`/chat/${conv.id}`);
77+
}}
78+
onDelete={() => {
79+
if (isGenerating(conv.id)) {
80+
toast.error('Cannot delete conversation while generating');
81+
return;
82+
}
83+
if (
84+
window.confirm('Are you sure to delete this conversation?')
85+
) {
86+
toast.success('Conversation deleted');
87+
StorageUtils.remove(conv.id);
88+
navigate('/');
89+
}
90+
}}
91+
onDownload={() => {
92+
if (isGenerating(conv.id)) {
93+
toast.error('Cannot download conversation while generating');
94+
return;
95+
}
96+
const conversationJson = JSON.stringify(conv, null, 2);
97+
const blob = new Blob([conversationJson], {
98+
type: 'application/json',
99+
});
100+
const url = URL.createObjectURL(blob);
101+
const a = document.createElement('a');
102+
a.href = url;
103+
a.download = `conversation_${conv.id}.json`;
104+
document.body.appendChild(a);
105+
a.click();
106+
document.body.removeChild(a);
107+
URL.revokeObjectURL(url);
108+
}}
109+
/>
77110
))}
78111
<div className="text-center text-xs opacity-40 mt-auto mx-4">
79112
Conversations are saved to browser's IndexedDB
@@ -83,3 +116,57 @@ export default function Sidebar() {
83116
</>
84117
);
85118
}
119+
120+
function ConversationItem({
121+
conv,
122+
isCurrConv,
123+
onSelect,
124+
onDelete,
125+
onDownload,
126+
}: {
127+
conv: Conversation;
128+
isCurrConv: boolean;
129+
onSelect: () => void;
130+
onDelete: () => void;
131+
onDownload: () => void;
132+
}) {
133+
return (
134+
<div
135+
className={classNames({
136+
'group flex flex-row btn btn-ghost justify-start items-center font-normal':
137+
true,
138+
'btn-soft': isCurrConv,
139+
})}
140+
>
141+
<div
142+
key={conv.id}
143+
className="w-full overflow-hidden truncate text-start"
144+
onClick={onSelect}
145+
dir="auto"
146+
>
147+
{conv.name}
148+
</div>
149+
<div className="dropdown dropdown-end h-5 opacity-0 group-hover:opacity-100">
150+
<BtnWithTooltips
151+
className="cursor-pointer block group-hover:block"
152+
onClick={() => {}}
153+
tooltipsContent="More"
154+
>
155+
<EllipsisVerticalIcon className="w-5 h-5" />
156+
</BtnWithTooltips>
157+
{/* dropdown menu */}
158+
<ul
159+
tabIndex={0}
160+
className="dropdown-content menu bg-base-100 rounded-box z-[1] w-32 p-2 shadow"
161+
>
162+
<li onClick={onDownload}>
163+
<a>Download</a>
164+
</li>
165+
<li className="text-error" onClick={onDelete}>
166+
<a>Delete</a>
167+
</li>
168+
</ul>
169+
</div>
170+
</div>
171+
);
172+
}

0 commit comments

Comments
 (0)