Skip to content

Commit a34e597

Browse files
jacoblee93nfcampos
authored andcommitted
WIP: Time travel to previous states of a thread
1 parent c4590ec commit a34e597

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

frontend/src/components/Timeline.tsx

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { useState } from "react";
2+
import { Slider } from "@mui/material";
3+
import {
4+
ChevronLeftIcon,
5+
ChevronRightIcon,
6+
ClockIcon,
7+
} from "@heroicons/react/24/outline";
8+
import { cn } from "../utils/cn";
9+
import { History } from "../hooks/useHistories";
10+
11+
export function Timeline(props: {
12+
disabled: boolean;
13+
histories: History[];
14+
activeHistoryIndex: number;
15+
onChange?: (newValue: number) => void;
16+
}) {
17+
const [expanded, setExpanded] = useState(false);
18+
return (
19+
<div className="flex items-center">
20+
<button
21+
className="flex items-center p-2 text-sm"
22+
type="submit"
23+
disabled={props.disabled}
24+
onClick={() => setExpanded((expanded) => !expanded)}
25+
>
26+
<ClockIcon className="h-4 w-4 mr-1 rounded-md shrink-0" />
27+
<span className="text-gray-900 font-semibold shrink-0">
28+
Time travel
29+
</span>
30+
</button>
31+
<Slider
32+
className={cn(
33+
"w-full shrink transition-max-width duration-200",
34+
expanded ? " ml-8 mr-8 max-w-full" : " invisible max-w-0",
35+
)}
36+
aria-label="Timeline"
37+
value={props.activeHistoryIndex}
38+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
39+
onChange={(e) => props.onChange?.((e.target as any).value)}
40+
valueLabelDisplay="auto"
41+
step={1}
42+
marks
43+
min={0}
44+
max={props.histories.length - 1}
45+
/>
46+
{expanded ? (
47+
<ChevronLeftIcon
48+
className="h-4 w-4 cursor-pointer shrink-0 mr-4"
49+
onClick={() => setExpanded((expanded) => !expanded)}
50+
/>
51+
) : (
52+
<ChevronRightIcon
53+
className="h-4 w-4 cursor-pointer shrink-0 mr-4"
54+
onClick={() => setExpanded((expanded) => !expanded)}
55+
/>
56+
)}
57+
</div>
58+
);
59+
}

frontend/src/hooks/useHistories.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useEffect, useState } from "react";
2+
import { Message } from "../types";
3+
import { StreamState } from "./useStreamState";
4+
5+
async function getHistories(threadId: string) {
6+
const response = await fetch(`/threads/${threadId}/history`, {
7+
headers: {
8+
Accept: "application/json",
9+
},
10+
}).then((r) => r.json());
11+
return response;
12+
}
13+
14+
export interface History {
15+
values: Message[];
16+
next: string[];
17+
config: Record<string, unknown>;
18+
}
19+
20+
export function useHistories(
21+
threadId: string | null,
22+
stream: StreamState | null,
23+
): {
24+
histories: History[];
25+
setHistories: React.Dispatch<React.SetStateAction<History[]>>;
26+
} {
27+
const [histories, setHistories] = useState<History[]>([]);
28+
29+
useEffect(() => {
30+
async function fetchHistories() {
31+
if (threadId) {
32+
const histories = await getHistories(threadId);
33+
setHistories(histories);
34+
}
35+
}
36+
fetchHistories();
37+
// eslint-disable-next-line react-hooks/exhaustive-deps
38+
}, [threadId, stream?.status]);
39+
40+
return { histories, setHistories };
41+
}

0 commit comments

Comments
 (0)