kenny commited on
Commit
a2b51e3
·
1 Parent(s): a73fb2c

Initial commit

Browse files
next.config.js ADDED
@@ -0,0 +1 @@
 
 
1
+
src/app/page.tsx CHANGED
@@ -1,101 +1,9 @@
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-8 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 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
16
- <li className="mb-2">
17
- Get started by editing{" "}
18
- <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
19
- src/app/page.tsx
20
- </code>
21
- .
22
- </li>
23
- <li>Save and see your changes instantly.</li>
24
- </ol>
25
-
26
- <div className="flex gap-4 items-center flex-col sm:flex-row">
27
- <a
28
- 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] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
29
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30
- target="_blank"
31
- rel="noopener noreferrer"
32
- >
33
- <Image
34
- className="dark:invert"
35
- src="/vercel.svg"
36
- alt="Vercel logomark"
37
- width={20}
38
- height={20}
39
- />
40
- Deploy now
41
- </a>
42
- <a
43
- 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 text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
44
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
45
- target="_blank"
46
- rel="noopener noreferrer"
47
- >
48
- Read our docs
49
- </a>
50
- </div>
51
- </main>
52
- <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
53
- <a
54
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
55
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
56
- target="_blank"
57
- rel="noopener noreferrer"
58
- >
59
- <Image
60
- aria-hidden
61
- src="/file.svg"
62
- alt="File icon"
63
- width={16}
64
- height={16}
65
- />
66
- Learn
67
- </a>
68
- <a
69
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
70
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
71
- target="_blank"
72
- rel="noopener noreferrer"
73
- >
74
- <Image
75
- aria-hidden
76
- src="/window.svg"
77
- alt="Window icon"
78
- width={16}
79
- height={16}
80
- />
81
- Examples
82
- </a>
83
- <a
84
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
85
- href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
86
- target="_blank"
87
- rel="noopener noreferrer"
88
- >
89
- <Image
90
- aria-hidden
91
- src="/globe.svg"
92
- alt="Globe icon"
93
- width={16}
94
- height={16}
95
- />
96
- Go to nextjs.org →
97
- </a>
98
- </footer>
99
- </div>
100
  );
101
  }
 
1
+ import Chat from "@/components/Chat";
2
 
3
  export default function Home() {
4
  return (
5
+ <main className="min-h-screen p-4 sm:p-8">
6
+ <Chat />
7
+ </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  );
9
  }
src/components/Chat.tsx ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { useState } from "react";
3
+ import ChatMessage from "./ChatMessage";
4
+ import FileUpload from "./FileUpload";
5
+
6
+ interface Message {
7
+ type: "user" | "assistant";
8
+ content: string;
9
+ }
10
+
11
+ export default function Chat() {
12
+ const [messages, setMessages] = useState<Message[]>([]);
13
+ const [input, setInput] = useState("");
14
+ const [isLoading, setIsLoading] = useState(false);
15
+ const [hasUploadedFile, setHasUploadedFile] = useState(false);
16
+
17
+ const handleSubmit = async (e: React.FormEvent) => {
18
+ e.preventDefault();
19
+ if (!input.trim() || isLoading) return;
20
+
21
+ const userMessage = input.trim();
22
+ setInput("");
23
+ setMessages((prev) => [...prev, { type: "user", content: userMessage }]);
24
+ setIsLoading(true);
25
+
26
+ try {
27
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/ask`, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify({ text: userMessage }),
31
+ });
32
+
33
+ const data = await response.json();
34
+ setMessages((prev) => [
35
+ ...prev,
36
+ { type: "assistant", content: data.answer },
37
+ ]);
38
+ } catch (error) {
39
+ console.error("Error:", error);
40
+ setMessages((prev) => [
41
+ ...prev,
42
+ {
43
+ type: "assistant",
44
+ content: "Sorry, there was an error processing your request.",
45
+ },
46
+ ]);
47
+ } finally {
48
+ setIsLoading(false);
49
+ }
50
+ };
51
+
52
+ return (
53
+ <div className="w-full max-w-4xl mx-auto h-[80vh] flex flex-col">
54
+ {!hasUploadedFile ? (
55
+ <FileUpload onUploadSuccess={() => setHasUploadedFile(true)} />
56
+ ) : (
57
+ <>
58
+ <div className="flex-1 overflow-y-auto p-4 space-y-4">
59
+ {messages.map((message, index) => (
60
+ <ChatMessage
61
+ key={index}
62
+ type={message.type}
63
+ content={message.content}
64
+ />
65
+ ))}
66
+ {isLoading && (
67
+ <div className="text-center text-gray-500">Loading...</div>
68
+ )}
69
+ </div>
70
+ <form onSubmit={handleSubmit} className="p-4 border-t">
71
+ <div className="flex gap-2">
72
+ <input
73
+ type="text"
74
+ value={input}
75
+ onChange={(e) => setInput(e.target.value)}
76
+ placeholder="Ask a question about your PDF..."
77
+ className="flex-1 p-2 border rounded-lg dark:bg-gray-800"
78
+ disabled={isLoading}
79
+ />
80
+ <button
81
+ type="submit"
82
+ disabled={isLoading}
83
+ className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50"
84
+ >
85
+ Send
86
+ </button>
87
+ </div>
88
+ </form>
89
+ </>
90
+ )}
91
+ </div>
92
+ );
93
+ }
src/components/ChatMessage.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ type MessageType = "user" | "assistant";
2
+
3
+ interface ChatMessageProps {
4
+ type: MessageType;
5
+ content: string;
6
+ }
7
+
8
+ export default function ChatMessage({ type, content }: ChatMessageProps) {
9
+ return (
10
+ <div
11
+ className={`flex ${
12
+ type === "user" ? "justify-end" : "justify-start"
13
+ } mb-4`}
14
+ >
15
+ <div
16
+ className={`max-w-[80%] rounded-lg px-4 py-2 ${
17
+ type === "user"
18
+ ? "bg-blue-500 text-white"
19
+ : "bg-gray-100 dark:bg-gray-800"
20
+ }`}
21
+ >
22
+ {content}
23
+ </div>
24
+ </div>
25
+ );
26
+ }
src/components/FileUpload.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+
3
+ export default function FileUpload({
4
+ onUploadSuccess,
5
+ }: {
6
+ onUploadSuccess: () => void;
7
+ }) {
8
+ const [uploading, setUploading] = useState(false);
9
+
10
+ const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
11
+ const file = e.target.files?.[0];
12
+ if (!file) return;
13
+
14
+ setUploading(true);
15
+ const formData = new FormData();
16
+ formData.append("file", file);
17
+
18
+ try {
19
+ const response = await fetch(
20
+ `${process.env.NEXT_PUBLIC_API_URL}/upload`,
21
+ {
22
+ method: "POST",
23
+ body: formData,
24
+ }
25
+ );
26
+
27
+ if (!response.ok) throw new Error("Upload failed");
28
+ onUploadSuccess();
29
+ } catch (error) {
30
+ console.error("Upload error:", error);
31
+ } finally {
32
+ setUploading(false);
33
+ }
34
+ };
35
+
36
+ return (
37
+ <div className="w-full max-w-md">
38
+ <label className="flex flex-col items-center p-4 border-2 border-dashed rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900">
39
+ <span className="text-sm mb-2">
40
+ {uploading ? "Uploading..." : "Upload PDF"}
41
+ </span>
42
+ <input
43
+ type="file"
44
+ className="hidden"
45
+ accept=".pdf"
46
+ onChange={handleFileUpload}
47
+ disabled={uploading}
48
+ />
49
+ </label>
50
+ </div>
51
+ );
52
+ }