44"""
55
66import os
7- import json
8- import asyncio
9- from typing import Optional , List , Dict , Any
10- from datetime import datetime
7+ from typing import List , Optional
118
12- from fastapi import APIRouter , HTTPException , Query , Depends
9+ from fastapi import APIRouter , Depends , HTTPException , Query
1310from fastapi .responses import HTMLResponse , RedirectResponse
1411from pydantic import BaseModel
1512
2017# Import OpenAI for embeddings
2118try :
2219 import openai
20+
2321 OPENAI_AVAILABLE = True
2422except ImportError :
2523 OPENAI_AVAILABLE = False
2624
2725logger = get_logger (__name__ )
2826router = APIRouter ()
2927
28+
3029# Models
3130class SyncFileRequest (BaseModel ):
3231 file_id : str
3332 process : bool = True
3433
34+
3535class BatchSyncRequest (BaseModel ):
3636 file_ids : List [str ]
3737 generate_embeddings : bool = True
3838
39+
3940# Dependency for memory service
4041async def get_memory_service ():
4142 """Get PostgreSQL memory service"""
4243 try :
4344 from app .factory import get_memory_service_instance
45+
4446 return get_memory_service_instance ()
45- except :
47+ except ImportError :
4648 return MemoryServicePostgres ()
4749
50+
4851@router .get ("/status" )
4952async def get_status ():
5053 """Get Google Drive connection status"""
5154 status = google_drive .get_connection_status ()
52- status [' credentials_configured' ] = bool (google_drive .client_id and google_drive .client_secret )
55+ status [" credentials_configured" ] = bool (google_drive .client_id and google_drive .client_secret )
5356 return status
5457
58+
5559@router .post ("/connect" )
5660async def connect ():
5761 """Initiate OAuth flow"""
5862 if not google_drive .client_id :
5963 raise HTTPException (status_code = 400 , detail = "Google OAuth not configured" )
60-
64+
6165 auth_url = google_drive .get_auth_url ()
6266 return {"auth_url" : auth_url }
6367
68+
6469@router .get ("/callback" )
6570async def oauth_callback (code : str ):
6671 """Handle OAuth callback"""
6772 result = await google_drive .exchange_code (code )
68-
69- if result .get (' success' ):
73+
74+ if result .get (" success" ):
7075 # Redirect to UI with success
71- return RedirectResponse (
72- url = "/static/gdrive-ui.html?connected=true" ,
73- status_code = 302
74- )
76+ return RedirectResponse (url = "/static/gdrive-ui.html?connected=true" , status_code = 302 )
7577 else :
7678 # Show error
77- html = f"""
78- <html>
79- <head>
80- <title>Connection Failed</title>
81- <style>
82- body {{
83- font-family: system-ui;
84- display: flex;
85- justify-content: center;
86- align-items: center;
87- height: 100vh;
88- background: #f3f4f6;
89- }}
90- .error {{
91- background: white;
92- padding: 2rem;
93- border-radius: 8px;
94- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
95- max-width: 500px;
96- }}
97- h1 {{ color: #ef4444; }}
98- pre {{ background: #f9fafb; padding: 1rem; border-radius: 4px; }}
99- a {{
100- display: inline-block;
101- margin-top: 1rem;
102- padding: 0.5rem 1rem;
103- background: #3b82f6;
104- color: white;
105- text-decoration: none;
106- border-radius: 4px;
107- }}
108- </style>
109- </head>
110- <body>
111- <div class="error">
112- <h1>❌ Connection Failed</h1>
113- <p>Could not connect to Google Drive:</p>
114- <pre>{ result .get ('error' , 'Unknown error' )} </pre>
115- <a href="/static/gdrive-ui.html">Try Again</a>
116- </div>
117- </body>
118- </html>
119- """
79+ html = (
80+ "<html><head><title>Connection Failed</title>"
81+ "<style>"
82+ "body{font-family:system-ui;display:flex;justify-content:center;"
83+ "align-items:center;height:100vh;background:#f3f4f6}"
84+ ".error{background:white;padding:2rem;border-radius:8px;"
85+ "box-shadow:0 2px 10px rgba(0,0,0,0.1);max-width:500px}"
86+ "h1{color:#ef4444}"
87+ "pre{background:#f9fafb;padding:1rem;border-radius:4px}"
88+ "a{display:inline-block;margin-top:1rem;padding:0.5rem 1rem;"
89+ "background:#3b82f6;color:white;text-decoration:none;border-radius:4px}"
90+ "</style></head><body>"
91+ '<div class="error">'
92+ "<h1>❌ Connection Failed</h1>"
93+ "<p>Could not connect to Google Drive:</p>"
94+ f"<pre>{ result .get ('error' , 'Unknown error' )} </pre>"
95+ '<a href="/static/gdrive-ui.html">Try Again</a>'
96+ "</div></body></html>"
97+ )
12098 return HTMLResponse (content = html )
12199
100+
122101@router .get ("/files" )
123102async def list_files (folder_id : Optional [str ] = None ):
124103 """List files from Google Drive"""
125104 if not google_drive .is_connected ():
126105 raise HTTPException (status_code = 401 , detail = "Not connected to Google Drive" )
127-
106+
128107 files = await google_drive .list_files (folder_id )
129- return {
130- "files" : files ,
131- "count" : len (files )
132- }
108+ return {"files" : files , "count" : len (files )}
109+
133110
134111@router .post ("/sync/file" )
135112async def sync_file (
136- request : SyncFileRequest ,
137- memory_service : MemoryServicePostgres = Depends (get_memory_service )
113+ request : SyncFileRequest , memory_service : MemoryServicePostgres = Depends (get_memory_service )
138114):
139115 """Sync a single file to Second Brain"""
140116 if not google_drive .is_connected ():
141117 raise HTTPException (status_code = 401 , detail = "Not connected to Google Drive" )
142-
118+
143119 try :
144120 # Get file content
145121 content = await google_drive .get_file_content (request .file_id )
146122 if not content :
147123 raise HTTPException (status_code = 404 , detail = "Could not retrieve file content" )
148-
124+
149125 # Get file metadata
150126 files = await google_drive .list_files ()
151- file_info = next ((f for f in files if f ['id' ] == request .file_id ), None )
152-
127+ file_info = next ((f for f in files if f ["id" ] == request .file_id ), None )
128+
153129 if not file_info :
154130 raise HTTPException (status_code = 404 , detail = "File not found" )
155-
131+
156132 # Generate embeddings if OpenAI is available
157133 embeddings = None
158- if OPENAI_AVAILABLE and os .getenv ("OPENAI_API_KEY" ) and os .getenv ("OPENAI_API_KEY" ) != "sk-mock-key-for-testing-only" :
134+ if (
135+ OPENAI_AVAILABLE
136+ and os .getenv ("OPENAI_API_KEY" )
137+ and os .getenv ("OPENAI_API_KEY" ) != "sk-mock-key-for-testing-only"
138+ ):
159139 try :
160140 openai .api_key = os .getenv ("OPENAI_API_KEY" )
161141 response = await openai .Embedding .acreate (
162142 model = "text-embedding-ada-002" ,
163- input = content [:8000 ] # Limit to 8000 chars for embedding
143+ input = content [:8000 ], # Limit to 8000 chars for embedding
164144 )
165- embeddings = response [' data' ][0 ][' embedding' ]
145+ embeddings = response [" data" ][0 ][" embedding" ]
166146 except Exception as e :
167147 logger .warning (f"Could not generate embeddings: { e } " )
168-
148+
169149 # Create memory in PostgreSQL
170150 memory_data = {
171151 "content" : content [:10000 ], # Limit content size
172152 "memory_type" : "document" ,
173- "tags" : ["google-drive" , file_info .get (' mimeType' , ' unknown' ).split ('/' )[- 1 ]],
153+ "tags" : ["google-drive" , file_info .get (" mimeType" , " unknown" ).split ("/" )[- 1 ]],
174154 "metadata" : {
175155 "source" : "google_drive" ,
176156 "file_id" : request .file_id ,
177- "file_name" : file_info .get (' name' ),
178- "mime_type" : file_info .get (' mimeType' ),
179- "size" : file_info .get (' size' ),
180- "modified_time" : file_info .get (' modifiedTime' ),
181- "web_view_link" : file_info .get (' webViewLink' )
182- }
157+ "file_name" : file_info .get (" name" ),
158+ "mime_type" : file_info .get (" mimeType" ),
159+ "size" : file_info .get (" size" ),
160+ "modified_time" : file_info .get (" modifiedTime" ),
161+ "web_view_link" : file_info .get (" webViewLink" ),
162+ },
183163 }
184-
164+
185165 # Add embeddings to metadata if available
186166 if embeddings :
187167 memory_data ["metadata" ]["embeddings" ] = embeddings
188-
168+
189169 memory = await memory_service .create_memory (** memory_data )
190-
170+
191171 return {
192172 "success" : True ,
193- "memory_id" : memory .get ('id' ),
194- "file_name" : file_info .get (' name' ),
173+ "memory_id" : memory .get ("id" ),
174+ "file_name" : file_info .get (" name" ),
195175 "content_length" : len (content ),
196- "has_embeddings" : embeddings is not None
176+ "has_embeddings" : embeddings is not None ,
197177 }
198-
178+
199179 except HTTPException :
200180 raise
201181 except Exception as e :
202182 logger .error (f"Error syncing file: { e } " )
203183 raise HTTPException (status_code = 500 , detail = str (e ))
204184
185+
205186@router .post ("/sync/batch" )
206187async def sync_batch (
207- request : BatchSyncRequest ,
208- memory_service : MemoryServicePostgres = Depends (get_memory_service )
188+ request : BatchSyncRequest , memory_service : MemoryServicePostgres = Depends (get_memory_service )
209189):
210190 """Sync multiple files in batch"""
211191 if not google_drive .is_connected ():
212192 raise HTTPException (status_code = 401 , detail = "Not connected to Google Drive" )
213-
193+
214194 results = []
215195 for file_id in request .file_ids [:10 ]: # Limit to 10 files at a time
216196 try :
217197 result = await sync_file (
218198 SyncFileRequest (file_id = file_id , process = request .generate_embeddings ),
219- memory_service
199+ memory_service ,
200+ )
201+ results .append (
202+ {"file_id" : file_id , "status" : "success" , "memory_id" : result .get ("memory_id" )}
220203 )
221- results .append ({
222- "file_id" : file_id ,
223- "status" : "success" ,
224- "memory_id" : result .get ("memory_id" )
225- })
226204 except Exception as e :
227- results .append ({
228- "file_id" : file_id ,
229- "status" : "failed" ,
230- "error" : str (e )
231- })
232-
205+ results .append ({"file_id" : file_id , "status" : "failed" , "error" : str (e )})
206+
233207 return {
234208 "processed" : len (results ),
235209 "successful" : sum (1 for r in results if r ["status" ] == "success" ),
236210 "failed" : sum (1 for r in results if r ["status" ] == "failed" ),
237- "results" : results
211+ "results" : results ,
238212 }
239213
214+
240215@router .post ("/disconnect" )
241216async def disconnect ():
242217 """Disconnect from Google Drive"""
243218 google_drive .tokens = {}
244219 google_drive .user_info = {}
245220 return {"status" : "disconnected" }
246221
222+
247223@router .get ("/search" )
248224async def search_drive (q : str = Query (..., description = "Search query" )):
249225 """Search files in Google Drive"""
250226 if not google_drive .is_connected ():
251227 raise HTTPException (status_code = 401 , detail = "Not connected to Google Drive" )
252-
228+
253229 # Search is just listing files for now
254230 # In production, you'd use the Drive API search
255231 files = await google_drive .list_files ()
256-
232+
257233 # Simple text search
258234 query_lower = q .lower ()
259- matching = [
260- f for f in files
261- if query_lower in f .get ('name' , '' ).lower ()
262- ]
263-
264- return {
265- "query" : q ,
266- "results" : matching ,
267- "count" : len (matching )
268- }
235+ matching = [f for f in files if query_lower in f .get ("name" , "" ).lower ()]
236+
237+ return {"query" : q , "results" : matching , "count" : len (matching )}
0 commit comments