diff --git a/surfsense_backend/app/agents/new_chat/tools/create_notion_page.py b/surfsense_backend/app/agents/new_chat/tools/create_notion_page.py index bc3d2b81f..bbbab81c3 100644 --- a/surfsense_backend/app/agents/new_chat/tools/create_notion_page.py +++ b/surfsense_backend/app/agents/new_chat/tools/create_notion_page.py @@ -1,3 +1,4 @@ +import hashlib from typing import Any from langchain_core.tools import tool @@ -19,11 +20,19 @@ def create_create_notion_page_tool(): title: The title of the Notion page. content: The markdown content for the page body. """ + # Generate a unique page ID based on title for testing + # This helps verify if edited args were used + page_hash = hashlib.md5(title.encode()).hexdigest()[:8] + + # Return detailed response showing what was actually received return { "status": "success", - "page_id": "stub-page-id-12345", + "page_id": f"stub-page-{page_hash}", "title": title, - "url": "https://www.notion.so/stub-page-12345", + "content_preview": content[:100] + "..." if len(content) > 100 else content, + "content_length": len(content), + "url": f"https://www.notion.so/stub-page-{page_hash}", + "message": f"✅ Created Notion page '{title}' with {len(content)} characters", } return create_notion_page diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 448148229..045626307 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -864,7 +864,7 @@ export default function NewChatPage() { ); const handleResume = useCallback( - async (decisions: Array<{ type: string; message?: string }>) => { + async (decisions: Array<{ type: string; message?: string; edited_action?: { name: string; args: Record } }>) => { if (!pendingInterrupt) return; const { threadId: resumeThreadId, assistantMsgId } = pendingInterrupt; setPendingInterrupt(null); @@ -1098,10 +1098,16 @@ export default function NewChatPage() { useEffect(() => { const handler = (e: Event) => { const detail = (e as CustomEvent).detail as { - decisions: Array<{ type: string; message?: string }>; + decisions: Array<{ + type: string; + message?: string; + edited_action?: { name: string; args: Record }; + }>; }; if (detail?.decisions && pendingInterrupt) { - const decisionType = detail.decisions[0]?.type as "approve" | "reject"; + const decision = detail.decisions[0]; + const decisionType = decision?.type as "approve" | "reject" | "edit"; + setMessages((prev) => prev.map((m) => { if (m.id !== pendingInterrupt.assistantMsgId) return m; @@ -1113,9 +1119,23 @@ export default function NewChatPage() { part.result !== null && "__interrupt__" in part.result ) { + // For edit decisions, also update the displayed args + if (decisionType === "edit" && decision.edited_action) { + return { + ...part, + args: decision.edited_action.args, // Update displayed args + result: { + ...(part.result as Record), + __decided__: decisionType + }, + }; + } return { ...part, - result: { ...(part.result as Record), __decided__: decisionType }, + result: { + ...(part.result as Record), + __decided__: decisionType + }, }; } return part; diff --git a/surfsense_web/app/globals.css b/surfsense_web/app/globals.css index cf6f48437..475bcb1b4 100644 --- a/surfsense_web/app/globals.css +++ b/surfsense_web/app/globals.css @@ -187,5 +187,21 @@ button { background-color: hsl(var(--muted-foreground) / 0.4); } +/* Human-in-the-loop approval card animations */ +@keyframes pulse-subtle { + 0%, 100% { + opacity: 1; + box-shadow: 0 0 0 0 rgb(0 0 0 / 0.15); + } + 50% { + opacity: 1; + box-shadow: 0 0 20px 4px rgb(0 0 0 / 0.12); + } +} + +.animate-pulse-subtle { + animation: pulse-subtle 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + @source '../node_modules/@llamaindex/chat-ui/**/*.{ts,tsx}'; @source '../node_modules/streamdown/dist/*.js'; diff --git a/surfsense_web/components/tool-ui/create-notion-page.tsx b/surfsense_web/components/tool-ui/create-notion-page.tsx index 9f69067b1..a1cf67109 100644 --- a/surfsense_web/components/tool-ui/create-notion-page.tsx +++ b/surfsense_web/components/tool-ui/create-notion-page.tsx @@ -1,13 +1,15 @@ "use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; -import { CheckIcon, FileTextIcon, Loader2Icon, XIcon } from "lucide-react"; +import { AlertTriangleIcon, CheckIcon, FileTextIcon, Loader2Icon, PencilIcon, XIcon } from "lucide-react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; interface InterruptResult { __interrupt__: true; - __decided__?: "approve" | "reject"; + __decided__?: "approve" | "reject" | "edit"; action_requests: Array<{ name: string; args: Record; @@ -24,6 +26,9 @@ interface SuccessResult { page_id: string; title: string; url: string; + content_preview?: string; + content_length?: number; + message?: string; } type CreateNotionPageResult = InterruptResult | SuccessResult; @@ -44,50 +49,114 @@ function ApprovalCard({ }: { args: Record; interruptData: InterruptResult; - onDecision: (decision: { type: "approve" | "reject"; message?: string }) => void; + onDecision: (decision: { type: "approve" | "reject" | "edit"; message?: string; edited_action?: { name: string; args: Record } }) => void; }) { - const [decided, setDecided] = useState<"approve" | "reject" | null>( + const [decided, setDecided] = useState<"approve" | "reject" | "edit" | null>( interruptData.__decided__ ?? null ); + const [isEditing, setIsEditing] = useState(false); + const [editedArgs, setEditedArgs] = useState>(args); + const reviewConfig = interruptData.review_configs[0]; const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; + const canEdit = allowedDecisions.includes("edit"); return ( -
-
-
- +
+
+
+
-

Create Notion Page

-

- Requires your approval to proceed +

Create Notion Page

+

+ {isEditing ? "You can edit the arguments below" : "Requires your approval to proceed"}

-
- {args.title != null && ( -
-

Title

-

{String(args.title)}

+ {/* Display mode - show args as read-only */} + {!isEditing && ( +
+ {args.title != null && ( +
+

Title

+

{String(args.title)}

+
+ )} + {args.content != null && ( +
+

Content

+

{String(args.content)}

+
+ )}
)} - {args.content != null && ( -
-

Content

-

{String(args.content)}

-
- )} -
-
+ {/* Edit mode - show editable form fields */} + {isEditing && ( +
+
+ + setEditedArgs({ ...editedArgs, title: e.target.value })} + placeholder="Enter page title" + /> +
+
+ +