From 638aefd3c751a3874ef1b4166ca45880059a0198 Mon Sep 17 00:00:00 2001 From: wongcolin45 Date: Sun, 13 Apr 2025 17:30:01 -0400 Subject: [PATCH 1/2] session history for multiple clients works --- .gitignore | 3 +++ backend/API/chatHistory.json | 10 ++++++++ backend/API/root.py | 33 +++++++++++++++++++------ backend/Chatbot/chatbot.py | 17 ++++--------- frontend/.gitignore | 4 +++ frontend/src/components/Chatbot.tsx | 4 ++- frontend/src/pages/Input/Input.tsx | 16 ++++++++---- frontend/src/pages/Ranking/Rankings.tsx | 9 ++----- frontend/src/pages/sendInputAPI.ts | 25 +++++++++++++++---- package-lock.json | 1 - 10 files changed, 83 insertions(+), 39 deletions(-) create mode 100644 backend/API/chatHistory.json diff --git a/.gitignore b/.gitignore index 87218c0e..cb7b3043 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ node_modules backend/__pycache__ backend/.env + +__pycache__/ +*.pyc diff --git a/backend/API/chatHistory.json b/backend/API/chatHistory.json new file mode 100644 index 00000000..1b194c3c --- /dev/null +++ b/backend/API/chatHistory.json @@ -0,0 +1,10 @@ +{ + "session_id" : { + "query" : "some string in format", + "history": [ + { "role": "user", "parts": ["Hello"] }, + { "role": "model", "parts": ["Hi there! How can I help you today?"] }, + { "role": "user", "parts": ["Tell me about my plan benefits."] } + ] + } +} \ No newline at end of file diff --git a/backend/API/root.py b/backend/API/root.py index 5225b6f1..beb99180 100644 --- a/backend/API/root.py +++ b/backend/API/root.py @@ -12,6 +12,9 @@ from ..Chatbot.chatbot import get_chatbot_response import asyncio +from uuid import uuid4 # for getting session id + + app = FastAPI() from fastapi.middleware.cors import CORSMiddleware @@ -31,21 +34,28 @@ history = {} +chat_history = {} @app.get("/") def root(): return {"FastAPI Running!!!!!"} -@app.post("/chat/message") -async def send_message(data: ChatBotMessage): +@app.get("/session") +def get_session_id(): + session_id = str(uuid4()) + chat_history[session_id] = {} # Optional: initialize chat history + print("session id made: "+session_id) + return {"sessionId" : session_id} - response = get_chatbot_response(data.message, history) +@app.post("/chat/message/{session_id}") +async def send_message(session_id: str, data: ChatBotMessage): + response = get_chatbot_response(data.message, chat_history[session_id]) return {"received": data.message, "response": response} -@app.post("/form/submit") -async def upload_pdfs(form_data: str = Form(...), +@app.post("/form/submit/{session_id}") +async def upload_pdfs(session_id: str, form_data: str = Form(...), files: List[UploadFile] = File(...)): # Process form data into dictionary form_dict = json.loads(form_data) @@ -59,14 +69,11 @@ async def upload_pdfs(form_data: str = Form(...), if file.content_type != 'application/pdf': return {"error": "file is not of type pdf"} - plans = {} premiums = user_input['premium'] for i, j in zip(premiums, files): plans[j] = i - - # upload to s3 and textract # { "name": filename, "text": "TEXT RESULTS " } results = await upload_and_extract(plans) @@ -106,6 +113,8 @@ async def process_plan(plan_name: str, plan_content: str, plan_premium: float): to_frontend = [] + prompt = "" + # Store the results in the history for name, unweighted_scores, weighted_scores, total_score, short_summary, plan_content in results: # {'file_name': 'weighted_scores: dict, 'total_score': float, 'text': str} @@ -121,6 +130,14 @@ async def process_plan(plan_name: str, plan_content: str, plan_premium: float): "totalScore": total_score, "shortSummary": short_summary }) + prompt += f"\n\nPlan Name: {name}\nTotal Score: {total_score}\nPlan Text: {short_summary}\n" + + prompt += ("Please justify the ranking of each plan based on its strengths and weaknesses using the information above " + "Explain each plan clearly, highlighting what makes it better or worse compared to the others.\n") + + session_history = chat_history.setdefault(session_id, {}) + session_history["prompt"] = prompt + return to_frontend diff --git a/backend/Chatbot/chatbot.py b/backend/Chatbot/chatbot.py index ae7d18be..8bd4a488 100644 --- a/backend/Chatbot/chatbot.py +++ b/backend/Chatbot/chatbot.py @@ -6,23 +6,16 @@ load_dotenv() api_key = os.getenv("GEMINI_API_KEY") -def get_chatbot_response(question: str, history: dict) -> str: - client = genai.Client(api_key=api_key) - # Combine plan textract stuff - combined_plans_text = "" - for key, value in history.items(): - combined_plans_text += f"---\nPlan: {key}\n\n{value['text']}\n" +def get_chatbot_response(question: str, session_history: dict) -> str: + client = genai.Client(api_key=api_key) + query = session_history["prompt"] # Prompting type - prompt = f"""You are a healthcare plan assistant. Only use the text provided to answer user questions. - - Below is the text for multiple plans: - {combined_plans_text} - + query += f"""You are a healthcare plan assistant. Only use the text provided to answer user questions. Answer the following question ONLY from the text above: Question: {question} @@ -30,7 +23,7 @@ def get_chatbot_response(question: str, history: dict) -> str: response = client.models.generate_content( model="gemini-2.0-flash", - contents=[prompt], + contents=[query], ) # 5) Return the text response diff --git a/frontend/.gitignore b/frontend/.gitignore index 883e79e8..f0b1ec59 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -44,3 +44,7 @@ amplifytools.xcconfig .secret-* **.sample #amplify-do-not-edit-end + + +__pycache__/ +*.pyc diff --git a/frontend/src/components/Chatbot.tsx b/frontend/src/components/Chatbot.tsx index f4e3b82e..1734f692 100644 --- a/frontend/src/components/Chatbot.tsx +++ b/frontend/src/components/Chatbot.tsx @@ -16,8 +16,10 @@ const Chatbot: React.FC = ({messages, setMessages}) => { setMessages((prev) => [...prev, { message: input, sender: "user" }]); try { + // get the session id + const sessionId = localStorage.getItem("sessionId"); // Replace with your AI API (e.g., OpenAI, Rasa, Dialogflow, etc.) - const response = await fetch("http://127.0.0.1:8000/chat/message", { + const response = await fetch("http://127.0.0.1:8000/chat/message/"+sessionId, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: input }), diff --git a/frontend/src/pages/Input/Input.tsx b/frontend/src/pages/Input/Input.tsx index 2f5eef8d..e65b7106 100644 --- a/frontend/src/pages/Input/Input.tsx +++ b/frontend/src/pages/Input/Input.tsx @@ -9,7 +9,7 @@ import Address from "../../components/form/Address"; import BudgetInfo from "../../components/form/BudgetInfo"; import UploadPdfs from "../../components/form/UploadPdfs"; import Rankings from "../../components/form/Rankings"; -import { sendInputData } from "../sendInputAPI"; +import {getSessionID, sendInputData} from "../sendInputAPI"; const Input: React.FC = ({ results, setResults }) => { const navigate = useNavigate(); @@ -29,15 +29,21 @@ const Input: React.FC = ({ results, setResults }) => { } setLoading(true); const { files, costs, ...formDataWithoutFilesAndCosts } = formData; + + setLoading(false); + setHasSubmittedInput(true); + // get session id and store it + const sessionId = await getSessionID(); + localStorage.setItem("sessionId", sessionId); + + // success = true, if form upload worked someone handle that.. const results = await sendInputData( formDataWithoutFilesAndCosts, files, - costs + costs,sessionId ); - setLoading(false); - setHasSubmittedInput(true); if (results.length === 0) { - console.log("FAILED"); + console.log("GOT 0 RESULTS"); return; } setResults(results); diff --git a/frontend/src/pages/Ranking/Rankings.tsx b/frontend/src/pages/Ranking/Rankings.tsx index 2f9eb8dc..12a920fd 100644 --- a/frontend/src/pages/Ranking/Rankings.tsx +++ b/frontend/src/pages/Ranking/Rankings.tsx @@ -12,7 +12,7 @@ import MenuItem from "@mui/material/MenuItem"; import {SelectChangeEvent} from "@mui/material/Select"; import {ResultsProps} from "../../App"; -import {sendInputData} from "../sendInputAPI.ts"; +import {getSessionID, sendInputData} from "../sendInputAPI.ts"; import {getResults} from "../resultsAPI.ts"; import styles from "./rankings.module.css"; import {useFlow} from "../../context/FlowContext.tsx"; @@ -94,16 +94,11 @@ const Rankings: React.FC = ({results, setResults}) => { } setHasCompletedRankings(true); const fullUserData = {...formData, ...rankings, selectedOption}; - console.log(planCost) - // success = true, if form upload worked someone handle that.. - const success = await sendInputData(fullUserData, files, planCost); navigate("/results"); - getResults().then((newResults) => { - setResults(newResults); - }); + }; return ( diff --git a/frontend/src/pages/sendInputAPI.ts b/frontend/src/pages/sendInputAPI.ts index 1c784623..98abaca3 100644 --- a/frontend/src/pages/sendInputAPI.ts +++ b/frontend/src/pages/sendInputAPI.ts @@ -63,7 +63,8 @@ function structureToJSON(data: FormDataInput, planCost: number[]) { async function sendInputData( data: FormDataInput, files: File[], - planCost: number[] + planCost: number[], + sessionId: string ) { console.log("Post request"); console.log("DATA CHECK:"); @@ -73,8 +74,7 @@ async function sendInputData( // add the user form data const jsonData = structureToJSON(data, planCost); - console.log("JSON DATA CHECK:"); - console.log(jsonData); + formData.append("form_data", JSON.stringify(jsonData)); // add all the uploaded files @@ -82,8 +82,9 @@ async function sendInputData( formData.append("files", file); formData.append("plan_cost", String(planCost[files.indexOf(file)])); }); + console.log("backend making request with sessionId:"+sessionId); const response = await axios.post( - "http://127.0.0.1:8000/form/submit", + "http://127.0.0.1:8000/form/submit/" + sessionId, formData ); console.log("Server response:", response.data); @@ -94,4 +95,18 @@ async function sendInputData( } } -export { sendInputData }; + +async function getSessionID() { + try { + const response = await axios.get("http://127.0.0.1:8000/session"); + console.log("GET SESSION ID:", response.data); + const data = response.data; + + return data["sessionId"]; + } catch (error) { + return null; + } +} + + +export { sendInputData, getSessionID}; diff --git a/package-lock.json b/package-lock.json index 5fc7a93a..1e9c7137 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "forge-spring25-software", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", From 7cd8bc98cf6f616839e2a515bc0c1a59c79b2fa1 Mon Sep 17 00:00:00 2001 From: wongcolin45 Date: Sun, 13 Apr 2025 17:48:36 -0400 Subject: [PATCH 2/2] done --- backend/API/root.py | 148 ++++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 73 deletions(-) diff --git a/backend/API/root.py b/backend/API/root.py index beb99180..f0396884 100644 --- a/backend/API/root.py +++ b/backend/API/root.py @@ -20,12 +20,12 @@ from fastapi.middleware.cors import CORSMiddleware app.add_middleware( - CORSMiddleware, #type: ignore - allow_origins=["http://localhost:5173"], - # Allow frontend in React to connect - allow_credentials=True, - allow_methods=["*"], # Allow all methods - allow_headers=["*"], # Allow all headers + CORSMiddleware, #type: ignore + allow_origins=["http://localhost:5173"], + # Allow frontend in React to connect + allow_credentials=True, + allow_methods=["*"], # Allow all methods + allow_headers=["*"], # Allow all headers ) # To run app: @@ -43,10 +43,9 @@ def root(): @app.get("/session") def get_session_id(): - session_id = str(uuid4()) - chat_history[session_id] = {} # Optional: initialize chat history - print("session id made: "+session_id) - return {"sessionId" : session_id} + session_id = str(uuid4()) + chat_history[session_id] = {} # Optional: initialize chat history + return {"sessionId" : session_id} @app.post("/chat/message/{session_id}") async def send_message(session_id: str, data: ChatBotMessage): @@ -56,88 +55,91 @@ async def send_message(session_id: str, data: ChatBotMessage): @app.post("/form/submit/{session_id}") async def upload_pdfs(session_id: str, form_data: str = Form(...), - files: List[UploadFile] = File(...)): - # Process form data into dictionary - form_dict = json.loads(form_data) + files: List[UploadFile] = File(...)): - # Check against Pydantic Model - user_input = UserInputForm(**form_dict) - user_input = user_input.model_dump() + form_dict = json.loads(form_data) + # Check against Pydantic Model + user_input = UserInputForm(**form_dict) + user_input = user_input.model_dump() - # check files for right type: - for file in files: - if file.content_type != 'application/pdf': - return {"error": "file is not of type pdf"} + # check files for right type: + for file in files: + if file.content_type != 'application/pdf': + return {"error": "file is not of type pdf"} - plans = {} - premiums = user_input['premium'] - for i, j in zip(premiums, files): - plans[j] = i + plans = {} + premiums = user_input['premium'] + for i, j in zip(premiums, files): + plans[j] = i - # upload to s3 and textract - # { "name": filename, "text": "TEXT RESULTS " } - results = await upload_and_extract(plans) + # upload to s3 and textract + # { "name": filename, "text": "TEXT RESULTS " } + results = await upload_and_extract(plans) - # The weights: - weight = user_input['weights'] - del user_input['weights'] + # The weights: + weight = user_input['weights'] + del user_input['weights'] - coverage = user_input['coverage'] - location = user_input['address'] + coverage = user_input['coverage'] + location = user_input['address'] - user_input['health_concerns'] = coverage['personal_health_concerns'] - user_input['budget'] = coverage['budget'] - user_input['zip_code'] = location['zip_code'] - user_input['city'] = location['city'] - user_input['state'] = location['state'] + user_input['health_concerns'] = coverage['personal_health_concerns'] + user_input['budget'] = coverage['budget'] + user_input['zip_code'] = location['zip_code'] + user_input['city'] = location['city'] + user_input['state'] = location['state'] - weights = {k: v/10 for k, v in weight.items()} + weights = {k: v/10 for k, v in weight.items()} - # The premiums: - async def process_plan(plan_name: str, plan_content: str, plan_premium: float): - ranking_instance = WeightedPlanRanking(weights, plan_content, - user_input, plan_premium) - unweighted_scores = await ranking_instance.ranking_logics() - weighted_scores = ranking_instance.pair_keys() - total_score = ranking_instance.total_scores() - summary = PlanSummaries(plan_content, user_input['age'], user_input['budget']) - short_summary = await summary.get_short_summary() + # The premiums: + async def process_plan(plan_name: str, plan_content: str, + plan_premium: float): + ranking_instance = WeightedPlanRanking(weights, plan_content, + user_input, plan_premium, + user_input['individual_bool']) + unweighted_scores = await ranking_instance.ranking_logics() + weighted_scores = ranking_instance.pair_keys() + total_score = ranking_instance.total_scores() + summary = PlanSummaries(plan_content, user_input['age'], + user_input['budget']) + short_summary = await summary.get_short_summary() + return plan_name, unweighted_scores, weighted_scores, total_score, short_summary, plan_content - return plan_name, unweighted_scores, weighted_scores, total_score, short_summary, plan_content + tasks = [process_plan(name, content[0], content[1]) for name, content in + results.items()] - tasks = [process_plan(name, content[0], content[1]) for name, content in results.items()] + # Run all tasks concurrently + results = await asyncio.gather(*tasks) - # Run all tasks concurrently - results = await asyncio.gather(*tasks) - to_frontend = [] + to_frontend = [] - prompt = "" + prompt = "" - # Store the results in the history - for name, unweighted_scores, weighted_scores, total_score, short_summary, plan_content in results: - # {'file_name': 'weighted_scores: dict, 'total_score': float, 'text': str} - history[name] = { - "weighted_scores": weighted_scores, - "total_score": total_score, - "text": plan_content - } + # Store the results in the history + for name, unweighted_scores, weighted_scores, total_score, short_summary, plan_content in results: + # {'file_name': 'weighted_scores: dict, 'total_score': float, 'text': str} + history[name] = { + "weighted_scores": weighted_scores, + "total_score": total_score, + "text": plan_content + } - to_frontend.append({ - "name": name, - "weightedScores": weighted_scores, - "totalScore": total_score, - "shortSummary": short_summary - }) - prompt += f"\n\nPlan Name: {name}\nTotal Score: {total_score}\nPlan Text: {short_summary}\n" + to_frontend.append({ + "name": name, + "weightedScores": weighted_scores, + "totalScore": total_score, + "shortSummary": short_summary + }) + prompt += f"\n\nPlan Name: {name}\nTotal Score: {total_score}\nPlan Text: {short_summary}\n" - prompt += ("Please justify the ranking of each plan based on its strengths and weaknesses using the information above " - "Explain each plan clearly, highlighting what makes it better or worse compared to the others.\n") + prompt += ("Please justify the ranking of each plan based on its strengths and weaknesses using the information above " + "Explain each plan clearly, highlighting what makes it better or worse compared to the others.\n") - session_history = chat_history.setdefault(session_id, {}) - session_history["prompt"] = prompt + session_history = chat_history.setdefault(session_id, {}) + session_history["prompt"] = prompt - return to_frontend + return to_frontend