Skip to content

Commit 69b63ba

Browse files
committed
chat working
1 parent 62a5bac commit 69b63ba

File tree

8 files changed

+325
-6
lines changed

8 files changed

+325
-6
lines changed

package-lock.json

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

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
},
1212
"dependencies": {
1313
"@huggingface/transformers": "^3.0.2",
14+
"@radix-ui/react-avatar": "^1.1.1",
1415
"@radix-ui/react-progress": "^1.1.0",
16+
"@radix-ui/react-scroll-area": "^1.2.1",
1517
"@radix-ui/react-slot": "^1.1.0",
1618
"class-variance-authority": "^0.7.0",
1719
"clsx": "^2.1.1",

src/App.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { useState } from "react";
22
import { ModelLoader } from "@/components/ModelLoader";
33
import { useWorker } from "./context/workerContext";
4+
import { Chat } from "@/components/Chat";
45

56
export default function App() {
67
const [progressItems, setProgressItems] = useState<ProgressItem[]>([]);
78
const { status } = useWorker();
89

9-
return <ModelLoader />;
10+
return <>{status === "ready" ? <Chat /> : <ModelLoader />}</>;
1011
}

src/components/Chat.tsx

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useState } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import { Input } from "@/components/ui/input";
4+
import { ScrollArea } from "@/components/ui/scroll-area";
5+
import { useWorker } from "@/context/workerContext";
6+
7+
interface Message {
8+
role: "user" | "assistant";
9+
content: string;
10+
}
11+
12+
export function Chat() {
13+
const [messages, setMessages] = useState<Message[]>([]);
14+
const [input, setInput] = useState("");
15+
const [isLoading, setIsLoading] = useState(false);
16+
const { generate } = useWorker();
17+
18+
const sendMessage = async (e: React.FormEvent) => {
19+
e.preventDefault();
20+
if (!input.trim()) return;
21+
22+
const newMessage: Message = { role: "user", content: input };
23+
setInput("");
24+
setIsLoading(true);
25+
26+
console.log("chat.tsx", [...messages, newMessage]);
27+
28+
const response = await generate([...messages, newMessage]);
29+
30+
setMessages((prev) => [
31+
...prev,
32+
newMessage,
33+
{ role: "assistant", content: response },
34+
]);
35+
setIsLoading(false);
36+
};
37+
38+
return (
39+
<div className="flex h-screen flex-col">
40+
<h1>Talk with SmollLM</h1>
41+
<ScrollArea className="flex-1 p-4">
42+
<div className="space-y-4">
43+
{messages.map((message, i) => (
44+
<div
45+
key={i}
46+
className={`flex ${
47+
message.role === "user" ? "justify-end" : "justify-start"
48+
}`}
49+
>
50+
<div
51+
className={`rounded-lg p-4 max-w-[80%] ${
52+
message.role === "user"
53+
? "bg-primary text-primary-foreground"
54+
: "bg-muted"
55+
}`}
56+
>
57+
{message.content}
58+
</div>
59+
</div>
60+
))}
61+
</div>
62+
</ScrollArea>
63+
64+
<form onSubmit={sendMessage} className="p-4 border-t">
65+
<div className="flex gap-2">
66+
<Input
67+
value={input}
68+
onChange={(e) => setInput(e.target.value)}
69+
placeholder="Type a message..."
70+
disabled={isLoading}
71+
/>
72+
<Button type="submit" disabled={isLoading}>
73+
Send
74+
</Button>
75+
</div>
76+
</form>
77+
</div>
78+
);
79+
}

src/components/ui/input.tsx

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from "react"
2+
3+
import { cn } from "@/lib/utils"
4+
5+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6+
({ className, type, ...props }, ref) => {
7+
return (
8+
<input
9+
type={type}
10+
className={cn(
11+
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12+
className
13+
)}
14+
ref={ref}
15+
{...props}
16+
/>
17+
)
18+
}
19+
)
20+
Input.displayName = "Input"
21+
22+
export { Input }

src/components/ui/scroll-area.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as React from "react"
2+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
3+
4+
import { cn } from "@/lib/utils"
5+
6+
const ScrollArea = React.forwardRef<
7+
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
8+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
9+
>(({ className, children, ...props }, ref) => (
10+
<ScrollAreaPrimitive.Root
11+
ref={ref}
12+
className={cn("relative overflow-hidden", className)}
13+
{...props}
14+
>
15+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
16+
{children}
17+
</ScrollAreaPrimitive.Viewport>
18+
<ScrollBar />
19+
<ScrollAreaPrimitive.Corner />
20+
</ScrollAreaPrimitive.Root>
21+
))
22+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
23+
24+
const ScrollBar = React.forwardRef<
25+
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
26+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
27+
>(({ className, orientation = "vertical", ...props }, ref) => (
28+
<ScrollAreaPrimitive.ScrollAreaScrollbar
29+
ref={ref}
30+
orientation={orientation}
31+
className={cn(
32+
"flex touch-none select-none transition-colors",
33+
orientation === "vertical" &&
34+
"h-full w-2.5 border-l border-l-transparent p-[1px]",
35+
orientation === "horizontal" &&
36+
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
37+
className
38+
)}
39+
{...props}
40+
>
41+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
42+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
43+
))
44+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
45+
46+
export { ScrollArea, ScrollBar }

src/context/workerContext.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { createContext, useContext, useRef, useState, useEffect } from "react";
22

3+
interface Message {
4+
role: "user" | "assistant";
5+
content: string;
6+
}
7+
38
const WorkerContext = createContext(null);
49

510
export function useWorker() {
@@ -60,7 +65,7 @@ export function WorkerProvider({ children }) {
6065
worker.current?.removeEventListener("message", onMessageReceived);
6166
}, []);
6267

63-
const generate = async (prompt: string): Promise<string> => {
68+
const generate = async (messages: Message[]): Promise<string> => {
6469
return new Promise((resolve, reject) => {
6570
let modelResponse = "";
6671

@@ -88,7 +93,7 @@ export function WorkerProvider({ children }) {
8893
setIsRunning(true);
8994
worker.current.postMessage({
9095
type: "generate",
91-
data: [{ role: "user", content: prompt }],
96+
data: messages,
9297
});
9398
});
9499
};

0 commit comments

Comments
 (0)