muhammadnoman76 commited on
Commit
b33f3aa
·
1 Parent(s): e5a4f30
CODE_DOCUMENTATION.md ADDED
The diff for this file is too large to render. See raw diff
 
Makefile CHANGED
@@ -19,9 +19,9 @@ run:
19
  $(VENV_DIR)/Scripts/python app.py
20
 
21
  # Remove virtual environment
22
- clean:
23
  if exist $(VENV_DIR) rmdir /s /q $(VENV_DIR)
24
 
25
  # Clean caches (__pycache__, .pytest_cache, *.pyc, *.pyo, build artifacts) - preserve .venv
26
- clean-all:
27
  - powershell -Command "Get-ChildItem -Recurse -Force -Include __pycache__,*.pyc,*.pyo,.pytest_cache,build,dist | Remove-Item -Recurse -Force"
 
19
  $(VENV_DIR)/Scripts/python app.py
20
 
21
  # Remove virtual environment
22
+ clean-all:
23
  if exist $(VENV_DIR) rmdir /s /q $(VENV_DIR)
24
 
25
  # Clean caches (__pycache__, .pytest_cache, *.pyc, *.pyo, build artifacts) - preserve .venv
26
+ clean:
27
  - powershell -Command "Get-ChildItem -Recurse -Force -Include __pycache__,*.pyc,*.pyo,.pytest_cache,build,dist | Remove-Item -Recurse -Force"
app/routers/agent_chat.py CHANGED
@@ -2,8 +2,10 @@
2
  import asyncio
3
  import json
4
  import logging
 
 
5
 
6
- from fastapi import APIRouter, Depends, Header, HTTPException
7
  from fastapi.responses import StreamingResponse
8
  from pydantic import BaseModel
9
 
@@ -84,28 +86,143 @@ async def stream_agent_response(agent_service: GoogleAgentService, query: str):
84
 
85
  @router.post("/agent-chat")
86
  async def agent_chat(
87
- request: AgentChatRequest,
88
  authorization: str = Header(None),
89
  username: str = Depends(get_current_user),
 
 
 
 
90
  ):
91
  if not authorization or not authorization.startswith("Bearer "):
92
  raise HTTPException(status_code=401, detail="Invalid authorization header")
93
 
94
  token = authorization.split(" ", 1)[1]
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  try:
97
  agent_service = GoogleAgentService(
98
  token=token,
99
- session_id=request.session_id,
100
- document=request.document.dict() if request.document else None,
101
- image=request.image.dict() if request.image else None,
102
  )
103
  except Exception as exc:
104
  logger.error("Failed to initialise agent service: %s", exc, exc_info=True)
105
  raise HTTPException(status_code=500, detail="Unable to initialise agent")
106
 
107
  return StreamingResponse(
108
- stream_agent_response(agent_service, request.query),
109
  media_type="text/event-stream",
110
  headers={
111
  "Cache-Control": "no-cache",
@@ -130,6 +247,8 @@ async def agent_status(username: str = Depends(get_current_user)):
130
  "image_search",
131
  "streaming",
132
  "tool_calls",
 
 
133
  ],
134
  }
135
  except Exception as exc:
 
2
  import asyncio
3
  import json
4
  import logging
5
+ import os
6
+ from datetime import datetime
7
 
8
+ from fastapi import APIRouter, Depends, File, Form, Header, HTTPException, UploadFile
9
  from fastapi.responses import StreamingResponse
10
  from pydantic import BaseModel
11
 
 
86
 
87
  @router.post("/agent-chat")
88
  async def agent_chat(
 
89
  authorization: str = Header(None),
90
  username: str = Depends(get_current_user),
91
+ session_id: Optional[str] = Form(None),
92
+ query: Optional[str] = Form(None),
93
+ file: Optional[UploadFile] = File(None),
94
+ image: Optional[UploadFile] = File(None),
95
  ):
96
  if not authorization or not authorization.startswith("Bearer "):
97
  raise HTTPException(status_code=401, detail="Invalid authorization header")
98
 
99
  token = authorization.split(" ", 1)[1]
100
 
