Skip to content

Commit 925f7d5

Browse files
committed
implemented thread history UI
1 parent deadd90 commit 925f7d5

File tree

7 files changed

+468
-13
lines changed

7 files changed

+468
-13
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { isToday, isYesterday, isWithinInterval, subDays } from "date-fns";
2+
import { dummyThreads } from "../utils/dummy";
3+
import { TooltipIconButton } from "./ui/assistant-ui/tooltip-icon-button";
4+
import { Button } from "./ui/button";
5+
import { SquarePen, History } from "lucide-react";
6+
import {
7+
Sheet,
8+
SheetContent,
9+
SheetDescription,
10+
SheetHeader,
11+
SheetTitle,
12+
SheetTrigger,
13+
} from "./ui/sheet";
14+
15+
interface ThreadHistoryProps {
16+
assistantId: string | undefined;
17+
}
18+
19+
interface ThreadProps {
20+
id: string;
21+
onClick: (id: string) => void;
22+
label: string;
23+
createdAt: Date;
24+
}
25+
26+
const Thread = (props: ThreadProps) => (
27+
<Button
28+
className="px-2 hover:bg-[#393939] hover:text-white justify-start"
29+
size="sm"
30+
variant="ghost"
31+
onClick={() => props.onClick(props.id)}
32+
>
33+
<p className="truncate ... text-sm font-light">{props.label}</p>
34+
</Button>
35+
);
36+
37+
const groupThreads = (threads: ThreadProps[]) => {
38+
const today = new Date();
39+
const yesterday = subDays(today, 1);
40+
const sevenDaysAgo = subDays(today, 7);
41+
42+
return {
43+
today: threads.filter((thread) => isToday(thread.createdAt)),
44+
yesterday: threads.filter((thread) => isYesterday(thread.createdAt)),
45+
lastSevenDays: threads.filter((thread) =>
46+
isWithinInterval(thread.createdAt, {
47+
start: sevenDaysAgo,
48+
end: yesterday,
49+
}),
50+
),
51+
older: threads.filter((thread) => thread.createdAt < sevenDaysAgo),
52+
};
53+
};
54+
55+
const prettifyDateLabel = (group: string): string => {
56+
switch (group) {
57+
case "today":
58+
return "Today";
59+
case "yesterday":
60+
return "Yesterday";
61+
case "lastSevenDays":
62+
return "Last 7 days";
63+
case "older":
64+
return "Older";
65+
default:
66+
return group;
67+
}
68+
};
69+
70+
interface ThreadsListProps {
71+
groupedThreads: {
72+
today: ThreadProps[];
73+
yesterday: ThreadProps[];
74+
lastSevenDays: ThreadProps[];
75+
older: ThreadProps[];
76+
};
77+
}
78+
79+
function ThreadsList(props: ThreadsListProps) {
80+
return (
81+
<div className="flex flex-col px-3 pt-3 gap-4">
82+
{Object.entries(props.groupedThreads).map(([group, threads]) =>
83+
threads.length > 0 ? (
84+
<div key={group}>
85+
<h3 className="text-sm font-medium text-gray-400 mb-1 pl-2">
86+
{prettifyDateLabel(group)}
87+
</h3>
88+
<div className="flex flex-col gap-1">
89+
{threads.map((thread) => (
90+
<Thread key={thread.id} {...thread} />
91+
))}
92+
</div>
93+
</div>
94+
) : null,
95+
)}
96+
</div>
97+
);
98+
}
99+
100+
export function ThreadHistory(props: ThreadHistoryProps) {
101+
const groupedThreads = groupThreads(dummyThreads);
102+
103+
return (
104+
<span>
105+
{/* Tablet & up */}
106+
<div className="hidden md:flex flex-col w-[260px] h-full">
107+
<div className="flex-grow border-l-[1px] border-[#393939] my-6">
108+
<div className="flex flex-row items-center justify-between border-b-[1px] border-[#393939] pt-3 px-2 mx-4 -mt-4 text-gray-200">
109+
<p className="text-lg font-medium">Chat History</p>
110+
<TooltipIconButton
111+
tooltip="New chat"
112+
variant="ghost"
113+
className="w-fit p-2"
114+
>
115+
<SquarePen className="w-5 h-5" />
116+
</TooltipIconButton>
117+
</div>
118+
<ThreadsList groupedThreads={groupedThreads} />
119+
</div>
120+
</div>
121+
{/* Mobile */}
122+
<span className="md:hidden flex flex-row gap-2 mt-2 mr-2">
123+
<Sheet>
124+
<SheetTrigger>
125+
<TooltipIconButton
126+
tooltip="New chat"
127+
variant="ghost"
128+
className="w-fit p-2"
129+
>
130+
<History className="w-6 h-6" />
131+
</TooltipIconButton>
132+
</SheetTrigger>
133+
<SheetContent className="bg-[#282828] border-none">
134+
<ThreadsList groupedThreads={groupedThreads} />
135+
</SheetContent>
136+
</Sheet>
137+
<TooltipIconButton
138+
tooltip="New chat"
139+
variant="ghost"
140+
className="w-fit p-2"
141+
>
142+
<SquarePen className="w-6 h-6" />
143+
</TooltipIconButton>
144+
</span>
145+
</span>
146+
);
147+
}

frontend/app/components/ui/assistant-ui/markdown-text.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ import { cn } from "../../../utils/cn";
1717

1818
import "katex/dist/katex.min.css";
1919

