├── .gitignore ├── backend ├── sample.env ├── NHS-medicines-scraper.py └── app.py ├── frontend ├── src │ ├── App.css │ ├── assets │ │ ├── nhs_attribution.png │ │ └── react.svg │ ├── main.jsx │ └── App.jsx ├── postcss.config.js ├── tailwind.config.js ├── vite.config.js ├── .gitignore ├── index.html ├── .eslintrc.cjs └── package.json ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | chroma_db/ 3 | .vscode/ 4 | testdata/ -------------------------------------------------------------------------------- /backend/sample.env: -------------------------------------------------------------------------------- 1 | NHS_API_KEY= 2 | LANGCHAIN_API_KEY= 3 | OPENAI_API_KEY= -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/assets/nhs_attribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiourrego/medicines-rag-chatbot/HEAD/frontend/src/assets/nhs_attribution.png -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | 5 | ReactDOM.createRoot(document.getElementById('root')).render( 6 | 7 | 8 | , 9 | ) 10 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | import daisyui from "daisyui" 3 | 4 | export default { 5 | content: [ 6 | "./src/App.jsx", 7 | "./index.html" 8 | ], 9 | theme: { 10 | extend: {}, 11 | }, 12 | plugins: [ 13 | daisyui, 14 | ], 15 | } 16 | 17 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | proxy: { 9 | '/messages': 'http://localhost:5000', 10 | } 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nhs-medication-rag", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "marked": "^12.0.2", 14 | "marked-react": "^2.0.0", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^18.2.66", 20 | "@types/react-dom": "^18.2.22", 21 | "@vitejs/plugin-react": "^4.2.1", 22 | "autoprefixer": "^10.4.19", 23 | "daisyui": "^4.11.1", 24 | "eslint": "^8.57.0", 25 | "eslint-plugin-react": "^7.34.1", 26 | "eslint-plugin-react-hooks": "^4.6.0", 27 | "eslint-plugin-react-refresh": "^0.4.6", 28 | "postcss": "^8.4.38", 29 | "tailwindcss": "^3.4.3", 30 | "vite": "^5.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from 'react' 2 | import './App.css' 3 | import Markdown from 'marked-react'; 4 | import nhslogo from './assets/nhs_attribution.png' 5 | 6 | function App() { 7 | const [chatHistory, setChatHistory] = useState({"messages": []}); 8 | const [chatCurrent, setChatCurrent] = useState(''); 9 | const [urlHistory, setURLHistory] = useState([]); 10 | 11 | const handleSendMessage = async() => { 12 | if (chatCurrent.trim()) { // Check for empty or whitespace-only input 13 | const updatedChatHistory = {"messages": [ 14 | ...chatHistory.messages, 15 | { content: chatCurrent, role: 'user' } 16 | ] 17 | }; 18 | setChatCurrent(''); // Clear input after sending message 19 | setChatHistory(updatedChatHistory); 20 | await sendChatToAI(updatedChatHistory); 21 | } 22 | }; 23 | 24 | // dev environment: this request is proxied to backend - see vite.config 25 | const sendChatToAI = async (chathistory) => { 26 | try { 27 | const response = await fetch('/messages', { 28 | method: 'POST', 29 | headers: { 'Content-Type': 'application/json' }, 30 | body: JSON.stringify({input: chathistory, urls: urlHistory}), 31 | }); 32 | console.log(response) 33 | if (!response.ok) { 34 | throw new Error(`Error sending message: ${response.statusText}`); 35 | } else { 36 | let result = await response.json(); 37 | let newChat = result.input; 38 | let urlHistory = result.urls.filter(url => url != null); //remove null objects when no url returned 39 | setURLHistory(urlHistory); 40 | setChatHistory(newChat); 41 | } 42 | } catch (error) { 43 | console.error('Error sending message:', error); 44 | } 45 | }; 46 | // Parse url into title 47 | const parseTitle = (url) => { 48 | let title = url.match(/medicines\/(.+)\/#/) 49 | let formatTitle = title[1] 50 | .replace(/-/g, " ") 51 | .replace(/\//g," | ") 52 | .split(" ") 53 | .map(word => { 54 | if (!/^and$|^to$|^for$/.test(word)){ 55 | return word[0].toUpperCase() + word.substring(1) 56 | } else { 57 | return word 58 | }}) 59 | .join(" ") 60 | return formatTitle 61 | } 62 | 63 | // Enable scroll to bottom of chat when new message 64 | const chatRef = useRef(null) 65 | useEffect(() => {chatRef.current?.lastElementChild?.scrollIntoView()}, [chatHistory]) 66 | // also for url display 67 | const urlsRef = useRef(null) 68 | useEffect(() => {urlsRef.current?.lastElementChild?.scrollIntoView()}, [urlHistory]) 69 | 70 | return ( 71 |
72 |
73 |

NHS Medicines Chatbot

74 |

Ask questions about medicines and I will answer using NHS guidance for patients



I will also provide direct links to sources

75 |
76 |
77 |
78 |
79 | {chatHistory.messages.map((message, index) => ( 80 |
84 |
{message.content}
85 |
86 | ))} 87 |
88 |
89 |