101
+ # Handle file/image uploads if present
102
+ document_info = None
103
+ image_info = None
104
+
105
+ # Setup upload directory
106
+ default_upload_root = os.path.abspath(
107
+ os.path.join(os.path.dirname(__file__), "..", "..", "uploads")
108
+ )
109
+ uploads_root = os.getenv('DERMAI_UPLOAD_DIR', default_upload_root)
110
+
111
+ # Handle document upload
112
+ if file:
113
+ try:
114
+ # Validate file type
115
+ file_extension = file.filename.rsplit('.', 1)[1].lower() if '.' in file.filename else ''
116
+ allowed_doc_extensions = {
117
+ 'pdf', 'xlsx', 'xls', 'csv', 'doc', 'docx',
118
+ 'ppt', 'pptx', 'txt', 'html'
119
+ }
120
+
121
+ if file_extension not in allowed_doc_extensions:
122
+ raise HTTPException(
123
+ status_code=400,
124
+ detail=f"Unsupported file type. Allowed types: {', '.join(sorted(allowed_doc_extensions))}"
125
+ )
126
+
127
+ # Create session directory
128
+ session_upload_dir = os.path.join(uploads_root, session_id or "temp")
129
+ os.makedirs(session_upload_dir, exist_ok=True)
130
+
131
+ # Save file
132
+ timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
133
+ sanitized_name = file.filename.replace(' ', '_')
134
+ stored_filename = f"{timestamp}_{sanitized_name}"
135
+ stored_path = os.path.join(session_upload_dir, stored_filename)
136
+
137
+ content = await file.read()
138
+ with open(stored_path, 'wb') as f:
139
+ f.write(content)
140
+
141
+ # Create document info
142
+ relative_path = os.path.relpath(stored_path, uploads_root).replace('\\', '/')
143
+ document_info = {
144
+ 'path': relative_path,
145
+ 'name': file.filename,
146
+ 'type': file.content_type,
147
+ 'extension': file_extension,
148
+ }
149
+
150
+ # Auto-generate query if not provided
151
+ if not query:
152
+ query = f"Please analyze the uploaded document: {file.filename}"
153
+
154
+ except HTTPException:
155
+ raise
156
+ except Exception as e:
157
+ logger.error(f"Error handling document upload: {e}")
158
+ raise HTTPException(status_code=500, detail="Failed to process document")
159
+
160
+ # Handle image upload
161
+ if image:
162
+ try:
163
+ # Validate image type
164
+ file_extension = image.filename.rsplit('.', 1)[1].lower() if '.' in image.filename else ''
165
+ allowed_image_extensions = {
166
+ "jpg", "jpeg", "png"
167
+ }
168
+
169
+ if file_extension not in allowed_image_extensions:
170
+ raise HTTPException(
171
+ status_code=400,
172
+ detail=f"Unsupported image type. Allowed types: {', '.join(sorted(allowed_image_extensions))}"
173
+ )
174
+
175
+ # Create session directory
176
+ session_upload_dir = os.path.join(uploads_root, session_id or "temp")
177
+ os.makedirs(session_upload_dir, exist_ok=True)
178
+
179
+ # Save image
180
+ timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
181
+ sanitized_name = image.filename.replace(' ', '_')
182
+ stored_filename = f"{timestamp}_{sanitized_name}"
183
+ stored_path = os.path.join(session_upload_dir, stored_filename)
184
+
185
+ content = await image.read()
186
+ with open(stored_path, 'wb') as f:
187
+ f.write(content)
188
+
189
+ # Create image info
190
+ relative_path = os.path.relpath(stored_path, uploads_root).replace('\\', '/')
191
+ image_info = {
192
+ 'path': relative_path,
193
+ 'name': image.filename,
194
+ 'type': image.content_type,
195
+ 'extension': file_extension,
196
+ 'prompt': query,
197
+ }
198
+
199
+ # Auto-generate query if not provided
200
+ if not query:
201
+ query = f"Please analyze this skin image: {image.filename}"
202
+
203
+ except HTTPException:
204
+ raise
205
+ except Exception as e:
206
+ logger.error(f"Error handling image upload: {e}")
207
+ raise HTTPException(status_code=500, detail="Failed to process image")
208
+
209
+ # Ensure we have a query
210
+ if not query:
211
+ raise HTTPException(status_code=400, detail="Query text is required")
212
+
213
  try:
214
  agent_service = GoogleAgentService(
215
  token=token,
216
+ session_id=session_id,
217
+ document=document_info,
218
+ image=image_info,
219
  )
220
  except Exception as exc:
221
  logger.error("Failed to initialise agent service: %s", exc, exc_info=True)
222
  raise HTTPException(status_code=500, detail="Unable to initialise agent")
223
 
224
  return StreamingResponse(
225
+ stream_agent_response(agent_service, query),
226
  media_type="text/event-stream",
227
  headers={
228
  "Cache-Control": "no-cache",
 
247
  "image_search",
248
  "streaming",
249
  "tool_calls",
250
+ "file_upload",
251
+ "image_upload",
252
  ],
253
  }
254
  except Exception as exc:
app/routers/chat.py CHANGED
@@ -188,91 +188,6 @@ async def export_all_chats(username: str = Depends(get_current_user)):
188
  except Exception as e:
189
  raise HTTPException(status_code=500, detail=str(e))
190
 