20-
const LinkWithIcon = () => (
21-
<span className="flex flex-row gap-1 items-center">
22-
<ExternalLink />
23-
</span>
24-
);
25-
2620
const MarkdownTextImpl = () => {
2721
return (
2822
<MarkdownTextPrimitive

frontend/app/components/ui/sheet.tsx

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as SheetPrimitive from "@radix-ui/react-dialog";
5+
import { Cross2Icon } from "@radix-ui/react-icons";
6+
import { cva, type VariantProps } from "class-variance-authority";
7+
8+
import { cn } from "../../utils/cn";
9+
10+
const Sheet = SheetPrimitive.Root;
11+
12+
const SheetTrigger = SheetPrimitive.Trigger;
13+
14+
const SheetClose = SheetPrimitive.Close;
15+
16+
const SheetPortal = SheetPrimitive.Portal;
17+
18+
const SheetOverlay = React.forwardRef<
19+
React.ElementRef<typeof SheetPrimitive.Overlay>,
20+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
21+
>(({ className, ...props }, ref) => (
22+
<SheetPrimitive.Overlay
23+
className={cn(
24+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
25+
className,
26+
)}
27+
{...props}
28+
ref={ref}
29+
/>
30+
));
31+
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
32+
33+
const sheetVariants = cva(
34+
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
35+
{
36+
variants: {
37+
side: {
38+
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39+
bottom:
40+
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41+
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42+
right:
43+
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44+
},
45+
},
46+
defaultVariants: {
47+
side: "right",
48+
},
49+
},
50+
);
51+
52+
interface SheetContentProps
53+
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
54+
VariantProps<typeof sheetVariants> {}
55+
56+
const SheetContent = React.forwardRef<
57+
React.ElementRef<typeof SheetPrimitive.Content>,
58+
SheetContentProps
59+
>(({ side = "right", className, children, ...props }, ref) => (
60+
<SheetPortal>
61+
<SheetOverlay />
62+
<SheetPrimitive.Content
63+
ref={ref}
64+
className={cn(sheetVariants({ side }), className)}
65+
{...props}
66+
>
67+
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
68+
<Cross2Icon className="h-4 w-4" />
69+
<span className="sr-only">Close</span>
70+
</SheetPrimitive.Close>
71+
{children}
72+
</SheetPrimitive.Content>
73+
</SheetPortal>
74+
));
75+
SheetContent.displayName = SheetPrimitive.Content.displayName;
76+
77+
const SheetHeader = ({
78+
className,
79+
...props
80+
}: React.HTMLAttributes<HTMLDivElement>) => (
81+
<div
82+
className={cn(
83+
"flex flex-col space-y-2 text-center sm:text-left",
84+
className,
85+
)}
86+
{...props}
87+
/>
88+
);
89+
SheetHeader.displayName = "SheetHeader";
90+
91+
const SheetFooter = ({
92+
className,
93+
...props
94+
}: React.HTMLAttributes<HTMLDivElement>) => (
95+
<div
96+
className={cn(
97+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
98+
className,
99+
)}
100+
{...props}
101+
/>
102+
);
103+
SheetFooter.displayName = "SheetFooter";
104+
105+
const SheetTitle = React.forwardRef<
106+
React.ElementRef<typeof SheetPrimitive.Title>,
107+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
108+
>(({ className, ...props }, ref) => (
109+
<SheetPrimitive.Title
110+
ref={ref}
111+
className={cn("text-lg font-semibold text-foreground", className)}
112+
{...props}
113+
/>
114+
));
115+
SheetTitle.displayName = SheetPrimitive.Title.displayName;
116+
117+
const SheetDescription = React.forwardRef<
118+
React.ElementRef<typeof SheetPrimitive.Description>,
119+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
120+
>(({ className, ...props }, ref) => (
121+
<SheetPrimitive.Description
122+
ref={ref}
123+
className={cn("text-sm text-muted-foreground", className)}
124+
{...props}
125+
/>
126+
));
127+
SheetDescription.displayName = SheetPrimitive.Description.displayName;
128+
129+
export {
130+
Sheet,
131+
SheetPortal,
132+
SheetOverlay,
133+
SheetTrigger,
134+
SheetClose,
135+
SheetContent,
136+
SheetHeader,
137+
SheetFooter,
138+
SheetTitle,
139+
SheetDescription,
140+
};

frontend/app/new/page.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import {
1616
convertToOpenAIFormat,
1717
} from "../utils/convert_messages";
1818
import { useGraph } from "../hooks/useGraph";
19+
import { ThreadHistory } from "../components/ThreadHistory";
1920

2021
export default function ContentComposerChatInterface(): React.ReactElement {
21-
const { messages, setMessages, streamMessage } = useGraph();
22+
const { messages, setMessages, streamMessage, assistantId } = useGraph();
2223
const [isRunning, setIsRunning] = useState(false);
2324

2425
async function onNew(message: AppendMessage): Promise<void> {
@@ -56,10 +57,15 @@ export default function ContentComposerChatInterface(): React.ReactElement {
5657
});
5758

5859
return (
59-
<div className="h-full">
60-
<AssistantRuntimeProvider runtime={runtime}>
61-
<MyThread messages={messages} />
62-
</AssistantRuntimeProvider>
60+
<div className="h-full flex flex-row">
61+
<div className="mr-auto w-full">
62+
<AssistantRuntimeProvider runtime={runtime}>
63+
<MyThread messages={messages} />
64+
</AssistantRuntimeProvider>
65+
</div>
66+
<div className="ml-auto">
67+
<ThreadHistory assistantId={assistantId} />
68+
</div>
6369
<Toaster />
6470
</div>
6571
);

0 commit comments

Comments
 (0)