tech-envision commited on
Commit
a40a50f
·
1 Parent(s): 71fac34

Add Next.js chat frontend

Browse files
frontend/.env.example ADDED
@@ -0,0 +1 @@
 
 
1
+ NEXT_PUBLIC_API_URL=http://localhost:8000
frontend/src/app/page.tsx CHANGED
@@ -1,103 +1,5 @@
1
- import Image from "next/image";
2
 
3
  export default function Home() {
4
- return (
5
- <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
6
- <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
7
- <Image
8
- className="dark:invert"
9
- src="/next.svg"
10
- alt="Next.js logo"
11
- width={180}
12
- height={38}
13
- priority
14
- />
15
- <ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
16
- <li className="mb-2 tracking-[-.01em]">
17
- Get started by editing{" "}
18
- <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
19
- src/app/page.tsx
20
- </code>
21
- .
22
- </li>
23
- <li className="tracking-[-.01em]">
24
- Save and see your changes instantly.
25
- </li>
26
- </ol>
27
-
28
- <div className="flex gap-4 items-center flex-col sm:flex-row">
29
- <a
30
- className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
31
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
32
- target="_blank"
33
- rel="noopener noreferrer"
34
- >
35
- <Image
36
- className="dark:invert"
37
- src="/vercel.svg"
38
- alt="Vercel logomark"
39
- width={20}
40
- height={20}
41
- />
42
- Deploy now
43
- </a>
44
- <a
45
- className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
46
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
47
- target="_blank"
48
- rel="noopener noreferrer"
49
- >
50
- Read our docs
51
- </a>
52
- </div>
53
- </main>
54
- <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
55
- <a
56
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
57
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
58
- target="_blank"
59
- rel="noopener noreferrer"
60
- >
61
- <Image
62
- aria-hidden
63
- src="/file.svg"
64
- alt="File icon"
65
- width={16}
66
- height={16}
67
- />
68
- Learn
69
- </a>
70
- <a
71
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
72
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
73
- target="_blank"
74
- rel="noopener noreferrer"
75
- >
76
- <Image
77
- aria-hidden
78
- src="/window.svg"
79
- alt="Window icon"
80
- width={16}
81
- height={16}
82
- />
83
- Examples
84
- </a>
85
- <a
86
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
87
- href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
88
- target="_blank"
89
- rel="noopener noreferrer"
90
- >
91
- <Image
92
- aria-hidden
93
- src="/globe.svg"
94
- alt="Globe icon"
95
- width={16}
96
- height={16}
97
- />
98
- Go to nextjs.org →
99
- </a>
100
- </footer>
101
- </div>
102
- );
103
  }
 
1
+ import ChatApp from "../components/ChatApp";
2
 
3
  export default function Home() {
4
+ return <ChatApp />;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  }
frontend/src/components/ChatApp.tsx ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState, useRef } from 'react';
4
+ import { streamChat, ChatRequest } from '../utils/api';
5
+
6
+ interface Message {
7
+ role: 'user' | 'assistant';
8
+ content: string;
9
+ }
10
+
11
+ export default function ChatApp() {
12
+ const [messages, setMessages] = useState<Message[]>([]);
13
+ const [input, setInput] = useState('');
14
+ const [loading, setLoading] = useState(false);
15
+ const endRef = useRef<HTMLDivElement>(null);
16
+
17
+ const scrollToEnd = () => {
18
+ endRef.current?.scrollIntoView({ behavior: 'smooth' });
19
+ };
20
+
21
+ const sendMessage = async () => {
22
+ if (!input.trim() || loading) return;
23
+
24
+ const prompt = input;
25
+ setInput('');
26
+ setMessages((prev) => [...prev, { role: 'user', content: prompt }, { role: 'assistant', content: '' }]);
27
+ setLoading(true);
28
+ try {
29
+ const req: ChatRequest = { user: 'demo', session: 'default', prompt };
30
+ for await (const chunk of streamChat(req)) {
31
+ setMessages((prev) => {
32
+ const msgs = [...prev];
33
+ msgs[msgs.length - 1] = {
34
+ role: 'assistant',
35
+ content: msgs[msgs.length - 1].content + chunk,
36
+ };
37
+ return msgs;
38
+ });
39
+ scrollToEnd();
40
+ }
41
+ } catch (err) {
42
+ console.error(err);
43
+ setMessages((prev) => {
44
+ const msgs = [...prev];
45
+ msgs[msgs.length - 1] = {
46
+ role: 'assistant',
47
+ content: 'Error retrieving response',
48
+ };
49
+ return msgs;
50
+ });
51
+ } finally {
52
+ setLoading(false);
53
+ scrollToEnd();
54
+ }
55
+ };
56
+
57
+ const onSubmit = (e: React.FormEvent) => {
58
+ e.preventDefault();
59
+ void sendMessage();
60
+ };
61
+
62
+ return (
63
+ <div className="flex flex-col max-w-2xl mx-auto h-screen p-4">
64
+ <div className="flex-1 overflow-y-auto space-y-4">
65
+ {messages.map((msg, idx) => (
66
+ <div key={idx} className={msg.role === 'user' ? 'text-right' : 'text-left'}>
67
+ <span className="px-3 py-2 inline-block rounded bg-gray-200 dark:bg-gray-700">
68
+ {msg.content}
69
+ </span>
70
+ </div>
71
+ ))}
72
+ <div ref={endRef} />
73
+ </div>
74
+ <form onSubmit={onSubmit} className="mt-4 flex gap-2">
75
+ <input
76
+ type="text"
77
+ className="flex-1 border rounded px-3 py-2 text-black"
78
+ value={input}
79
+ onChange={(e) => setInput(e.target.value)}
80
+ placeholder="Type your message..."
81
+ />
82
+ <button
83
+ type="submit"
84
+ className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
85
+ disabled={loading}
86
+ >
87
+ Send
88
+ </button>
89
+ </form>
90
+ </div>
91
+ );
92
+ }
frontend/src/utils/api.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface ChatRequest {
2
+ user: string;
3
+ session: string;
4
+ prompt: string;
5
+ }
6
+
7
+ export async function* streamChat(
8
+ req: ChatRequest
9
+ ): AsyncGenerator<string> {
10
+ const url = `${process.env.NEXT_PUBLIC_API_URL}/chat/stream`;
11
+ const res = await fetch(url, {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ },
16
+ body: JSON.stringify(req),
17
+ });
18
+
19
+ if (!res.ok || !res.body) {
20
+ throw new Error('API request failed');
21
+ }
22
+
23
+ const reader = res.body.getReader();
24
+ const decoder = new TextDecoder();
25
+
26
+ while (true) {
27
+ const { value, done } = await reader.read();
28
+ if (done) break;
29
+ yield decoder.decode(value);
30
+ }
31
+ }