Update app.py
Browse files
app.py
CHANGED
|
@@ -133,7 +133,7 @@ STRICT IDENTITY RULES:
|
|
| 133 |
|
| 134 |
3. NATURAL CONVERSATION & TOOL RESPONSE STYLE:
|
| 135 |
- Do NOT introduce your company, founder, or model name in general responses unless requested.
|
| 136 |
-
- Always summarize and format Google Connector execution results into clean, polite, and natural language automatically matching the language used by the user.
|
| 137 |
- Never output raw technical logs, IDs, or developer headers directly to the user."""
|
| 138 |
|
| 139 |
TOOLS = [
|
|
@@ -358,6 +358,65 @@ TOOLS = [
|
|
| 358 |
"required": ["file_id", "content"]
|
| 359 |
}
|
| 360 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
}
|
| 362 |
]
|
| 363 |
|
|
@@ -444,6 +503,157 @@ async def get_google_creds(user_id: str, service_name: str = "google"):
|
|
| 444 |
pass
|
| 445 |
return creds
|
| 446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
async def gmail_search_emails(user_id: str, query: str = "", max_results: int = 5):
|
| 448 |
creds = await get_google_creds(user_id, "gmail")
|
| 449 |
if not creds:
|
|
@@ -724,6 +934,14 @@ async def execute_tool_call(user_id: str, func_name: str, args: dict) -> str:
|
|
| 724 |
return await drive_delete_file(user_id, args.get("file_id", ""))
|
| 725 |
elif func_name == "drive_update_file":
|
| 726 |
return await drive_update_file(user_id, args.get("file_id", ""), args.get("content", ""))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
return "Unknown tool function."
|
| 728 |
|
| 729 |
def count_tokens(messages):
|
|
@@ -932,7 +1150,7 @@ async def generate(request: Request):
|
|
| 932 |
|
| 933 |
if content and not has_tool_call:
|
| 934 |
yield json.dumps({"answer": content}) + "\n"
|
| 935 |
-
|
| 936 |
continue
|
| 937 |
|
| 938 |
if has_tool_call and tool_calls_acc:
|
|
|
|
| 133 |
|
| 134 |
3. NATURAL CONVERSATION & TOOL RESPONSE STYLE:
|
| 135 |
- Do NOT introduce your company, founder, or model name in general responses unless requested.
|
| 136 |
+
- Always summarize and format Google and Notion Connector execution results into clean, polite, and natural language automatically matching the language used by the user.
|
| 137 |
- Never output raw technical logs, IDs, or developer headers directly to the user."""
|
| 138 |
|
| 139 |
TOOLS = [
|
|
|
|
| 358 |
"required": ["file_id", "content"]
|
| 359 |
}
|
| 360 |
}
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"type": "function",
|
| 364 |
+
"function": {
|
| 365 |
+
"name": "notion_search",
|
| 366 |
+
"description": "Search Notion pages, databases, or documents by query",
|
| 367 |
+
"parameters": {
|
| 368 |
+
"type": "object",
|
| 369 |
+
"properties": {
|
| 370 |
+
"query": {"type": "string", "description": "Search keyword or empty to list recent pages"},
|
| 371 |
+
"max_results": {"type": "integer", "description": "Number of results to fetch, default 5"}
|
| 372 |
+
}
|
| 373 |
+
}
|
| 374 |
+
}
|
| 375 |
+
},
|
| 376 |
+
{
|
| 377 |
+
"type": "function",
|
| 378 |
+
"function": {
|
| 379 |
+
"name": "notion_get_page_content",
|
| 380 |
+
"description": "Read text content and blocks of a specific Notion page by page_id",
|
| 381 |
+
"parameters": {
|
| 382 |
+
"type": "object",
|
| 383 |
+
"properties": {
|
| 384 |
+
"page_id": {"type": "string", "description": "The ID of the Notion page"}
|
| 385 |
+
},
|
| 386 |
+
"required": ["page_id"]
|
| 387 |
+
}
|
| 388 |
+
}
|
| 389 |
+
},
|
| 390 |
+
{
|
| 391 |
+
"type": "function",
|
| 392 |
+
"function": {
|
| 393 |
+
"name": "notion_create_page",
|
| 394 |
+
"description": "Create a new page in Notion inside a parent page or database",
|
| 395 |
+
"parameters": {
|
| 396 |
+
"type": "object",
|
| 397 |
+
"properties": {
|
| 398 |
+
"parent_id": {"type": "string", "description": "Optional parent page or database ID"},
|
| 399 |
+
"title": {"type": "string", "description": "Title of the new Notion page"},
|
| 400 |
+
"content": {"type": "string", "description": "Initial text content for the page"}
|
| 401 |
+
},
|
| 402 |
+
"required": ["title"]
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
},
|
| 406 |
+
{
|
| 407 |
+
"type": "function",
|
| 408 |
+
"function": {
|
| 409 |
+
"name": "notion_append_block_children",
|
| 410 |
+
"description": "Append text or paragraph blocks to an existing Notion page by page_id",
|
| 411 |
+
"parameters": {
|
| 412 |
+
"type": "object",
|
| 413 |
+
"properties": {
|
| 414 |
+
"page_id": {"type": "string", "description": "The ID of the Notion page"},
|
| 415 |
+
"content": {"type": "string", "description": "Text content to append to the page"}
|
| 416 |
+
},
|
| 417 |
+
"required": ["page_id", "content"]
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
}
|
| 421 |
]
|
| 422 |
|
|
|
|
| 503 |
pass
|
| 504 |
return creds
|
| 505 |
|
| 506 |
+
async def notion_get_headers(user_id: str):
|
| 507 |
+
token_data = await get_token_from_supabase(user_id, "notion")
|
| 508 |
+
if not token_data:
|
| 509 |
+
return None
|
| 510 |
+
access_token = token_data.get("access_token") or token_data.get("token")
|
| 511 |
+
if not access_token:
|
| 512 |
+
return None
|
| 513 |
+
return {
|
| 514 |
+
"Authorization": f"Bearer {access_token}",
|
| 515 |
+
"Notion-Version": "2022-06-28",
|
| 516 |
+
"Content-Type": "application/json"
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
async def notion_search(user_id: str, query: str = "", max_results: int = 5):
|
| 520 |
+
headers = await notion_get_headers(user_id)
|
| 521 |
+
if not headers:
|
| 522 |
+
return "Notion is not connected. Please connect Notion account."
|
| 523 |
+
try:
|
| 524 |
+
url = "https://api.notion.com/v1/search"
|
| 525 |
+
payload = {"page_size": max_results}
|
| 526 |
+
if query:
|
| 527 |
+
payload["query"] = query
|
| 528 |
+
async with httpx.AsyncClient() as c:
|
| 529 |
+
res = await c.post(url, headers=headers, json=payload)
|
| 530 |
+
if res.status_code != 200:
|
| 531 |
+
return f"Error searching Notion: {res.text}"
|
| 532 |
+
data = res.json()
|
| 533 |
+
results = data.get("results", [])
|
| 534 |
+
if not results:
|
| 535 |
+
return "No pages or databases found in Notion."
|
| 536 |
+
items = []
|
| 537 |
+
for item in results:
|
| 538 |
+
item_id = item.get("id", "")
|
| 539 |
+
obj_type = item.get("object", "page")
|
| 540 |
+
title = "Untitled"
|
| 541 |
+
if obj_type == "page":
|
| 542 |
+
props = item.get("properties", {})
|
| 543 |
+
for k, v in props.items():
|
| 544 |
+
if v.get("type") == "title":
|
| 545 |
+
t_arr = v.get("title", [])
|
| 546 |
+
if t_arr:
|
| 547 |
+
title = "".join([t.get("plain_text", "") for t in t_arr])
|
| 548 |
+
break
|
| 549 |
+
elif obj_type == "database":
|
| 550 |
+
t_arr = item.get("title", [])
|
| 551 |
+
if t_arr:
|
| 552 |
+
title = "".join([t.get("plain_text", "") for t in t_arr])
|
| 553 |
+
url_val = item.get("url", "")
|
| 554 |
+
items.append(f"Page/DB ID: {item_id} | Type: {obj_type} | Title: {title} | URL: {url_val}")
|
| 555 |
+
return "\n".join(items)
|
| 556 |
+
except Exception as e:
|
| 557 |
+
return f"Error searching Notion: {str(e)}"
|
| 558 |
+
|
| 559 |
+
async def notion_get_page_content(user_id: str, page_id: str):
|
| 560 |
+
headers = await notion_get_headers(user_id)
|
| 561 |
+
if not headers:
|
| 562 |
+
return "Notion is not connected."
|
| 563 |
+
try:
|
| 564 |
+
clean_id = page_id.replace("-", "").strip()
|
| 565 |
+
url = f"https://api.notion.com/v1/blocks/{clean_id}/children"
|
| 566 |
+
async with httpx.AsyncClient() as c:
|
| 567 |
+
res = await c.get(url, headers=headers)
|
| 568 |
+
if res.status_code != 200:
|
| 569 |
+
return f"Error reading Notion page content: {res.text}"
|
| 570 |
+
data = res.json()
|
| 571 |
+
results = data.get("results", [])
|
| 572 |
+
if not results:
|
| 573 |
+
return "Notion page is empty."
|
| 574 |
+
texts = []
|
| 575 |
+
for b in results:
|
| 576 |
+
b_type = b.get("type")
|
| 577 |
+
if b_type and b_type in b and "rich_text" in b[b_type]:
|
| 578 |
+
rt = b[b_type].get("rich_text", [])
|
| 579 |
+
t = "".join([x.get("plain_text", "") for x in rt])
|
| 580 |
+
if t:
|
| 581 |
+
texts.append(t)
|
| 582 |
+
return "\n".join(texts) if texts else "Page contains non-text blocks."
|
| 583 |
+
except Exception as e:
|
| 584 |
+
return f"Error reading Notion page: {str(e)}"
|
| 585 |
+
|
| 586 |
+
async def notion_create_page(user_id: str, parent_id: str = None, title: str = "Untitled", content: str = ""):
|
| 587 |
+
headers = await notion_get_headers(user_id)
|
| 588 |
+
if not headers:
|
| 589 |
+
return "Notion is not connected."
|
| 590 |
+
try:
|
| 591 |
+
url = "https://api.notion.com/v1/pages"
|
| 592 |
+
payload = {
|
| 593 |
+
"properties": {
|
| 594 |
+
"title": {
|
| 595 |
+
"title": [{"text": {"content": title}}]
|
| 596 |
+
}
|
| 597 |
+
}
|
| 598 |
+
}
|
| 599 |
+
if parent_id:
|
| 600 |
+
clean_parent = parent_id.replace("-", "").strip()
|
| 601 |
+
payload["parent"] = {"page_id": clean_parent}
|
| 602 |
+
else:
|
| 603 |
+
search_res = await notion_search(user_id, max_results=1)
|
| 604 |
+
if "Page/DB ID: " in search_res:
|
| 605 |
+
p_id = search_res.split("Page/DB ID: ")[1].split(" ")[0].strip()
|
| 606 |
+
payload["parent"] = {"page_id": p_id.replace("-", "")}
|
| 607 |
+
else:
|
| 608 |
+
return "Cannot find parent page. Please provide a parent_id to create a new page in Notion."
|
| 609 |
+
|
| 610 |
+
if content:
|
| 611 |
+
payload["children"] = [
|
| 612 |
+
{
|
| 613 |
+
"object": "block",
|
| 614 |
+
"type": "paragraph",
|
| 615 |
+
"paragraph": {
|
| 616 |
+
"rich_text": [{"type": "text", "text": {"content": content}}]
|
| 617 |
+
}
|
| 618 |
+
}
|
| 619 |
+
]
|
| 620 |
+
|
| 621 |
+
async with httpx.AsyncClient() as c:
|
| 622 |
+
res = await c.post(url, headers=headers, json=payload)
|
| 623 |
+
if res.status_code != 200:
|
| 624 |
+
return f"Error creating Notion page: {res.text}"
|
| 625 |
+
data = res.json()
|
| 626 |
+
new_id = data.get("id", "")
|
| 627 |
+
return f"Successfully created Notion page '{title}' (Page ID: {new_id})."
|
| 628 |
+
except Exception as e:
|
| 629 |
+
return f"Error creating Notion page: {str(e)}"
|
| 630 |
+
|
| 631 |
+
async def notion_append_block_children(user_id: str, page_id: str, content: str):
|
| 632 |
+
headers = await notion_get_headers(user_id)
|
| 633 |
+
if not headers:
|
| 634 |
+
return "Notion is not connected."
|
| 635 |
+
try:
|
| 636 |
+
clean_id = page_id.replace("-", "").strip()
|
| 637 |
+
url = f"https://api.notion.com/v1/blocks/{clean_id}/children"
|
| 638 |
+
payload = {
|
| 639 |
+
"children": [
|
| 640 |
+
{
|
| 641 |
+
"object": "block",
|
| 642 |
+
"type": "paragraph",
|
| 643 |
+
"paragraph": {
|
| 644 |
+
"rich_text": [{"type": "text", "text": {"content": content}}]
|
| 645 |
+
}
|
| 646 |
+
}
|
| 647 |
+
]
|
| 648 |
+
}
|
| 649 |
+
async with httpx.AsyncClient() as c:
|
| 650 |
+
res = await c.patch(url, headers=headers, json=payload)
|
| 651 |
+
if res.status_code != 200:
|
| 652 |
+
return f"Error appending content to Notion: {res.text}"
|
| 653 |
+
return f"Successfully appended content to Notion page ID: {page_id}."
|
| 654 |
+
except Exception as e:
|
| 655 |
+
return f"Error appending content to Notion: {str(e)}"
|
| 656 |
+
|
| 657 |
async def gmail_search_emails(user_id: str, query: str = "", max_results: int = 5):
|
| 658 |
creds = await get_google_creds(user_id, "gmail")
|
| 659 |
if not creds:
|
|
|
|
| 934 |
return await drive_delete_file(user_id, args.get("file_id", ""))
|
| 935 |
elif func_name == "drive_update_file":
|
| 936 |
return await drive_update_file(user_id, args.get("file_id", ""), args.get("content", ""))
|
| 937 |
+
elif func_name == "notion_search":
|
| 938 |
+
return await notion_search(user_id, args.get("query", ""), args.get("max_results", 5))
|
| 939 |
+
elif func_name == "notion_get_page_content":
|
| 940 |
+
return await notion_get_page_content(user_id, args.get("page_id", ""))
|
| 941 |
+
elif func_name == "notion_create_page":
|
| 942 |
+
return await notion_create_page(user_id, args.get("parent_id"), args.get("title", "Untitled"), args.get("content", ""))
|
| 943 |
+
elif func_name == "notion_append_block_children":
|
| 944 |
+
return await notion_append_block_children(user_id, args.get("page_id", ""), args.get("content", ""))
|
| 945 |
return "Unknown tool function."
|
| 946 |
|
| 947 |
def count_tokens(messages):
|
|
|
|
| 1150 |
|
| 1151 |
if content and not has_tool_call:
|
| 1152 |
yield json.dumps({"answer": content}) + "\n"
|
| 1153 |
+
except json.JSONDecodeError:
|
| 1154 |
continue
|
| 1155 |
|
| 1156 |
if has_tool_call and tool_calls_acc:
|