Skip to content

Commit 7dd123a

Browse files
ngxsonmglambda
authored andcommitted
server : (webui) introduce conversation branching + idb storage (ggml-org#11792)
* server : (webui) introduce conversation branching + idb storage * mark old conv as "migrated" instead deleting them * improve migration * add more comments * more clarification
1 parent acdaa09 commit 7dd123a

File tree

11 files changed

+628
-227
lines changed

11 files changed

+628
-227
lines changed

examples/server/public/index.html.gz

34 KB
Binary file not shown.

examples/server/webui/package-lock.json

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/server/webui/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@vscode/markdown-it-katex": "^1.1.1",
1717
"autoprefixer": "^10.4.20",
1818
"daisyui": "^4.12.14",
19+
"dexie": "^4.0.11",
1920
"highlight.js": "^11.10.0",
2021
"katex": "^0.16.15",
2122
"postcss": "^8.4.49",

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

+53-21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useAppContext } from '../utils/app.context';
33
import { Message, PendingMessage } from '../utils/types';
44
import { classNames } from '../utils/misc';
55
import MarkdownDisplay, { CopyButton } from './MarkdownDisplay';
6+
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
67

78
interface SplitMessage {
89
content: PendingMessage['content'];
@@ -12,17 +13,24 @@ interface SplitMessage {
1213

1314
export default function ChatMessage({
1415
msg,
16+
siblingLeafNodeIds,
17+
siblingCurrIdx,
1518
id,
16-
scrollToBottom,
19+
onRegenerateMessage,
20+
onEditMessage,
21+
onChangeSibling,
1722
isPending,
1823
}: {
1924
msg: Message | PendingMessage;
25+
siblingLeafNodeIds: Message['id'][];
26+
siblingCurrIdx: number;
2027
id?: string;
21-
scrollToBottom: (requiresNearBottom: boolean) => void;
28+
onRegenerateMessage(msg: Message): void;
29+
onEditMessage(msg: Message, content: string): void;
30+
onChangeSibling(sibling: Message['id']): void;
2231
isPending?: boolean;
2332
}) {
24-
const { viewingConversation, replaceMessageAndGenerate, config } =
25-
useAppContext();
33+
const { viewingChat, config } = useAppContext();
2634
const [editingContent, setEditingContent] = useState<string | null>(null);
2735
const timings = useMemo(
2836
() =>
@@ -37,6 +45,8 @@ export default function ChatMessage({
3745
: null,
3846
[msg.timings]
3947
);
48+
const nextSibling = siblingLeafNodeIds[siblingCurrIdx + 1];
49+
const prevSibling = siblingLeafNodeIds[siblingCurrIdx - 1];
4050

4151
// for reasoning model, we split the message into content and thought
4252
// TODO: implement this as remark/rehype plugin in the future
@@ -64,13 +74,7 @@ export default function ChatMessage({
6474
return { content: actualContent, thought, isThinking };
6575
}, [msg]);
6676

67-
if (!viewingConversation) return null;
68-
69-
const regenerate = async () => {
70-
replaceMessageAndGenerate(viewingConversation.id, msg.id, undefined, () =>
71-
scrollToBottom(true)
72-
);
73-
};
77+
if (!viewingChat) return null;
7478

7579
return (
7680
<div className="group" id={id}>
@@ -105,13 +109,12 @@ export default function ChatMessage({
105109
</button>
106110
<button
107111
className="btn mt-2"
108-
onClick={() =>
109-
replaceMessageAndGenerate(
110-
viewingConversation.id,
111-
msg.id,
112-
editingContent
113-
)
114-
}
112+
onClick={() => {
113+
if (msg.content !== null) {
114+
setEditingContent(null);
115+
onEditMessage(msg as Message, editingContent);
116+
}
117+
}}
115118
>
116119
Submit
117120
</button>
@@ -196,10 +199,35 @@ export default function ChatMessage({
196199
{msg.content !== null && (
197200
<div
198201
className={classNames({
199-
'mx-4 mt-2 mb-2': true,
200-
'text-right': msg.role === 'user',
202+
'flex items-center gap-2 mx-4 mt-2 mb-2': true,
203+
'flex-row-reverse': msg.role === 'user',
201204
})}
202205
>
206+
{siblingLeafNodeIds && siblingLeafNodeIds.length > 1 && (
207+
<div className="flex gap-1 items-center opacity-60 text-sm">
208+
<button
209+
className={classNames({
210+
'btn btn-sm btn-ghost p-1': true,
211+
'opacity-20': !prevSibling,
212+
})}
213+
onClick={() => prevSibling && onChangeSibling(prevSibling)}
214+
>
215+
<ChevronLeftIcon className="h-4 w-4" />
216+
</button>
217+
<span>
218+
{siblingCurrIdx + 1} / {siblingLeafNodeIds.length}
219+
</span>
220+
<button
221+
className={classNames({
222+
'btn btn-sm btn-ghost p-1': true,
223+
'opacity-20': !nextSibling,
224+
})}
225+
onClick={() => nextSibling && onChangeSibling(nextSibling)}
226+
>
227+
<ChevronRightIcon className="h-4 w-4" />
228+
</button>
229+
</div>
230+
)}
203231
{/* user message */}
204232
{msg.role === 'user' && (
205233
<button
@@ -216,7 +244,11 @@ export default function ChatMessage({
216244
{!isPending && (
217245
<button
218246
className="badge btn-mini show-on-hover mr-2"
219-
onClick={regenerate}
247+
onClick={() => {
248+
if (msg.content !== null) {
249+
onRegenerateMessage(msg as Message);
250+
}
251+
}}
220252
disabled={msg.content === null}
221253
>
222254
🔄 Regenerate

0 commit comments

Comments
 (0)