diff --git a/web-app/components.json b/web-app/components.json
new file mode 100644
index 0000000..7a2e4d8
--- /dev/null
+++ b/web-app/components.json
@@ -0,0 +1,15 @@
+{
+ "style": "default",
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "src/app/globals.css",
+ "baseColor": "zinc",
+ "cssVariables": true
+ },
+ "rsc": false,
+ "tsx": false,
+ "aliases": {
+ "utils": "~/lib/utils",
+ "components": "~/components"
+ }
+}
diff --git a/web-app/package-lock.json b/web-app/package-lock.json
index 9cba0a3..a0bc9b4 100644
--- a/web-app/package-lock.json
+++ b/web-app/package-lock.json
@@ -13,6 +13,7 @@
"@vitejs/plugin-react": "^5.0.4",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
+ "class-variance-authority": "^0.7.1",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.1.0",
@@ -26,10 +27,12 @@
"react-markdown": "^10.1.0",
"react-router": "^7.9.4",
"react-router-dom": "^7.9.4",
+ "shadcn-ui": "^0.9.5",
"vite-jsconfig-paths": "^2.0.1"
},
"devDependencies": {
"@eslint/js": "^9.38.0",
+ "daisyui": "^5.3.7",
"eslint": "^9.38.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-react": "^7.37.5",
@@ -2607,12 +2610,33 @@
"node": ">=18"
}
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2735,6 +2759,16 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
+ "node_modules/daisyui": {
+ "version": "5.3.7",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.3.7.tgz",
+ "integrity": "sha512-0+8PaSGift0HlIQABCeZzWOBV5Nx/vsI2TihB9hbaEyZENPlZZz+se2JnAH5rz9gBYTyDLB7NJup8hkREr6WBw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/saadeghi/daisyui?sponsor=1"
+ }
+ },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -7558,6 +7592,30 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
+ "node_modules/shadcn-ui": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.9.5.tgz",
+ "integrity": "sha512-dsBQWpdLLYCdSdmvOmu53nJhhWnQD1OiblhuhkI4rPYxPKTyfbmZ2NTJHWMu1fXN9PTfN6IVK5vvh+BrjHJx2g==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.4.1"
+ },
+ "bin": {
+ "shadcn-ui": "dist/index.js"
+ }
+ },
+ "node_modules/shadcn-ui/node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/web-app/package.json b/web-app/package.json
index 4606845..84361bb 100644
--- a/web-app/package.json
+++ b/web-app/package.json
@@ -18,6 +18,7 @@
"@vitejs/plugin-react": "^5.0.4",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
+ "class-variance-authority": "^0.7.1",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.1.0",
@@ -31,11 +32,13 @@
"react-markdown": "^10.1.0",
"react-router": "^7.9.4",
"react-router-dom": "^7.9.4",
+ "shadcn-ui": "^0.9.5",
"vite-jsconfig-paths": "^2.0.1"
},
"packageManager": ">=npm@10.9.0",
"devDependencies": {
"@eslint/js": "^9.38.0",
+ "daisyui": "^5.3.7",
"eslint": "^9.38.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-react": "^7.37.5",
diff --git a/web-app/src/app/index.jsx b/web-app/src/app/index.jsx
index 86a06e6..ece1d51 100644
--- a/web-app/src/app/index.jsx
+++ b/web-app/src/app/index.jsx
@@ -5,6 +5,7 @@ function App() {
return (
);
}
diff --git a/web-app/src/components/layouts/chat-layout.jsx b/web-app/src/components/layouts/chat-layout.jsx
index 78dff4f..ce45310 100644
--- a/web-app/src/components/layouts/chat-layout.jsx
+++ b/web-app/src/components/layouts/chat-layout.jsx
@@ -3,20 +3,6 @@ import ChatHeader from "src/components/ui/chat/chat-header";
import ChatWindow from "src/components/ui/chat/chat-window";
import MessageInput from "src/components/ui/chat/message-input";
-import { GoogleGenAI } from "@google/genai"
-
-const ai = new GoogleGenAI({ apiKey: import.meta.env.GEMINI_API_KEY })
-
-async function AIRepsponse(userInputArray) {
- const response = await ai.models.generateContent({
- model: "gemini-2.5-flash",
- contents: userInputArray
- })
- return response.text
- }
-
-let userInput = []
-
export default function ChatLayout() {
const [messages, setMessages] = useState([
{
@@ -25,23 +11,21 @@ export default function ChatLayout() {
},
]);
- async function handleSend(text) {
- userInput.push(text)
- const res = await AIRepsponse(userInput)
-
+ function handleSend(text) {
const userMsg = { role: "user", content: text };
setMessages((s) => [...s, userMsg]);
+ // fake assistant reply after short delay
setTimeout(() => {
setMessages((s) => [
...s,
- { role: "assistant", content: res },
+ { role: "assistant", content: `You said: ${text}` },
]);
}, 600);
}
return (
-
+
diff --git a/web-app/src/components/ui/button/delete-button.jsx b/web-app/src/components/ui/button/delete-button.jsx
index 61b4df0..c75c306 100644
--- a/web-app/src/components/ui/button/delete-button.jsx
+++ b/web-app/src/components/ui/button/delete-button.jsx
@@ -5,7 +5,7 @@ export default function FlameButton({ onClick }) {
return (
diff --git a/web-app/src/components/ui/button/down-button.jsx b/web-app/src/components/ui/button/down-button.jsx
index c5a5d9f..b51dc3f 100644
--- a/web-app/src/components/ui/button/down-button.jsx
+++ b/web-app/src/components/ui/button/down-button.jsx
@@ -6,7 +6,7 @@ export default function DownButton({ onClick }) {
return (
diff --git a/web-app/src/components/ui/button/schematic-button.jsx b/web-app/src/components/ui/button/schematic-button.jsx
index 027d00e..c7e3c00 100644
--- a/web-app/src/components/ui/button/schematic-button.jsx
+++ b/web-app/src/components/ui/button/schematic-button.jsx
@@ -1,16 +1,71 @@
-import React from "react";
+import React, { useState, useRef } from "react";
+import { X } from "lucide-react";
import { motion } from "motion/react";
import { FilePlus2 } from "lucide-react";
-export default function SchematicButton({ onClick }) {
+export default function SchematicButton({ onFiles }) {
+ const [filesList, setFilesList] = useState([]);
+ const inputRef = useRef(null);
+
+ function handleFiles(e) {
+ const files = Array.from(e.target.files || []);
+ if (files.length === 0) return;
+
+ setFilesList((s) => [...s, ...files]);
+ if (onFiles) onFiles(files);
+ if (inputRef.current) inputRef.current.value = null;
+ }
+
+ function removeFile(index) {
+ setFilesList((s) => {
+ const copy = [...s];
+ copy.splice(index, 1);
+ return copy;
+ });
+ }
+
return (
-
-
-
+
+
+
+ {filesList.length > 0 && (
+
+ {filesList.map((f, i) => (
+
+ {f.name}
+
+
+ ))}
+
+ )}
+
);
}
diff --git a/web-app/src/components/ui/chat/chat-header.jsx b/web-app/src/components/ui/chat/chat-header.jsx
index 89b5738..2a75129 100644
--- a/web-app/src/components/ui/chat/chat-header.jsx
+++ b/web-app/src/components/ui/chat/chat-header.jsx
@@ -1,13 +1,17 @@
import React from "react";
+import DeleteButton from "src/components/ui/button/delete-button";
+import SchematicButton from "../button/schematic-button";
export default function ChatHeader({ title = "Title of Chat" }) {
return (
-
-
diff --git a/web-app/src/components/ui/chat/message-input.jsx b/web-app/src/components/ui/chat/message-input.jsx
index 951a70a..8e48f34 100644
--- a/web-app/src/components/ui/chat/message-input.jsx
+++ b/web-app/src/components/ui/chat/message-input.jsx
@@ -1,12 +1,17 @@
-import React, { useState } from "react";
+import React, { useState, useRef, useEffect } from "react";
import DeleteButton from "src/components/ui/button/delete-button";
import DownButton from "src/components/ui/button/down-button";
-import SchematicButton from "src/components/ui/button/schematic-button";
import { motion } from "motion/react";
import { BotMessageSquare } from "lucide-react";
export default function MessageInput({ onSend }) {
const [text, setText] = useState("");
+ const textareaRef = useRef(null);
+
+ useEffect(() => {
+ // ensure correct initial height
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
+ }, []);
function handleSubmit(e) {
e.preventDefault();
@@ -17,29 +22,37 @@ export default function MessageInput({ onSend }) {
return (