191
- @router.post('/report-analysis')
192
- async def upload_report(
193
- file: UploadFile = File(...),
194
- session_id: str = Form(...),
195
- authorization: str = Header(None),
196
- username: str = Depends(get_current_user)
197
- ):
198
- try:
199
- _ = authorization.split(" ")[1]
200
-
201
- if not file.filename:
202
- return JSONResponse(
203
- status_code=400,
204
- content={"status": "error", "error": "Empty file provided"}
205
- )
206
-
207
- file_extension = file.filename.rsplit('.', 1)[1].lower() if '.' in file.filename else ''
208
- allowed_extensions = {
209
- 'pdf',
210
- 'xlsx',
211
- 'xls',
212
- 'csv',
213
- 'jpg',
214
- 'jpeg',
215
- 'png',
216
- 'doc',
217
- 'docx',
218
- 'ppt',
219
- 'pptx',
220
- 'txt',
221
- 'html'
222
- }
223
-
224
- if file_extension not in allowed_extensions:
225
- return JSONResponse(
226
- status_code=400,
227
- content={
228
- "status": "error",
229
- "error": f"Unsupported file type. Allowed types: {', '.join(sorted(allowed_extensions))}",
230
- }
231
- )
232
-
233
- default_upload_root = os.path.abspath(
234
- os.path.join(os.path.dirname(__file__), "..", "..", "uploads")
235
- )
236
- uploads_root = os.getenv('DERMAI_UPLOAD_DIR', default_upload_root)
237
- session_upload_dir = os.path.join(uploads_root, session_id)
238
- os.makedirs(session_upload_dir, exist_ok=True)
239
-
240
- timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
241
- sanitized_name = file.filename.replace(' ', '_')
242
- stored_filename = f"{timestamp}_{sanitized_name}"
243
- stored_path = os.path.join(session_upload_dir, stored_filename)
244
-
245
- content = await file.read()
246
- with open(stored_path, 'wb') as f:
247
- f.write(content)
248
-
249
- relative_root = os.path.abspath(uploads_root)
250
- absolute_path = os.path.abspath(stored_path)
251
- relative_path = os.path.relpath(absolute_path, relative_root)
252
-
253
- return {
254
- "status": "success",
255
- "message": "File uploaded successfully",
256
- "file": {
257
- "path": relative_path.replace('\\', '/'),
258
- "name": file.filename,
259
- "content_type": file.content_type,
260
- "size": len(content),
261
- "extension": file_extension,
262
- }
263
- }
264
-
265
- except Exception as e:
266
- logging.error(f"Error in upload_report: {str(e)}")
267
- raise HTTPException(
268
- status_code=500,
269
- detail={
270
- "status": "error",
271
- "error": "Internal server error",
272
- "details": str(e)
273
- }
274
- )
275
-
276
  @router.get('/skin-care-schedule')
277
  async def get_skin_care_schedule(
278
  authorization: str = Header(None),
 
188
  except Exception as e:
189
  raise HTTPException(status_code=500, detail=str(e))
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  @router.get('/skin-care-schedule')
192
  async def get_skin_care_schedule(
193
  authorization: str = Header(None),
test_skin_analysis.py DELETED
@@ -1,84 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Simple test script to verify that the modified analyze_skin_image function works correctly
4
- without the skin-vs-non-skin classification step.
5
- """
6
-
7
- import os
8
- import sys
9
- from pathlib import Path
10
-
11
- # Add the app directory to the Python path
12
- sys.path.insert(0, str(Path(__file__).parent / "app"))
13
-
14
- from services.tools import analyze_skin_image
15
-
16
-
17
- def test_analyze_skin_image():
18
- """Test the analyze_skin_image function with a sample image."""
19
-
20
- # Find the first available image in uploads
21
- uploads_dir = Path("uploads")
22
- image_files = []
23
-
24
- for subdir in uploads_dir.iterdir():
25
- if subdir.is_dir():
26
- for file in subdir.iterdir():
27
- if file.suffix.lower() in ['.jpg', '.jpeg', '.png']:
28
- image_files.append(file)
29
- break
30
- if image_files:
31
- break
32
-
33
- if not image_files:
34
- print("No image files found in uploads directory for testing")
35
- return False
36
-
37
- test_image = image_files[0]
38
- print(f"Testing with image: {test_image}")
39
-
40
- # Use relative path from uploads directory
41
- relative_path = test_image.relative_to(uploads_dir)
42
- print(f"Using relative path: {relative_path}")
43
-
44
- # Test the function
45
- try:
46
- result = analyze_skin_image(str(relative_path))
47
-
48
- print("=== Test Results ===")
49
- print(f"Status: {result.get('status')}")
50
- print(f"Is skin: {result.get('is_skin')}")
51
-
52
- if result.get('status') == 'success':
53
- if result.get('diagnosis'):
54
- print(f"Diagnosis: {result.get('diagnosis')}")
55
- print(f"Confidence: {result.get('confidence')}%")
56
- else:
57
- print("No diagnosis (confidence below threshold)")
58
- print(f"Confidence: {result.get('confidence')}%")
59
-
60
- print(f"Message: {result.get('message')}")
61
- print("✅ Test passed - function executed successfully")
62
- return True
63
- else:
64
- print(f"❌ Test failed - Error: {result.get('error_message')}")
65
- return False
66
-
67
- except Exception as e:
68
- print(f"❌ Test failed with exception: {e}")
69
- return False
70
-
71
-
72
- if __name__ == "__main__":
73
- print("Testing modified analyze_skin_image function...")
74
- print("This test verifies that skin disease classification works directly without skin-vs-non-skin pre-classification.")
75
- print()
76
-
77
- success = test_analyze_skin_image()
78
-
79
- if success:
80
- print("\n🎉 All tests passed! The skin disease classification is working correctly.")
81
- else:
82
- print("\n💥 Tests failed. Please check the error messages above.")
83
-
84
- sys.exit(0 if success else 1)