ntphuc149 commited on
Commit
6733769
·
verified ·
1 Parent(s): be55f39

Upload 21 files

Browse files
.env.example ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Frontend Environment Variables
2
+ REACT_APP_API_URL=http://localhost:8000
3
+ REACT_APP_ENV=development
4
+
5
+ # Backend Environment Variables
6
+ QWEN_SERVER_IP=https://your-kaggle-server.ngrok.io
7
+ WEB_API_PORT=8000
8
+ WEB_API_HOST=0.0.0.0
README.md CHANGED
@@ -1,81 +0,0 @@
1
- ---
2
- title: Medical Summarizer
3
- emoji: 🐠
4
- colorFrom: indigo
5
- colorTo: red
6
- sdk: static
7
- pinned: false
8
- app_build_command: npm run build
9
- app_file: build/index.html
10
- ---
11
-
12
- # Getting Started with Create React App
13
-
14
- This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
15
-
16
- ## Available Scripts
17
-
18
- In the project directory, you can run:
19
-
20
- ### `npm start`
21
-
22
- Runs the app in the development mode.\
23
- Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
24
-
25
- The page will reload when you make changes.\
26
- You may also see any lint errors in the console.
27
-
28
- ### `npm test`
29
-
30
- Launches the test runner in the interactive watch mode.\
31
- See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
32
-
33
- ### `npm run build`
34
-
35
- Builds the app for production to the `build` folder.\
36
- It correctly bundles React in production mode and optimizes the build for the best performance.
37
-
38
- The build is minified and the filenames include the hashes.\
39
- Your app is ready to be deployed!
40
-
41
- See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
42
-
43
- ### `npm run eject`
44
-
45
- **Note: this is a one-way operation. Once you `eject`, you can't go back!**
46
-
47
- If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
48
-
49
- Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
50
-
51
- You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
52
-
53
- ## Learn More
54
-
55
- You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
56
-
57
- To learn React, check out the [React documentation](https://reactjs.org/).
58
-
59
- ### Code Splitting
60
-
61
- This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
62
-
63
- ### Analyzing the Bundle Size
64
-
65
- This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
66
-
67
- ### Making a Progressive Web App
68
-
69
- This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
70
-
71
- ### Advanced Configuration
72
-
73
- This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
74
-
75
- ### Deployment
76
-
77
- This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
78
-
79
- ### `npm run build` fails to minify
80
-
81
- This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public/favicon.ico ADDED
public/index.html ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="theme-color" content="#000000" />
7
+ <meta
8
+ name="description"
9
+ content="Customization Patient Healcare with AI Powered Multi-Agent System"
10
+ />
11
+ <title>Medical Multi-Agent System</title>
12
+ <script src="https://cdn.tailwindcss.com"></script>
13
+
14
+ <link
15
+ href="https://cdn.jsdelivr.net/npm/remixicon@4.1.0/fonts/remixicon.css"
16
+ rel="stylesheet"
17
+ />
18
+
19
+ <link
20
+ rel="stylesheet"
21
+ href="https://cdn.jsdelivr.net/npm/@tabler/icons-sprite@latest/dist/tabler-icons.css"
22
+ />
23
+ </head>
24
+ <body>
25
+ <noscript>You need to enable JavaScript to run this app.</noscript>
26
+ <div id="root"></div>
27
+ </body>
28
+ </html>
public/logo192.png ADDED
public/logo512.png ADDED
public/manifest.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "short_name": "React App",
3
+ "name": "Create React App Sample",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ },
10
+ {
11
+ "src": "logo192.png",
12
+ "type": "image/png",
13
+ "sizes": "192x192"
14
+ },
15
+ {
16
+ "src": "logo512.png",
17
+ "type": "image/png",
18
+ "sizes": "512x512"
19
+ }
20
+ ],
21
+ "start_url": ".",
22
+ "display": "standalone",
23
+ "theme_color": "#000000",
24
+ "background_color": "#ffffff"
25
+ }
public/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
src/App.css ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .App {
2
+ text-align: center;
3
+ }
4
+
5
+ .App-logo {
6
+ height: 40vmin;
7
+ pointer-events: none;
8
+ }
9
+
10
+ @media (prefers-reduced-motion: no-preference) {
11
+ .App-logo {
12
+ animation: App-logo-spin infinite 20s linear;
13
+ }
14
+ }
15
+
16
+ .App-header {
17
+ background-color: #282c34;
18
+ min-height: 100vh;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ font-size: calc(10px + 2vmin);
24
+ color: white;
25
+ }
26
+
27
+ .App-link {
28
+ color: #61dafb;
29
+ }
30
+
31
+ @keyframes App-logo-spin {
32
+ from {
33
+ transform: rotate(0deg);
34
+ }
35
+ to {
36
+ transform: rotate(360deg);
37
+ }
38
+ }
src/App.js ADDED
@@ -0,0 +1,772 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import {
3
+ Send,
4
+ Moon,
5
+ Sun,
6
+ User,
7
+ Bot,
8
+ AlertCircle,
9
+ CheckCircle,
10
+ Clock,
11
+ Heart,
12
+ Phone,
13
+ Calendar,
14
+ MapPin,
15
+ FileText,
16
+ } from "lucide-react";
17
+ import { apiService } from "./services/apiService";
18
+
19
+ const MedicalChatUI = () => {
20
+ // UI State
21
+ const [isDarkMode, setIsDarkMode] = useState(false);
22
+ const [currentMessage, setCurrentMessage] = useState("");
23
+ const [messages, setMessages] = useState([]);
24
+ const [isTyping, setIsTyping] = useState(false);
25
+ const [isConnected, setIsConnected] = useState(false);
26
+ const [connectionError, setConnectionError] = useState("");
27
+
28
+ // Patient Data State
29
+ const [patientRecord, setPatientRecord] = useState({
30
+ personal_info: {
31
+ full_name: "",
32
+ dob: "",
33
+ gender: "",
34
+ phone: "",
35
+ address: "",
36
+ },
37
+ medical_info: {
38
+ current_symptoms: [],
39
+ symptom_start_date: "",
40
+ previous_medications: [],
41
+ allergies: [],
42
+ severity_level: "",
43
+ },
44
+ });
45
+
46
+ // Session State
47
+ const [sessionStatus, setSessionStatus] = useState("idle"); // idle | collecting | completed
48
+ const [confidenceScores, setConfidenceScores] = useState({
49
+ extraction: 0,
50
+ validation: 0,
51
+ });
52
+
53
+ const messagesEndRef = useRef(null);
54
+
55
+ // Auto-scroll to bottom
56
+ const scrollToBottom = () => {
57
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
58
+ };
59
+
60
+ useEffect(() => {
61
+ scrollToBottom();
62
+ }, [messages]);
63
+
64
+ // Initialize connection and start chat
65
+ useEffect(() => {
66
+ initializeChat();
67
+ }, []);
68
+
69
+ const initializeChat = async () => {
70
+ try {
71
+ // Health check
72
+ await apiService.healthCheck();
73
+ setIsConnected(true);
74
+ setConnectionError("");
75
+
76
+ // Start chat session
77
+ const response = await apiService.startChat();
78
+
79
+ // Add initial AI message
80
+ setMessages([
81
+ {
82
+ id: Date.now(),
83
+ text: response.message,
84
+ sender: "ai",
85
+ timestamp: new Date().toISOString(),
86
+ },
87
+ ]);
88
+
89
+ setSessionStatus("collecting");
90
+ } catch (error) {
91
+ setIsConnected(false);
92
+ setConnectionError(error.message);
93
+ console.error("Failed to initialize chat:", error);
94
+ }
95
+ };
96
+
97
+ const handleSendMessage = async () => {
98
+ if (!currentMessage.trim() || isTyping) return;
99
+
100
+ const userMessage = {
101
+ id: Date.now(),
102
+ text: currentMessage.trim(),
103
+ sender: "user",
104
+ timestamp: new Date().toISOString(),
105
+ };
106
+
107
+ // Add user message to chat
108
+ setMessages((prev) => [...prev, userMessage]);
109
+ setCurrentMessage("");
110
+ setIsTyping(true);
111
+
112
+ try {
113
+ // Send to backend
114
+ const response = await apiService.sendMessage(userMessage.text);
115
+
116
+ // Update patient record
117
+ setPatientRecord(response.patient_record);
118
+ setConfidenceScores(response.confidence_scores);
119
+ setSessionStatus(response.session_status);
120
+
121
+ // Add AI response
122
+ const aiMessage = {
123
+ id: Date.now() + 1,
124
+ text: response.ai_message,
125
+ sender: "ai",
126
+ timestamp: new Date().toISOString(),
127
+ metadata: response.metadata,
128
+ };
129
+
130
+ setMessages((prev) => [...prev, aiMessage]);
131
+
132
+ // If conversation completed, show completion message
133
+ if (response.should_end) {
134
+ setTimeout(() => {
135
+ const completionMessage = {
136
+ id: Date.now() + 2,
137
+ text: "✅ Information collection completed! Click 'Generate Summary' to create a doctor report.",
138
+ sender: "system",
139
+ timestamp: new Date().toISOString(),
140
+ };
141
+ setMessages((prev) => [...prev, completionMessage]);
142
+ }, 1000);
143
+ }
144
+ } catch (error) {
145
+ console.error("Failed to send message:", error);
146
+
147
+ // Add error message
148
+ const errorMessage = {
149
+ id: Date.now() + 1,
150
+ text: `❌ Error: ${error.message}. Please try again.`,
151
+ sender: "system",
152
+ timestamp: new Date().toISOString(),
153
+ };
154
+ setMessages((prev) => [...prev, errorMessage]);
155
+ } finally {
156
+ setIsTyping(false);
157
+ }
158
+ };
159
+
160
+ const handleGenerateSummary = async () => {
161
+ try {
162
+ setIsTyping(true);
163
+ const summary = await apiService.generateSummary();
164
+
165
+ const summaryMessage = {
166
+ id: Date.now(),
167
+ text: `📋 **Doctor Summary Generated**\n\n**Summary:** ${
168
+ summary.doctor_summary
169
+ }\n\n**Urgency Level:** ${
170
+ summary.urgency_level
171
+ }\n\n**Key Findings:**\n${summary.key_findings
172
+ .map((f) => `• ${f}`)
173
+ .join(
174
+ "\n"
175
+ )}\n\n**Recommended Questions:**\n${summary.recommended_questions
176
+ .map((q) => `• ${q}`)
177
+ .join("\n")}`,
178
+ sender: "system",
179
+ timestamp: new Date().toISOString(),
180
+ };
181
+
182
+ setMessages((prev) => [...prev, summaryMessage]);
183
+ } catch (error) {
184
+ console.error("Failed to generate summary:", error);
185
+ } finally {
186
+ setIsTyping(false);
187
+ }
188
+ };
189
+
190
+ const handleResetChat = async () => {
191
+ try {
192
+ await apiService.resetSession();
193
+ setMessages([]);
194
+ setPatientRecord({
195
+ personal_info: {
196
+ full_name: "",
197
+ dob: "",
198
+ gender: "",
199
+ phone: "",
200
+ address: "",
201
+ },
202
+ medical_info: {
203
+ current_symptoms: [],
204
+ symptom_start_date: "",
205
+ previous_medications: [],
206
+ allergies: [],
207
+ severity_level: "",
208
+ },
209
+ });
210
+ setSessionStatus("idle");
211
+ setConfidenceScores({ extraction: 0, validation: 0 });
212
+
213
+ // Restart chat
214
+ await initializeChat();
215
+ } catch (error) {
216
+ console.error("Failed to reset chat:", error);
217
+ }
218
+ };
219
+
220
+ // Components
221
+ const MessageBubble = ({ message }) => {
222
+ const isUser = message.sender === "user";
223
+ const isSystem = message.sender === "system";
224
+
225
+ return (
226
+ <div className={`flex ${isUser ? "justify-end" : "justify-start"} mb-4`}>
227
+ <div className="flex items-start space-x-3 max-w-3xl">
228
+ {!isUser && (
229
+ <div
230
+ className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
231
+ isSystem
232
+ ? isDarkMode
233
+ ? "bg-yellow-900/50 text-yellow-300"
234
+ : "bg-yellow-100 text-yellow-700"
235
+ : isDarkMode
236
+ ? "bg-blue-600"
237
+ : "bg-blue-500"
238
+ }`}
239
+ >
240
+ {isSystem ? (
241
+ <AlertCircle className="w-4 h-4" />
242
+ ) : (
243
+ <Bot className="w-4 h-4 text-white" />
244
+ )}
245
+ </div>
246
+ )}
247
+
248
+ <div
249
+ className={`px-4 py-3 rounded-2xl ${
250
+ isUser
251
+ ? isDarkMode
252
+ ? "bg-blue-600 text-white"
253
+ : "bg-blue-500 text-white"
254
+ : isSystem
255
+ ? isDarkMode
256
+ ? "bg-yellow-900/20 text-yellow-200 border border-yellow-700"
257
+ : "bg-yellow-50 text-yellow-800 border border-yellow-200"
258
+ : isDarkMode
259
+ ? "bg-gray-700 text-gray-200"
260
+ : "bg-gray-100 text-gray-900"
261
+ }`}
262
+ >
263
+ <div className="whitespace-pre-wrap">{message.text}</div>
264
+
265
+ {message.metadata && (
266
+ <div className="text-xs mt-2 opacity-70">
267
+ Exchange #{message.metadata.exchange_count} | Confidence:{" "}
268
+ {(message.metadata.confidence || 0).toFixed(2)}
269
+ </div>
270
+ )}
271
+ </div>
272
+
273
+ {isUser && (
274
+ <div className="flex-shrink-0 w-8 h-8 rounded-full bg-gray-400 flex items-center justify-center">
275
+ <User className="w-4 h-4 text-white" />
276
+ </div>
277
+ )}
278
+ </div>
279
+ </div>
280
+ );
281
+ };
282
+
283
+ const TypingIndicator = () => (
284
+ <div className="flex justify-start mb-4">
285
+ <div className="flex items-center space-x-3 max-w-xs">
286
+ <div
287
+ className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
288
+ isDarkMode ? "bg-blue-600" : "bg-blue-500"
289
+ }`}
290
+ >
291
+ <Bot className="w-4 h-4 text-white" />
292
+ </div>
293
+ <div
294
+ className={`px-4 py-3 rounded-2xl ${
295
+ isDarkMode ? "bg-gray-700" : "bg-gray-100"
296
+ }`}
297
+ >
298
+ <div className="flex space-x-1">
299
+ <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
300
+ <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce [animation-delay:0.1s]"></div>
301
+ <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce [animation-delay:0.2s]"></div>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ );
307
+
308
+ const PatientPanel = () => (
309
+ <div
310
+ className={`w-96 ${
311
+ isDarkMode ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"
312
+ } border-l flex flex-col`}
313
+ >
314
+ {/* Header */}
315
+ <div
316
+ className={`p-4 border-b ${
317
+ isDarkMode ? "border-gray-700" : "border-gray-200"
318
+ }`}
319
+ >
320
+ <h2
321
+ className={`text-lg font-semibold ${
322
+ isDarkMode ? "text-gray-200" : "text-gray-900"
323
+ } flex items-center`}
324
+ >
325
+ <FileText className="w-5 h-5 mr-2" />
326
+ Patient Record
327
+ </h2>
328
+ <div className="flex items-center mt-2 space-x-4">
329
+ <div
330
+ className={`text-sm ${
331
+ isDarkMode ? "text-gray-400" : "text-gray-600"
332
+ }`}
333
+ >
334
+ Status: {sessionStatus}
335
+ </div>
336
+ <div className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">
337
+ Confidence:{" "}
338
+ {Math.round(
339
+ ((confidenceScores.extraction + confidenceScores.validation) /
340
+ 2) *
341
+ 100
342
+ )}
343
+ %
344
+ </div>
345
+ </div>
346
+ </div>
347
+
348
+ {/* Content */}
349
+ <div className="flex-1 overflow-y-auto p-4 space-y-6">
350
+ {/* Personal Information */}
351
+ <div>
352
+ <h3
353
+ className={`text-sm font-medium ${
354
+ isDarkMode ? "text-gray-300" : "text-gray-700"
355
+ } mb-3 flex items-center`}
356
+ >
357
+ <User className="w-4 h-4 mr-2" />
358
+ Personal Information
359
+ </h3>
360
+
361
+ <div className="space-y-3">
362
+ <div>
363
+ <span
364
+ className={`text-sm ${
365
+ isDarkMode ? "text-gray-400" : "text-gray-600"
366
+ }`}
367
+ >
368
+ Name:
369
+ </span>
370
+ <p
371
+ className={`text-sm mt-1 ${
372
+ isDarkMode ? "text-gray-200" : "text-gray-900"
373
+ }`}
374
+ >
375
+ {patientRecord.personal_info.full_name || "Not provided"}
376
+ </p>
377
+ </div>
378
+
379
+ <div>
380
+ <span
381
+ className={`text-sm ${
382
+ isDarkMode ? "text-gray-400" : "text-gray-600"
383
+ }`}
384
+ >
385
+ Date of Birth:
386
+ </span>
387
+ <p
388
+ className={`text-sm mt-1 ${
389
+ isDarkMode ? "text-gray-200" : "text-gray-900"
390
+ } flex items-center`}
391
+ >
392
+ <Calendar className="w-3 h-3 mr-1" />
393
+ {patientRecord.personal_info.dob || "Not provided"}
394
+ </p>
395
+ </div>
396
+
397
+ <div>
398
+ <span
399
+ className={`text-sm ${
400
+ isDarkMode ? "text-gray-400" : "text-gray-600"
401
+ }`}
402
+ >
403
+ Phone:
404
+ </span>
405
+ <p
406
+ className={`text-sm mt-1 ${
407
+ isDarkMode ? "text-gray-200" : "text-gray-900"
408
+ } flex items-center`}
409
+ >
410
+ <Phone className="w-3 h-3 mr-1" />
411
+ {patientRecord.personal_info.phone || "Not provided"}
412
+ </p>
413
+ </div>
414
+
415
+ <div>
416
+ <span
417
+ className={`text-sm ${
418
+ isDarkMode ? "text-gray-400" : "text-gray-600"
419
+ }`}
420
+ >
421
+ Address:
422
+ </span>
423
+ <p
424
+ className={`text-sm mt-1 ${
425
+ isDarkMode ? "text-gray-200" : "text-gray-900"
426
+ } flex items-center`}
427
+ >
428
+ <MapPin className="w-3 h-3 mr-1" />
429
+ {patientRecord.personal_info.address || "Not provided"}
430
+ </p>
431
+ </div>
432
+ </div>
433
+ </div>
434
+
435
+ {/* Medical Information */}
436
+ <div>
437
+ <h3
438
+ className={`text-sm font-medium ${
439
+ isDarkMode ? "text-gray-300" : "text-gray-700"
440
+ } mb-3 flex items-center`}
441
+ >
442
+ <Heart className="w-4 h-4 mr-2" />
443
+ Medical Information
444
+ </h3>
445
+
446
+ <div className="space-y-3">
447
+ <div>
448
+ <span
449
+ className={`text-sm ${
450
+ isDarkMode ? "text-gray-400" : "text-gray-600"
451
+ }`}
452
+ >
453
+ Symptoms:
454
+ </span>
455
+ <p
456
+ className={`text-sm mt-1 ${
457
+ isDarkMode ? "text-gray-200" : "text-gray-900"
458
+ }`}
459
+ >
460
+ {patientRecord.medical_info.current_symptoms?.length > 0
461
+ ? patientRecord.medical_info.current_symptoms.join(", ")
462
+ : "Not provided"}
463
+ </p>
464
+ </div>
465
+
466
+ <div>
467
+ <span
468
+ className={`text-sm ${
469
+ isDarkMode ? "text-gray-400" : "text-gray-600"
470
+ }`}
471
+ >
472
+ Duration:
473
+ </span>
474
+ <p
475
+ className={`text-sm mt-1 ${
476
+ isDarkMode ? "text-gray-200" : "text-gray-900"
477
+ } flex items-center`}
478
+ >
479
+ <Clock className="w-3 h-3 mr-1" />
480
+ {patientRecord.medical_info.symptom_start_date ||
481
+ "Not provided"}
482
+ </p>
483
+ </div>
484
+
485
+ <div>
486
+ <span
487
+ className={`text-sm ${
488
+ isDarkMode ? "text-gray-400" : "text-gray-600"
489
+ }`}
490
+ >
491
+ Severity:
492
+ </span>
493
+ <p
494
+ className={`text-sm mt-1 ${
495
+ isDarkMode ? "text-gray-200" : "text-gray-900"
496
+ }`}
497
+ >
498
+ {patientRecord.medical_info.severity_level || "Not assessed"}
499
+ </p>
500
+ </div>
501
+
502
+ <div>
503
+ <span
504
+ className={`text-sm ${
505
+ isDarkMode ? "text-gray-400" : "text-gray-600"
506
+ }`}
507
+ >
508
+ Medications:
509
+ </span>
510
+ <p
511
+ className={`text-sm mt-1 ${
512
+ isDarkMode ? "text-gray-200" : "text-gray-900"
513
+ }`}
514
+ >
515
+ {patientRecord.medical_info.previous_medications?.length > 0
516
+ ? patientRecord.medical_info.previous_medications.join(", ")
517
+ : "None reported"}
518
+ </p>
519
+ </div>
520
+
521
+ <div>
522
+ <span
523
+ className={`text-sm ${
524
+ isDarkMode ? "text-gray-400" : "text-gray-600"
525
+ }`}
526
+ >
527
+ Allergies:
528
+ </span>
529
+ <p
530
+ className={`text-sm mt-1 ${
531
+ isDarkMode ? "text-gray-200" : "text-gray-900"
532
+ }`}
533
+ >
534
+ {patientRecord.medical_info.allergies?.length > 0
535
+ ? patientRecord.medical_info.allergies.join(", ")
536
+ : "None reported"}
537
+ </p>
538
+ </div>
539
+ </div>
540
+ </div>
541
+ </div>
542
+
543
+ {/* Actions */}
544
+ <div
545
+ className={`p-4 border-t ${
546
+ isDarkMode ? "border-gray-700" : "border-gray-200"
547
+ } space-y-2`}
548
+ >
549
+ <button
550
+ onClick={handleGenerateSummary}
551
+ disabled={sessionStatus === "idle"}
552
+ className={`w-full px-4 py-2 rounded-lg transition-colors ${
553
+ sessionStatus === "idle"
554
+ ? `${
555
+ isDarkMode
556
+ ? "bg-gray-700 text-gray-500 cursor-not-allowed"
557
+ : "bg-gray-200 text-gray-400 cursor-not-allowed"
558
+ }`
559
+ : `${
560
+ isDarkMode
561
+ ? "bg-blue-600 hover:bg-blue-700"
562
+ : "bg-blue-500 hover:bg-blue-600"
563
+ } text-white`
564
+ }`}
565
+ >
566
+ Generate Summary
567
+ </button>
568
+ <button
569
+ onClick={handleResetChat}
570
+ className={`w-full px-4 py-2 rounded-lg transition-colors ${
571
+ isDarkMode
572
+ ? "border border-gray-600 hover:bg-gray-700 text-gray-300"
573
+ : "border border-gray-300 hover:bg-gray-50 text-gray-700"
574
+ }`}
575
+ >
576
+ New Session
577
+ </button>
578
+ </div>
579
+ </div>
580
+ );
581
+
582
+ // Connection error screen
583
+ if (!isConnected && connectionError) {
584
+ return (
585
+ <div
586
+ className={`h-screen flex items-center justify-center ${
587
+ isDarkMode ? "bg-gray-900" : "bg-gray-50"
588
+ }`}
589
+ >
590
+ <div
591
+ className={`text-center p-8 rounded-lg ${
592
+ isDarkMode ? "bg-gray-800" : "bg-white"
593
+ } shadow-lg`}
594
+ >
595
+ <AlertCircle
596
+ className={`w-16 h-16 mx-auto mb-4 ${
597
+ isDarkMode ? "text-red-400" : "text-red-500"
598
+ }`}
599
+ />
600
+ <h2
601
+ className={`text-xl font-semibold mb-2 ${
602
+ isDarkMode ? "text-gray-200" : "text-gray-900"
603
+ }`}
604
+ >
605
+ Connection Failed
606
+ </h2>
607
+ <p
608
+ className={`mb-4 ${isDarkMode ? "text-gray-400" : "text-gray-600"}`}
609
+ >
610
+ {connectionError}
611
+ </p>
612
+ <button
613
+ onClick={initializeChat}
614
+ className={`px-4 py-2 rounded-lg ${
615
+ isDarkMode
616
+ ? "bg-blue-600 hover:bg-blue-700"
617
+ : "bg-blue-500 hover:bg-blue-600"
618
+ } text-white transition-colors`}
619
+ >
620
+ Retry Connection
621
+ </button>
622
+ </div>
623
+ </div>
624
+ );
625
+ }
626
+
627
+ return (
628
+ <div
629
+ className={`h-screen flex ${isDarkMode ? "bg-gray-900" : "bg-gray-50"}`}
630
+ >
631
+ {/* Main Content */}
632
+ <div className="flex-1 flex flex-col">
633
+ {/* Header */}
634
+ <header
635
+ className={`${
636
+ isDarkMode
637
+ ? "bg-gray-800 border-gray-700"
638
+ : "bg-white border-gray-200"
639
+ } border-b px-6 py-4 flex items-center justify-between`}
640
+ >
641
+ <div className="flex items-center space-x-4">
642
+ <h1
643
+ className={`text-xl font-semibold ${
644
+ isDarkMode ? "text-gray-200" : "text-gray-900"
645
+ }`}
646
+ >
647
+ Medical Multi-Agent System
648
+ </h1>
649
+ </div>
650
+
651
+ <div className="flex items-center space-x-2">
652
+ <div
653
+ className={`px-3 py-1 rounded-full text-sm flex items-center space-x-2 ${
654
+ isConnected
655
+ ? isDarkMode
656
+ ? "bg-green-900/50 text-green-300 border border-green-700"
657
+ : "bg-green-100 text-green-700 border border-green-200"
658
+ : isDarkMode
659
+ ? "bg-red-900/50 text-red-300 border border-red-700"
660
+ : "bg-red-100 text-red-700 border border-red-200"
661
+ }`}
662
+ >
663
+ {isConnected ? (
664
+ <CheckCircle className="w-3 h-3" />
665
+ ) : (
666
+ <AlertCircle className="w-3 h-3" />
667
+ )}
668
+ <span>
669
+ {isConnected ? "Connected to Qwen 3-4B" : "Disconnected"}
670
+ </span>
671
+ </div>
672
+ <button
673
+ onClick={() => setIsDarkMode(!isDarkMode)}
674
+ className={`p-2 rounded-lg ${
675
+ isDarkMode
676
+ ? "hover:bg-gray-700 text-gray-300"
677
+ : "hover:bg-gray-100 text-gray-600"
678
+ } transition-colors`}
679
+ >
680
+ {isDarkMode ? (
681
+ <Sun className="w-5 h-5" />
682
+ ) : (
683
+ <Moon className="w-5 h-5" />
684
+ )}
685
+ </button>
686
+ </div>
687
+ </header>
688
+
689
+ <div className="flex-1 flex min-h-0 overflow-hidden">
690
+ {/* Chat Area */}
691
+ <div className="flex-1 flex flex-col">
692
+ {/* Messages */}
693
+ <div className="flex-1 overflow-y-auto p-6">
694
+ {messages.length === 0 && (
695
+ <div className="flex items-center justify-center h-full">
696
+ <div className="text-center">
697
+ <Bot
698
+ className={`w-16 h-16 mx-auto mb-4 ${
699
+ isDarkMode ? "text-gray-600" : "text-gray-400"
700
+ }`}
701
+ />
702
+ <p
703
+ className={`text-lg ${
704
+ isDarkMode ? "text-gray-400" : "text-gray-600"
705
+ }`}
706
+ >
707
+ Initializing Medical Multi-Agent System...
708
+ </p>
709
+ </div>
710
+ </div>
711
+ )}
712
+
713
+ {messages.map((message) => (
714
+ <MessageBubble key={message.id} message={message} />
715
+ ))}
716
+ {isTyping && <TypingIndicator />}
717
+ <div ref={messagesEndRef} />
718
+ </div>
719
+
720
+ {/* Input Area */}
721
+ <div
722
+ className={`p-6 border-t ${
723
+ isDarkMode ? "border-gray-700" : "border-gray-200"
724
+ }`}
725
+ >
726
+ <div className="flex space-x-4">
727
+ <input
728
+ type="text"
729
+ value={currentMessage}
730
+ onChange={(e) => setCurrentMessage(e.target.value)}
731
+ onKeyPress={(e) => e.key === "Enter" && handleSendMessage()}
732
+ placeholder="Describe your symptoms or provide requested information..."
733
+ disabled={!isConnected}
734
+ className={`flex-1 px-4 py-3 rounded-lg border ${
735
+ isDarkMode
736
+ ? "bg-gray-700 border-gray-600 text-gray-200 placeholder-gray-400"
737
+ : "bg-white border-gray-300 text-gray-900 placeholder-gray-500"
738
+ } focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50`}
739
+ />
740
+ <button
741
+ onClick={handleSendMessage}
742
+ disabled={!currentMessage.trim() || isTyping || !isConnected}
743
+ className={`px-6 py-3 rounded-lg transition-colors flex items-center space-x-2 ${
744
+ !currentMessage.trim() || isTyping || !isConnected
745
+ ? `${
746
+ isDarkMode
747
+ ? "bg-gray-700 text-gray-500"
748
+ : "bg-gray-200 text-gray-400"
749
+ } cursor-not-allowed`
750
+ : `${
751
+ isDarkMode
752
+ ? "bg-blue-600 hover:bg-blue-700"
753
+ : "bg-blue-500 hover:bg-blue-600"
754
+ } text-white`
755
+ }`}
756
+ >
757
+ <Send className="w-4 h-4" />
758
+ <span>{isTyping ? "Sending..." : "Send"}</span>
759
+ </button>
760
+ </div>
761
+ </div>
762
+ </div>
763
+
764
+ {/* Patient Panel */}
765
+ <PatientPanel />
766
+ </div>
767
+ </div>
768
+ </div>
769
+ );
770
+ };
771
+
772
+ export default MedicalChatUI;
src/App.test.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { render, screen } from '@testing-library/react';
2
+ import App from './App';
3
+
4
+ test('renders learn react link', () => {
5
+ render(<App />);
6
+ const linkElement = screen.getByText(/learn react/i);
7
+ expect(linkElement).toBeInTheDocument();
8
+ });
src/index.css ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* Custom styles nếu cần */
6
+ body {
7
+ margin: 0;
8
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
9
+ }
src/index.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ const root = ReactDOM.createRoot(document.getElementById('root'));
6
+ root.render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
src/logo.svg ADDED
src/reportWebVitals.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const reportWebVitals = onPerfEntry => {
2
+ if (onPerfEntry && onPerfEntry instanceof Function) {
3
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4
+ getCLS(onPerfEntry);
5
+ getFID(onPerfEntry);
6
+ getFCP(onPerfEntry);
7
+ getLCP(onPerfEntry);
8
+ getTTFB(onPerfEntry);
9
+ });
10
+ }
11
+ };
12
+
13
+ export default reportWebVitals;
src/services/apiService.js ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * API Service for Medical Multi-Agent System
3
+ * Handles communication with backend Web API
4
+ */
5
+
6
+ const API_BASE_URL = process.env.REACT_APP_API_URL || "http://localhost:8000";
7
+
8
+ class ApiService {
9
+ constructor() {
10
+ this.baseUrl = API_BASE_URL;
11
+ }
12
+
13
+ /**
14
+ * Generic API call wrapper
15
+ */
16
+ async apiCall(endpoint, options = {}) {
17
+ const url = `${this.baseUrl}${endpoint}`;
18
+ const config = {
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ ...options.headers,
22
+ },
23
+ ...options,
24
+ };
25
+
26
+ try {
27
+ const response = await fetch(url, config);
28
+
29
+ if (!response.ok) {
30
+ const errorData = await response
31
+ .json()
32
+ .catch(() => ({ detail: "Unknown error" }));
33
+ throw new Error(
34
+ `API Error: ${response.status} - ${
35
+ errorData.detail || response.statusText
36
+ }`
37
+ );
38
+ }
39
+
40
+ return await response.json();
41
+ } catch (error) {
42
+ console.error("API call failed:", error);
43
+ throw error;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Health check
49
+ */
50
+ async healthCheck() {
51
+ return this.apiCall("/api/health");
52
+ }
53
+
54
+ /**
55
+ * Start new chat session
56
+ */
57
+ async startChat() {
58
+ return this.apiCall("/api/chat/start", {
59
+ method: "POST",
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Send message to AI
65
+ */
66
+ async sendMessage(message) {
67
+ return this.apiCall("/api/chat/send", {
68
+ method: "POST",
69
+ body: JSON.stringify({ message }),
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Get current session status and patient record
75
+ */
76
+ async getSessionStatus() {
77
+ return this.apiCall("/api/chat/status");
78
+ }
79
+
80
+ /**
81
+ * Generate doctor summary
82
+ */
83
+ async generateSummary() {
84
+ return this.apiCall("/api/chat/summary", {
85
+ method: "POST",
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Reset current session
91
+ */
92
+ async resetSession() {
93
+ return this.apiCall("/api/chat/reset", {
94
+ method: "DELETE",
95
+ });
96
+ }
97
+ }
98
+
99
+ // Export singleton instance
100
+ export const apiService = new ApiService();
101
+ export default apiService;
src/setupTests.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom';