Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ MLFLOW_TRACKING_URI=http://localhost:5000
MLFLOW_ARTIFACT_LOCATION=logs/mlflow
FASTAPI_HOST=0.0.0.0
FASTAPI_PORT=8000
REACT_APP_API_URL=http://127.0.0.1:8000
ENV=development
DEBUG=True
Empty file removed configs/settings.yaml
Empty file.
104 changes: 102 additions & 2 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,20 @@ header.header {
overflow: hidden;
}

/* SIDEBAR */
aside.sidebar {
/* SIDEBAR CONTAINER (New wrapper for mobile logic) */
.sidebar-container {
width: var(--sidebar);
flex-shrink: 0;
height: 100%;
background: var(--surface);
border-right: 1px solid var(--border);
z-index: 50;
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* SIDEBAR */
aside.sidebar {
width: 100%;
display: flex;
flex-direction: column;
overflow-y: auto;
Expand Down Expand Up @@ -522,3 +530,95 @@ aside.sidebar {
text-align: right;
font-family: 'DM Mono', monospace;
}

/* --- MOBILE RESPONSIVE RULES --- */

/* Hide the hamburger menu and overlay on desktop */
.mobile-menu-toggle {
display: none;
}

.mobile-overlay {
display: block;
position: absolute;
top: var(--header); /* Start below the fixed header */
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 40;
backdrop-filter: blur(2px);
}

@media (max-width: 768px) {
header.header {
padding: 0 16px;
}

/* Show hamburger menu */
.mobile-menu-toggle {
display: block;
}

/* Make layout relative for absolute positioning of sidebar */
.layout {
position: relative;
flex-direction: column;
}

/* Turn Sidebar into a slide-out drawer */
.sidebar-container {
position: absolute;
top: var(--header); /* Start below the fixed header */
bottom: 0; /* Stretch to the bottom */
height: auto; /* Override the desktop 100% height */
left: -100%;
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
}

/* Slide in when 'open' */
.sidebar-container.open {
left: 0;
}

/* Stronger shadow in dark mode */
body.dark-mode .sidebar-container.open {
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.6);
}

/* Chat window fills screen */
.chat-window {
width: 100%;
}

/* Show the darkening overlay */
.mobile-overlay {
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 40;
backdrop-filter: blur(2px);
}

/* Adjust message padding and widths */
.messages {
padding: 16px;
}

.message-user .bubble {
max-width: 90%; /* Let bubbles be wider on small screens */
}

.input-area {
padding: 12px 16px;
}

/* Hide the "Enter to send" hint on mobile */
.input-hint {
display: none;
}
}
41 changes: 32 additions & 9 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {useLayoutEffect, useState} from 'react';
import { Sun, Moon } from 'lucide-react';
import React, { useLayoutEffect, useState } from 'react';
import { Sun, Moon, Menu, X } from 'lucide-react';
import Sidebar from './components/Sidebar';
import ChatWindow from './components/ChatWindow';
import './App.css';
Expand All @@ -12,6 +12,7 @@ function App() {
const [messages, setMessages] = useState([]);
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [darkMode, setDarkMode] = useState(() => {
const saved = localStorage.getItem('theme');
return saved ? saved === 'dark' : true;
Expand All @@ -29,9 +30,14 @@ function App() {
});
};

const closeSidebar = () => setIsSidebarOpen(false);

const handleSubmit = async (fhirData = {}) => {
if ((!query.trim() && !fhirData.has_fhir) || loading) return;

// Close the sidebar automatically on mobile when a query is sent
closeSidebar();

const userMessage = {
role: 'user',
text: query || `FHIR ${fhirData.fhir_resource_type}/${fhirData.fhir_resource_id}`
Expand Down Expand Up @@ -78,22 +84,39 @@ function App() {
return (
<div className="app">
<header className="header">
<div className="logo">
Sentinel<span>MD</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<button
className="mobile-menu-toggle"
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
style={{ background: 'none', border: 'none', color: 'inherit', cursor: 'pointer' }}
>
{isSidebarOpen ? <X size={20} /> : <Menu size={20} />}
</button>

<div className="logo">
Sentinel<span>MD</span>
</div>
</div>

<div>
<button onClick={toggleTheme} style={{ background: 'none', border: 'none', outline: 'none', color: 'inherit' }}>
{darkMode ? <Sun /> : <Moon />}
<button onClick={toggleTheme} style={{ background: 'none', border: 'none', outline: 'none', color: 'inherit', cursor: 'pointer' }}>
{darkMode ? <Sun size={20} /> : <Moon size={20} />}
</button>
</div>
</header>

<div className="layout">
<Sidebar apiKey={apiKey} onApiKeyChange={setApiKey} result={result}/>
<div className={`sidebar-container ${isSidebarOpen ? 'open' : ''}`}>
<Sidebar apiKey={apiKey} onApiKeyChange={setApiKey} result={result}/>
</div>

{isSidebarOpen && (
<div className="mobile-overlay" onClick={closeSidebar}></div>
)}

<ChatWindow
messages={messages}
query={query}
messages={messages}
query={query}
onQueryChange={setQuery}
onSubmit={handleSubmit}
loading={loading}
Expand Down
14 changes: 9 additions & 5 deletions frontend/src/components/ChatWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ function ChatWindow({ messages, query, onQueryChange, onSubmit, loading }) {
// Track which placeholder is currently active
const [placeholderIndex, setPlaceholderIndex] = useState(0);

// Swap the placeholder every 3.5 seconds
// Swap the placeholder every 5 seconds
useEffect(() => {
// Stop the carousel if a message has already been sent
if (messages.length > 0) return;

const timer = setInterval(() => {
setPlaceholderIndex((prev) => (prev + 1) % SAMPLE_PLACEHOLDERS.length);
}, 5000); // 5 seconds
}, 5000);

return () => clearInterval(timer);
}, [messages.length]);
Expand Down Expand Up @@ -159,7 +159,9 @@ function ChatWindow({ messages, query, onQueryChange, onSubmit, loading }) {
· HAPI R4 Server
</span>
</div>
<div style={{ display: 'flex', gap: 8 }}>

{/* Added flexWrap: 'wrap' here so the inputs stack cleanly on small mobile screens */}
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<select
value={fhirType}
onChange={(e) => setFhirType(e.target.value)}
Expand All @@ -173,7 +175,8 @@ function ChatWindow({ messages, query, onQueryChange, onSubmit, loading }) {
padding: '6px 10px',
outline: 'none',
cursor: 'pointer',
flexShrink: 0,
flex: '1 1 auto', /* Allows the select to shrink/grow gracefully */
minWidth: '140px', /* Ensures it doesn't get too small before wrapping */
}}
>
{FHIR_RESOURCE_TYPES.map(t => (
Expand All @@ -186,7 +189,8 @@ function ChatWindow({ messages, query, onQueryChange, onSubmit, loading }) {
value={fhirId}
onChange={(e) => setFhirId(e.target.value)}
style={{
flex: 1,
flex: '2 1 auto', /* Allows input to take up remaining space */
minWidth: '150px',
background: 'var(--bg)',
border: '1px solid var(--border)',
borderRadius: 6,
Expand Down
Loading
Loading