Improve notion page creation approval UI

This commit is contained in:
CREDO23 2026-02-12 17:35:27 +02:00
parent c47171e218
commit 9023f2656b
3 changed files with 42 additions and 28 deletions

View file

@ -50,11 +50,15 @@ def create_create_notion_page_tool(
Returns:
Dictionary with:
- status: "success" or "error"
- page_id: Created page ID
- url: URL to the created page
- title: Page title
- message: Success or error message
- status: "success", "rejected", or "error"
- page_id: Created page ID (if success)
- url: URL to the created page (if success)
- title: Page title (if success)
- message: Result message
IMPORTANT: If status is "rejected", the user explicitly declined the action.
Respond with a brief acknowledgment (e.g., "Understood, I didn't create the page.")
and move on. Do NOT ask for parent page IDs, troubleshoot, or suggest alternatives.
Examples:
- "Create a Notion page titled 'Meeting Notes' with content 'Discussed project timeline'"
@ -80,10 +84,9 @@ def create_create_notion_page_tool(
"message": context["error"],
}
logger.info("Requesting approval for creating Notion page")
logger.info(f"Requesting approval for creating Notion page: '{title}'")
approval = interrupt({
"type": "notion_page_creation",
"message": f"Approve creating Notion page: '{title}'",
"action": {
"tool": "create_notion_page",
"params": {
@ -105,13 +108,14 @@ def create_create_notion_page_tool(
}
decision = decisions[0]
decision_type = decision.get("decision_type")
decision_type = decision.get("type") or decision.get("decision_type")
logger.info(f"User decision: {decision_type}")
if decision_type == "reject":
logger.info("Notion page creation rejected by user")
return {
"status": "rejected",
"message": "Page creation was rejected",
"message": "User declined. The page was not created. Do not ask again or suggest alternatives.",
}
edited_action = decision.get("edited_action", {})

View file

@ -523,19 +523,19 @@ class VercelStreamingService:
Handles two interrupt sources:
1. interrupt_on config (Deep Agent built-in): Already has action_requests/review_configs
2. interrupt() primitive (custom tool code): Has type/message/action/context
2. interrupt() primitive (custom tool code): Has type/action/context (message is optional)
Args:
interrupt_value: Raw interrupt payload from Deep Agent
Returns:
dict: Normalized payload with action_requests, review_configs, and optional context
dict: Normalized payload with action_requests, review_configs, and optional context/message
"""
if "action_requests" in interrupt_value and "review_configs" in interrupt_value:
return interrupt_value
interrupt_type = interrupt_value.get("type", "unknown")
message = interrupt_value.get("message", "Approval required")
message = interrupt_value.get("message")
action = interrupt_value.get("action", {})
context = interrupt_value.get("context", {})
@ -553,9 +553,10 @@ class VercelStreamingService:
}
],
"interrupt_type": interrupt_type,
"message": message,
"context": context,
}
if message:
normalized["message"] = message
return normalized
# =========================================================================

View file

@ -164,19 +164,15 @@ function ApprovalCard({
{/* Context section - account and parent page selection */}
{!decided && interruptData.context && (
<div className="border-b border-border px-4 py-3 bg-muted/30 space-y-3">
{interruptData.message && (
<p className="text-sm text-foreground">{interruptData.message}</p>
)}
{interruptData.context.error ? (
<p className="text-sm text-destructive">{interruptData.context.error}</p>
) : (
<>
{accounts.length > 0 && (
<div className="space-y-2">
<label className="text-xs font-medium text-muted-foreground">
<div className="text-xs font-medium text-muted-foreground">
Notion Account <span className="text-destructive">*</span>
</label>
</div>
<Select
value={selectedAccountId}
onValueChange={(value) => {
@ -190,7 +186,7 @@ function ApprovalCard({
<SelectContent>
{accounts.map((account) => (
<SelectItem key={account.id} value={String(account.id)}>
{account.workspace_name}
{account.workspace_name}
</SelectItem>
))}
</SelectContent>
@ -200,15 +196,15 @@ function ApprovalCard({
{selectedAccountId && (
<div className="space-y-2">
<label className="text-xs font-medium text-muted-foreground">
<div className="text-xs font-medium text-muted-foreground">
Parent Page (optional)
</label>
</div>
<Select value={selectedParentPageId} onValueChange={setSelectedParentPageId}>
<SelectTrigger className="w-full">
<SelectValue placeholder="None (create at root level)" />
<SelectValue placeholder="None" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__none__">None (create at root level)</SelectItem>
<SelectItem value="__none__">None</SelectItem>
{availableParentPages.map((page) => (
<SelectItem key={page.page_id} value={page.page_id}>
📄 {page.title}
@ -218,7 +214,7 @@ function ApprovalCard({
</Select>
{availableParentPages.length === 0 && selectedAccountId && (
<p className="text-xs text-muted-foreground">
No pages available. Page will be created at root level.
No pages available. Page will be created at workspace root.
</p>
)}
</div>
@ -249,7 +245,7 @@ function ApprovalCard({
)}
{/* Edit mode - show editable form fields */}
{isEditing && (
{isEditing && !decided && (
<div className="space-y-3 px-4 py-3 bg-card">
<div>
<label
@ -310,6 +306,7 @@ function ApprovalCard({
size="sm"
onClick={() => {
setDecided("edit");
setIsEditing(false);
onDecision({
type: "edit",
edited_action: {
@ -317,11 +314,13 @@ function ApprovalCard({
args: {
...editedArgs,
connector_id: selectedAccountId ? Number(selectedAccountId) : null,
parent_page_id: selectedParentPageId === "__none__" ? null : selectedParentPageId,
parent_page_id:
selectedParentPageId === "__none__" ? null : selectedParentPageId,
},
},
});
}}
disabled={!selectedAccountId}
>
<CheckIcon />
Approve with Changes
@ -351,7 +350,8 @@ function ApprovalCard({
args: {
...args,
connector_id: selectedAccountId ? Number(selectedAccountId) : null,
parent_page_id: selectedParentPageId === "__none__" ? null : selectedParentPageId,
parent_page_id:
selectedParentPageId === "__none__" ? null : selectedParentPageId,
},
},
});
@ -477,6 +477,15 @@ export const CreateNotionPageToolUI = makeAssistantToolUI<
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isErrorResult(result)) {
return <ErrorCard result={result} />;
}