Skip to content

Commit 7488dd4

Browse files
committed
chore: add threads support
1 parent 61f1089 commit 7488dd4

File tree

5 files changed

+81
-1
lines changed

5 files changed

+81
-1
lines changed

package-lock.json

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"ai": "^4.3.9",
5050
"class-variance-authority": "^0.7.1",
5151
"clsx": "^2.1.1",
52+
"nuqs": "^2.4.3",
5253
"react": "^19.1.0",
5354
"react-dom": "^19.1.0",
5455
"tailwind-merge": "^3.2.0",

src/app.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useAgentChat } from "agents/ai-react";
44
import type { Message } from "@ai-sdk/react";
55
import { APPROVAL } from "./shared";
66
import type { tools } from "./tools";
7+
import { useQueryState } from "nuqs";
78

89
// Component imports
910
import { Button } from "@/components/button/Button";
@@ -21,14 +22,17 @@ import {
2122
Robot,
2223
Sun,
2324
Trash,
25+
FilePlus,
2426
} from "@phosphor-icons/react";
27+
import { generateShortId } from "./lib/utils";
2528

2629
// List of tools that require human confirmation
2730
const toolsRequiringConfirmation: (keyof typeof tools)[] = [
2831
"getWeatherInformation",
2932
];
3033

3134
export default function Chat() {
35+
const [ thread, setThread ] = useQueryState("thread");
3236
const [theme, setTheme] = useState<"dark" | "light">(() => {
3337
// Check localStorage first, default to dark if not found
3438
const savedTheme = localStorage.getItem("theme");
@@ -60,13 +64,24 @@ export default function Chat() {
6064
scrollToBottom();
6165
}, [scrollToBottom]);
6266

67+
useEffect(() => {
68+
if (thread) return;
69+
createNewThread();
70+
}, [thread]);
71+
6372
const toggleTheme = () => {
6473
const newTheme = theme === "dark" ? "light" : "dark";
6574
setTheme(newTheme);
6675
};
6776

77+
const createNewThread = () => {
78+
const newThread = generateShortId();
79+
setThread(newThread);
80+
};
81+
6882
const agent = useAgent({
6983
agent: "chat",
84+
name: thread ?? undefined,
7085
});
7186

7287
const {
@@ -101,6 +116,10 @@ export default function Chat() {
101116
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
102117
};
103118

119+
if (!thread) {
120+
return <></>;
121+
}
122+
104123
return (
105124
<div className="h-[100vh] w-full p-4 flex justify-center items-center bg-fixed overflow-hidden">
106125
<HasOpenAIKey />
@@ -152,6 +171,18 @@ export default function Chat() {
152171
size="md"
153172
shape="square"
154173
className="rounded-full h-9 w-9"
174+
tooltip={"New Thread"}
175+
onClick={createNewThread}
176+
>
177+
<FilePlus size={20} />
178+
</Button>
179+
180+
<Button
181+
variant="ghost"
182+
size="md"
183+
shape="square"
184+
className="rounded-full h-9 w-9"
185+
tooltip={"Clear History"}
155186
onClick={clearHistory}
156187
>
157188
<Trash size={20} />

src/client.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import "./styles.css";
22
import { createRoot } from "react-dom/client";
33
import App from "./app";
44
import { Providers } from "@/providers";
5+
import { NuqsAdapter } from 'nuqs/adapters/react';
56

67
const root = createRoot(document.getElementById("app")!);
78

89
root.render(
910
<Providers>
1011
<div className="bg-neutral-50 text-base text-neutral-900 antialiased transition-colors selection:bg-blue-700 selection:text-white dark:bg-neutral-950 dark:text-neutral-100">
11-
<App />
12+
<NuqsAdapter>
13+
<App />
14+
</NuqsAdapter>
1215
</div>
1316
</Providers>
1417
);

src/lib/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ import { twMerge } from "tailwind-merge";
44
export function cn(...inputs: ClassValue[]) {
55
return twMerge(clsx(inputs));
66
}
7+
8+
export function generateShortId(length = 8) {
9+
const bytes = crypto.getRandomValues(new Uint8Array(length));
10+
return btoa(String.fromCharCode(...bytes)).replace(/[^a-zA-Z0-9]/g, '').slice(0, length);
11+
}

0 commit comments

Comments
 (0)