diff --git a/fe_repo/package.json b/fe_repo/package.json index 19bc34f3b..6f0fa62ae 100644 --- a/fe_repo/package.json +++ b/fe_repo/package.json @@ -13,17 +13,20 @@ "preview": "vite preview" }, "dependencies": { + "axios": "^1.7.7", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@mui/base": "5.0.0-beta.58", "@mui/system": "^6.1.1", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "uuid": "^10.0.0" }, "devDependencies": { "@eslint/js": "^9.9.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/uuid": "^10.0.0", "@vitejs/plugin-react-swc": "^3.5.0", "@vitest/coverage-v8": "2.1.2", "autoprefixer": "^10.4.20", diff --git a/fe_repo/pnpm-lock.yaml b/fe_repo/pnpm-lock.yaml index 15d469276..195fc8bc4 100644 --- a/fe_repo/pnpm-lock.yaml +++ b/fe_repo/pnpm-lock.yaml @@ -20,12 +20,18 @@ importers: '@mui/system': specifier: ^6.1.1 version: 6.1.1(@emotion/react@11.13.3(@types/react@18.3.8)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.8)(react@18.3.1))(@types/react@18.3.8)(react@18.3.1))(@types/react@18.3.8)(react@18.3.1) + axios: + specifier: ^1.7.7 + version: 1.7.7 react: specifier: ^18.3.1 version: 18.3.1 react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + uuid: + specifier: ^10.0.0 + version: 10.0.0 devDependencies: '@eslint/js': specifier: ^9.9.0 @@ -36,6 +42,9 @@ importers: '@types/react-dom': specifier: ^18.3.0 version: 18.3.0 + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 '@vitejs/plugin-react-swc': specifier: ^3.5.0 version: 3.7.0(vite@5.4.7) @@ -672,6 +681,9 @@ packages: '@types/react@18.3.8': resolution: {integrity: sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@typescript-eslint/eslint-plugin@8.6.0': resolution: {integrity: sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -823,6 +835,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -830,6 +845,9 @@ packages: peerDependencies: postcss: ^8.1.0 + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} @@ -908,6 +926,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -950,6 +972,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1082,10 +1108,23 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1291,6 +1330,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1447,6 +1494,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1660,6 +1710,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + vite-node@2.1.2: resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2278,6 +2332,8 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 + '@types/uuid@10.0.0': {} + '@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.1 @@ -2464,6 +2520,8 @@ snapshots: assertion-error@2.0.1: {} + asynckit@0.4.0: {} + autoprefixer@10.4.20(postcss@8.4.47): dependencies: browserslist: 4.23.3 @@ -2474,6 +2532,14 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 + axios@1.7.7: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-plugin-macros@3.1.0: dependencies: '@babel/runtime': 7.25.6 @@ -2559,6 +2625,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@4.1.1: {} concat-map@0.0.1: {} @@ -2591,6 +2661,8 @@ snapshots: deep-is@0.1.4: {} + delayed-stream@1.0.0: {} + didyoumean@1.2.2: {} dlv@1.1.3: {} @@ -2759,11 +2831,19 @@ snapshots: flatted@3.3.1: {} + follow-redirects@1.15.9: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fraction.js@4.3.7: {} fsevents@2.3.3: @@ -2942,6 +3022,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -3074,6 +3160,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -3300,6 +3388,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@10.0.0: {} + vite-node@2.1.2: dependencies: cac: 6.7.14 diff --git a/fe_repo/src/App.tsx b/fe_repo/src/App.tsx index 98ff4f1aa..8c1ae70e7 100644 --- a/fe_repo/src/App.tsx +++ b/fe_repo/src/App.tsx @@ -1,28 +1,49 @@ -import { Header, Content, ChatBox } from './pages' -import { useState } from "react"; -import { sendMessage } from "./functions/api.ts"; +import {ChatBox, Content, Header} from './pages' +import {useState} from "react"; +import {sendMessage} from "./functions/api.ts"; export type Message = { text: string, isUser: boolean, } + function App() { const [messages, setMessages] = useState([]) const onSendMessage = (message: string) => { - setMessages((messages) => [...messages, { text: message, isUser: true }]); + const sendingMessage = { + text: 'Sending message...', + isUser: false + }; + + setMessages((messages) => [...messages, + { + text: message, + isUser: true + }, + sendingMessage]); setTimeout(() => { // handle message sent, update conversation section - const response = { text: sendMessage(message), isUser: false } - setMessages((messages) => [...messages, response]); + // const response = {text: sendMessage(message), isUser: false} + // remove sending prompt message + sendingMessage.text = sendMessage(message); + setMessages((messages) => [...messages]); }, 1000); } + const mainContainerStyle = { + display: 'flex', + flexDirection: 'column' as const as 'column', + height: '100vh', + backgroundColor: '#f0f4f8', // Light pastel background + padding: '20px', + }; + return ( -
-
+
+
diff --git a/fe_repo/src/functions/api.ts b/fe_repo/src/functions/api.ts index ba63d5d6c..cdd81bdee 100644 --- a/fe_repo/src/functions/api.ts +++ b/fe_repo/src/functions/api.ts @@ -1,3 +1,6 @@ +import {v4 as uuidv4} from 'uuid'; +import axios from "axios"; + // TODO: Implement API functions export function sendMessage(message: string) { @@ -23,14 +26,34 @@ export function sendMessage(message: string) { } export function uploadFile(file: File) { - // TODO: Implement file upload after api schema is provided console.log("uploading file", file.name); - // const formData = new FormData(); - // formData.append('file', file); - // fetch('/api/upload', { - // method: 'POST', - // body: formData, - // }).then(response => response.json()) - // .then(data => console.log('File upload success:', data)) - // .catch(error => console.error('Error uploading file:', error)); + axios.post('/api/upload', {file: file, user_id: getUserId()}, + {headers: {'Content-Type': 'multipart/form-data'}}) + .catch(error => console.error('Error fetching response', error)); +} + + +// user id +export function getUserId() { + // TODO: Implement login using google to replace random uuid generation + let uuid; + if (typeof window !== 'undefined') { + uuid = localStorage.getItem('userId') || uuidv4(); + } else { + uuid = uuidv4(); + } + setUserId(uuid); + return uuid; +} + +export function setUserId(userId: string) { + if (!userId || typeof window === 'undefined') { + return; + } + localStorage.setItem('userId', userId); +} + +// login using google +export function login() { + return getUserId(); } \ No newline at end of file diff --git a/fe_repo/src/main.tsx b/fe_repo/src/main.tsx index 6f4ac9bcc..0d5193e08 100644 --- a/fe_repo/src/main.tsx +++ b/fe_repo/src/main.tsx @@ -1,10 +1,10 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' +import {StrictMode} from 'react' +import {createRoot} from 'react-dom/client' import App from './App.tsx' import './index.css' createRoot(document.getElementById('root')!).render( - + , ) diff --git a/fe_repo/src/pages/ChatBox.tsx b/fe_repo/src/pages/ChatBox.tsx index 2b6adcf4a..f6da232f2 100644 --- a/fe_repo/src/pages/ChatBox.tsx +++ b/fe_repo/src/pages/ChatBox.tsx @@ -1,12 +1,13 @@ -import { UploadIcon } from "../icons" -import { Textarea } from '../components' -import { useState } from "react"; -import { uploadFile } from "../functions/api.ts"; +import {UploadIcon} from "../icons" +import {Textarea} from '../components' +import {useState} from "react"; +import {uploadFile} from "../functions/api.ts"; interface IChatBoxProps { onSendMessage: (message: string) => void; } -export const ChatBox = ({ onSendMessage }: IChatBoxProps) => { + +export const ChatBox = ({onSendMessage}: IChatBoxProps) => { const [message, setMessage] = useState(""); @@ -19,33 +20,44 @@ export const ChatBox = ({ onSendMessage }: IChatBoxProps) => { setMessage(""); } + const chatBoxStyle = { + borderWidth: '2px', + padding: '16px', + display: 'flex', + alignItems: 'center' as const as 'center', + backgroundColor: '#ffffff', // White background for chat input + borderRadius: '8px', + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', + }; + return ( -
+
{ // Upload files - const input = document.createElement("input"); - input.type = 'file'; - input.accept = 'image/*,.pdf,.doc,.docx'; - // @ts-expect-error @Linchen - input.onchange = handleFiles; - input.click(); - - function handleFiles(event: { target: HTMLInputElement }) { - if (!event.target || !event.target.files) return; - const file = event.target.files[0]; + const handleFiles = (event: Event) => { + const files = (event.target as HTMLInputElement)?.files; + if (!files || files.length === 0) return; + const file = files[0]; if (file) { // upload if file is valid uploadFile(file); } } + + const input = document.createElement("input"); + input.type = 'file'; + input.accept = '.pdf'; + input.onchange = handleFiles; + input.click(); }} > - +