Spaces:
Running
Running
Commit
Β·
8da73c0
1
Parent(s):
7e89912
added logs
Browse files- backend/agent.py +36 -42
- backend/api.py +48 -4
- backend/g_cal.py +7 -10
- ui/src/useChat.tsx +8 -0
backend/agent.py
CHANGED
@@ -27,6 +27,9 @@ from langgraph.graph import StateGraph, START, END
|
|
27 |
from langgraph.prebuilt import ToolNode
|
28 |
from langgraph.checkpoint.memory import MemorySaver
|
29 |
|
|
|
|
|
|
|
30 |
# from google.oauth2.credentials import Credentials
|
31 |
# from google_auth_oauthlib.flow import InstalledAppFlow
|
32 |
# from googleapiclient.discovery import build
|
@@ -264,21 +267,6 @@ def memory_flush():
|
|
264 |
memory_dirty = False
|
265 |
memory_write_count = 0
|
266 |
|
267 |
-
# def _get_gcal_service():
|
268 |
-
# creds = None
|
269 |
-
# if TOKEN_FILE.exists():
|
270 |
-
# creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), GOOGLE_SCOPES)
|
271 |
-
# if not creds or not creds.valid:
|
272 |
-
# if creds and creds.expired and creds.refresh_token:
|
273 |
-
# creds.refresh(Request()) # type: ignore
|
274 |
-
# else:
|
275 |
-
# flow = InstalledAppFlow.from_client_secrets_file(str(CLIENT_SECRET_FILE), GOOGLE_SCOPES)
|
276 |
-
# # This opens a local browser once per server instance to authorize
|
277 |
-
# creds = flow.run_local_server(port=8765, prompt="consent")
|
278 |
-
# with open(TOKEN_FILE, "w") as f:
|
279 |
-
# f.write(creds.to_json())
|
280 |
-
# return build("calendar", "v3", credentials=creds)
|
281 |
-
|
282 |
class Attendee(TypedDict):
|
283 |
email: str
|
284 |
optional: Optional[bool]
|
@@ -298,36 +286,42 @@ def schedule_meeting(
|
|
298 |
Create a Google Calendar event (and optional Google Meet link).
|
299 |
Returns a human-readable confirmation.
|
300 |
"""
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
params = {}
|
313 |
-
if make_meet_link:
|
314 |
-
body["conferenceData"] = {
|
315 |
-
"createRequest": {"requestId": str(uuid4())}
|
316 |
}
|
317 |
-
params["conferenceDataVersion"] = 1
|
318 |
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
|
328 |
-
|
329 |
-
|
330 |
-
|
|
|
|
|
|
|
331 |
|
332 |
@tool("update_meeting")
|
333 |
def update_meeting(
|
|
|
27 |
from langgraph.prebuilt import ToolNode
|
28 |
from langgraph.checkpoint.memory import MemorySaver
|
29 |
|
30 |
+
import logging
|
31 |
+
tool_log = logging.getLogger("tools")
|
32 |
+
|
33 |
# from google.oauth2.credentials import Credentials
|
34 |
# from google_auth_oauthlib.flow import InstalledAppFlow
|
35 |
# from googleapiclient.discovery import build
|
|
|
267 |
memory_dirty = False
|
268 |
memory_write_count = 0
|
269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
270 |
class Attendee(TypedDict):
|
271 |
email: str
|
272 |
optional: Optional[bool]
|
|
|
286 |
Create a Google Calendar event (and optional Google Meet link).
|
287 |
Returns a human-readable confirmation.
|
288 |
"""
|
289 |
+
try:
|
290 |
+
tool_log.info(f"schedule_meeting args title={title} start={start_rfc3339} end={end_rfc3339} attendees={[a.get('email') for a in (attendees or [])]}")
|
291 |
+
svc = get_gcal_service()
|
292 |
+
|
293 |
+
body = {
|
294 |
+
"summary": title,
|
295 |
+
"description": description or "",
|
296 |
+
"location": location or "",
|
297 |
+
"start": {"dateTime": start_rfc3339},
|
298 |
+
"end": {"dateTime": end_rfc3339},
|
299 |
+
"attendees": [{"email": a["email"], "optional": a.get("optional", False)} for a in (attendees or [])],
|
|
|
|
|
|
|
|
|
300 |
}
|
|
|
301 |
|
302 |
+
params = {}
|
303 |
+
if make_meet_link:
|
304 |
+
body["conferenceData"] = {
|
305 |
+
"createRequest": {"requestId": str(uuid4())}
|
306 |
+
}
|
307 |
+
params["conferenceDataVersion"] = 1
|
308 |
+
|
309 |
+
event = svc.events().insert(calendarId=calendar_id, body=body, **params).execute()
|
310 |
+
tool_log.info(f"β
created event id={event.get('id')} html={event.get('htmlLink')}")
|
311 |
+
meet = (
|
312 |
+
(event.get("conferenceData") or {})
|
313 |
+
.get("entryPoints", [{}])[0]
|
314 |
+
.get("uri")
|
315 |
+
if make_meet_link
|
316 |
+
else None
|
317 |
+
)
|
318 |
|
319 |
+
attendee_str = ", ".join([a["email"] for a in (attendees or [])]) or "no attendees"
|
320 |
+
when = f'{event["start"]["dateTime"]} β {event["end"]["dateTime"]}'
|
321 |
+
return f"β
Scheduled: {title}\nπ
When: {when}\nπ₯ With: {attendee_str}\nπ Meet: {meet or 'β'}\nποΈ Calendar: {calendar_id}\nπ Event ID: {event.get('id','')}"
|
322 |
+
except Exception as e:
|
323 |
+
tool_log.exception("β schedule_meeting failed")
|
324 |
+
return f"Calendar error: {e}"
|
325 |
|
326 |
@tool("update_meeting")
|
327 |
def update_meeting(
|
backend/api.py
CHANGED
@@ -12,6 +12,16 @@ from uuid import uuid4
|
|
12 |
from pathlib import Path
|
13 |
import json, secrets, urllib.parse, os
|
14 |
from google_auth_oauthlib.flow import Flow
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
from .agent import app as lg_app
|
17 |
|
@@ -33,6 +43,14 @@ api.add_middleware(
|
|
33 |
allow_credentials=True,
|
34 |
)
|
35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
from .g_cal import SCOPES, TOKEN_FILE
|
37 |
|
38 |
CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
|
@@ -78,7 +96,21 @@ def oauth_callback(request: Request):
|
|
78 |
|
79 |
@api.get("/api/health")
|
80 |
def health():
|
81 |
-
return {"ok": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
|
83 |
# --- GET route for EventSource (matches the React UI I gave you) ---
|
84 |
# GET
|
@@ -136,16 +168,28 @@ UI_DIST = REPO_ROOT / "ui" / "dist"
|
|
136 |
|
137 |
RESUME_PATH = REPO_ROOT / "backend" / "assets" / "KrishnaVamsiDhulipalla.pdf"
|
138 |
|
139 |
-
@api.get("/resume/download")
|
140 |
def resume_download():
|
|
|
141 |
if not RESUME_PATH.is_file():
|
142 |
-
return
|
143 |
-
# Same-origin download; content-disposition prompts save/open dialog
|
144 |
return FileResponse(
|
145 |
path=str(RESUME_PATH),
|
146 |
media_type="application/pdf",
|
147 |
filename="Krishna_Vamsi_Dhulipalla_Resume.pdf",
|
148 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
149 |
|
150 |
if UI_DIST.is_dir():
|
151 |
api.mount("/", StaticFiles(directory=str(UI_DIST), html=True), name="ui")
|
|
|
12 |
from pathlib import Path
|
13 |
import json, secrets, urllib.parse, os
|
14 |
from google_auth_oauthlib.flow import Flow
|
15 |
+
from .g_cal import get_gcal_service
|
16 |
+
|
17 |
+
|
18 |
+
# logging + helpers
|
19 |
+
import logging, os, time
|
20 |
+
from datetime import datetime, timezone
|
21 |
+
from fastapi.responses import JSONResponse, FileResponse, RedirectResponse
|
22 |
+
|
23 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s :: %(message)s")
|
24 |
+
log = logging.getLogger("api")
|
25 |
|
26 |
from .agent import app as lg_app
|
27 |
|
|
|
43 |
allow_credentials=True,
|
44 |
)
|
45 |
|
46 |
+
# logging + helpers
|
47 |
+
import logging, os, time
|
48 |
+
from datetime import datetime, timezone
|
49 |
+
from fastapi.responses import JSONResponse, FileResponse, RedirectResponse
|
50 |
+
|
51 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s :: %(message)s")
|
52 |
+
log = logging.getLogger("api")
|
53 |
+
|
54 |
from .g_cal import SCOPES, TOKEN_FILE
|
55 |
|
56 |
CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
|
|
|
96 |
|
97 |
@api.get("/api/health")
|
98 |
def health():
|
99 |
+
return {"ok": True, "ts": datetime.now(timezone.utc).isoformat()}
|
100 |
+
|
101 |
+
@api.get("/api/debug/routes")
|
102 |
+
def list_routes():
|
103 |
+
return {"routes": sorted([getattr(r, "path", str(r)) for r in api.router.routes])}
|
104 |
+
|
105 |
+
@api.get("/api/debug/env")
|
106 |
+
def debug_env():
|
107 |
+
return {
|
108 |
+
"public_base_url": os.getenv("PUBLIC_BASE_URL"),
|
109 |
+
"has_google_client_id": bool(os.getenv("GOOGLE_CLIENT_ID")),
|
110 |
+
"has_google_client_secret": bool(os.getenv("GOOGLE_CLIENT_SECRET")),
|
111 |
+
"ui_dist_exists": UI_DIST.is_dir(),
|
112 |
+
"resume_exists": RESUME_PATH.is_file(),
|
113 |
+
}
|
114 |
|
115 |
# --- GET route for EventSource (matches the React UI I gave you) ---
|
116 |
# GET
|
|
|
168 |
|
169 |
RESUME_PATH = REPO_ROOT / "backend" / "assets" / "KrishnaVamsiDhulipalla.pdf"
|
170 |
|
171 |
+
@api.get("/api/resume/download")
|
172 |
def resume_download():
|
173 |
+
log.info(f"π resume download hit; exists={RESUME_PATH.is_file()} path={RESUME_PATH}")
|
174 |
if not RESUME_PATH.is_file():
|
175 |
+
return JSONResponse({"ok": False, "error": "Resume not found"}, status_code=404)
|
|
|
176 |
return FileResponse(
|
177 |
path=str(RESUME_PATH),
|
178 |
media_type="application/pdf",
|
179 |
filename="Krishna_Vamsi_Dhulipalla_Resume.pdf",
|
180 |
)
|
181 |
+
|
182 |
+
@api.get("/api/health/google")
|
183 |
+
def google_health():
|
184 |
+
try:
|
185 |
+
svc = get_gcal_service()
|
186 |
+
data = svc.calendarList().list(maxResults=1).execute()
|
187 |
+
names = [item.get("summary") for item in data.get("items", [])]
|
188 |
+
log.info(f"π’ Google token OK; calendars={names}")
|
189 |
+
return {"ok": True, "calendars": names}
|
190 |
+
except Exception as e:
|
191 |
+
log.exception("π΄ Google Calendar health failed")
|
192 |
+
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
193 |
|
194 |
if UI_DIST.is_dir():
|
195 |
api.mount("/", StaticFiles(directory=str(UI_DIST), html=True), name="ui")
|
backend/g_cal.py
CHANGED
@@ -1,21 +1,18 @@
|
|
1 |
-
|
2 |
-
import os
|
3 |
from pathlib import Path
|
4 |
from google.oauth2.credentials import Credentials
|
5 |
from googleapiclient.discovery import build
|
6 |
|
7 |
-
|
8 |
-
SCOPES = ["https://www.googleapis.com/auth/calendar.events"]
|
9 |
|
10 |
-
|
11 |
TOKEN_FILE = Path(os.getenv("GCAL_TOKEN_PATH", "/data/google_token.json"))
|
12 |
|
13 |
def get_gcal_service():
|
14 |
-
""
|
15 |
-
Load saved Google OAuth credentials and return a Calendar API client.
|
16 |
-
Raise a clear error if not connected yet.
|
17 |
-
"""
|
18 |
if not TOKEN_FILE.exists():
|
19 |
raise RuntimeError("Google Calendar not connected. Visit /oauth/google/start to connect.")
|
20 |
creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)
|
21 |
-
|
|
|
|
|
|
1 |
+
import os, logging
|
|
|
2 |
from pathlib import Path
|
3 |
from google.oauth2.credentials import Credentials
|
4 |
from googleapiclient.discovery import build
|
5 |
|
6 |
+
log = logging.getLogger("g_cal")
|
|
|
7 |
|
8 |
+
SCOPES = ["https://www.googleapis.com/auth/calendar.events"]
|
9 |
TOKEN_FILE = Path(os.getenv("GCAL_TOKEN_PATH", "/data/google_token.json"))
|
10 |
|
11 |
def get_gcal_service():
|
12 |
+
log.info(f"π get_gcal_service token_path={TOKEN_FILE} exists={TOKEN_FILE.exists()}")
|
|
|
|
|
|
|
13 |
if not TOKEN_FILE.exists():
|
14 |
raise RuntimeError("Google Calendar not connected. Visit /oauth/google/start to connect.")
|
15 |
creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)
|
16 |
+
svc = build("calendar", "v3", credentials=creds)
|
17 |
+
log.info("β
built calendar service client")
|
18 |
+
return svc
|
ui/src/useChat.tsx
CHANGED
@@ -186,6 +186,14 @@ export function useChat() {
|
|
186 |
url.searchParams.set("thread_id", thread_id);
|
187 |
|
188 |
const es = new EventSource(url.toString());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
189 |
esRef.current = es;
|
190 |
setIsStreaming(true);
|
191 |
setHasFirstToken(false);
|
|
|
186 |
url.searchParams.set("thread_id", thread_id);
|
187 |
|
188 |
const es = new EventSource(url.toString());
|
189 |
+
es.addEventListener("tool", (ev: MessageEvent) => {
|
190 |
+
try {
|
191 |
+
const data = JSON.parse((ev as MessageEvent<string>).data || "{}");
|
192 |
+
console.log("[TOOL]", data.phase, data.name);
|
193 |
+
} catch {
|
194 |
+
console.log("[TOOL]", (ev as MessageEvent<string>).data);
|
195 |
+
}
|
196 |
+
});
|
197 |
esRef.current = es;
|
198 |
setIsStreaming(true);
|
199 |
setHasFirstToken(false);
|