diff --git a/surfsense_backend/app/agents/new_chat/system_prompt.py b/surfsense_backend/app/agents/new_chat/system_prompt.py
index 77df3acfd..b53251a1d 100644
--- a/surfsense_backend/app/agents/new_chat/system_prompt.py
+++ b/surfsense_backend/app/agents/new_chat/system_prompt.py
@@ -220,7 +220,8 @@ _TOOL_INSTRUCTIONS["scrape_webpage"] = """
- url: The URL of the webpage to scrape (must be HTTP/HTTPS)
- max_length: Maximum content length to return (default: 50000 chars)
- Returns: The page title, description, full content (in markdown), word count, and metadata
- - After scraping, you will have the full article text and can analyze, summarize, or answer questions about it.
+ - After scraping, provide a comprehensive, well-structured summary with key takeaways using headings or bullet points.
+ - Reference the source using markdown links [descriptive text](url) — never bare URLs.
- IMAGES: The scraped content may contain image URLs in markdown format like ``.
* When you find relevant/important images in the scraped content, include them in your response using standard markdown image syntax: ``.
* This makes your response more visual and engaging.
@@ -244,6 +245,8 @@ _TOOL_INSTRUCTIONS["web_search"] = """
- Args:
- query: The search query - use specific, descriptive terms
- top_k: Number of results to retrieve (default: 10, max: 50)
+ - If search snippets are insufficient for the user's question, use `scrape_webpage` on the most relevant result URL for full content.
+ - When presenting results, reference sources as markdown links [descriptive text](url) — never bare URLs.
"""
# Memory tool instructions have private and shared variants.
@@ -429,13 +432,16 @@ _TOOL_EXAMPLES["generate_report"] = """
_TOOL_EXAMPLES["scrape_webpage"] = """
- User: "Check out https://dev.to/some-article"
- Call: `scrape_webpage(url="https://dev.to/some-article")`
- - Then provide your analysis of the content.
+ - Respond with a structured analysis — key points, takeaways.
- User: "Read this article and summarize it for me: https://example.com/blog/ai-trends"
- Call: `scrape_webpage(url="https://example.com/blog/ai-trends")`
- - Then provide a summary based on the scraped text.
+ - Respond with a thorough summary using headings and bullet points.
- User: (after discussing https://example.com/stats) "Can you get the live data from that page?"
- Call: `scrape_webpage(url="https://example.com/stats")`
- IMPORTANT: Always attempt scraping first. Never refuse before trying the tool.
+- User: "https://example.com/blog/weekend-recipes"
+ - Call: `scrape_webpage(url="https://example.com/blog/weekend-recipes")`
+ - When a user sends just a URL with no instructions, scrape it and provide a concise summary of the content.
"""
_TOOL_EXAMPLES["generate_image"] = """
diff --git a/surfsense_backend/app/services/public_chat_service.py b/surfsense_backend/app/services/public_chat_service.py
index 763ae64c3..376db974f 100644
--- a/surfsense_backend/app/services/public_chat_service.py
+++ b/surfsense_backend/app/services/public_chat_service.py
@@ -42,7 +42,6 @@ UI_TOOLS = {
"generate_podcast",
"generate_report",
"generate_video_presentation",
- "scrape_webpage",
}
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 b3cc4fa6c..3f6893169 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
@@ -134,7 +134,6 @@ const TOOLS_WITH_UI = new Set([
"display_image",
"generate_image",
"delete_notion_page",
- "scrape_webpage",
"create_notion_page",
"update_notion_page",
"create_linear_issue",
diff --git a/surfsense_web/components/assistant-ui/assistant-message.tsx b/surfsense_web/components/assistant-ui/assistant-message.tsx
index fa3aec45a..14fb18bf9 100644
--- a/surfsense_web/components/assistant-ui/assistant-message.tsx
+++ b/surfsense_web/components/assistant-ui/assistant-message.tsx
@@ -29,7 +29,6 @@ import { CreateJiraIssueToolUI, DeleteJiraIssueToolUI, UpdateJiraIssueToolUI } f
import { CreateLinearIssueToolUI, DeleteLinearIssueToolUI, UpdateLinearIssueToolUI } from "@/components/tool-ui/linear";
import { CreateNotionPageToolUI, DeleteNotionPageToolUI, UpdateNotionPageToolUI } from "@/components/tool-ui/notion";
import { SandboxExecuteToolUI } from "@/components/tool-ui/sandbox-execute";
-import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage";
import { RecallMemoryToolUI, SaveMemoryToolUI } from "@/components/tool-ui/user-memory";
import { useComments } from "@/hooks/use-comments";
import { useMediaQuery } from "@/hooks/use-media-query";
@@ -59,7 +58,6 @@ const AssistantMessageInner: FC = () => {
generate_video_presentation: GenerateVideoPresentationToolUI,
display_image: DisplayImageToolUI,
generate_image: GenerateImageToolUI,
- scrape_webpage: ScrapeWebpageToolUI,
save_memory: SaveMemoryToolUI,
recall_memory: RecallMemoryToolUI,
execute: SandboxExecuteToolUI,
diff --git a/surfsense_web/components/assistant-ui/tool-fallback.tsx b/surfsense_web/components/assistant-ui/tool-fallback.tsx
index 636b43c36..d12ffb5d6 100644
--- a/surfsense_web/components/assistant-ui/tool-fallback.tsx
+++ b/surfsense_web/components/assistant-ui/tool-fallback.tsx
@@ -1,8 +1,14 @@
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon, XCircleIcon } from "lucide-react";
import { useState } from "react";
-import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
+import { getToolIcon } from "@/contracts/enums/toolIcons";
+
+function formatToolName(name: string): string {
+ return name
+ .replace(/_/g, " ")
+ .replace(/\b\w/g, (c) => c.toUpperCase());
+}
export const ToolFallback: ToolCallMessagePartComponent = ({
toolName,
@@ -10,66 +16,127 @@ export const ToolFallback: ToolCallMessagePartComponent = ({
result,
status,
}) => {
- const [isCollapsed, setIsCollapsed] = useState(true);
+ const [isExpanded, setIsExpanded] = useState(false);
const isCancelled = status?.type === "incomplete" && status.reason === "cancelled";
+ const isError = status?.type === "incomplete" && status.reason === "error";
+ const isRunning = status?.type === "running" || status?.type === "requires-action";
const cancelledReason =
isCancelled && status.error
? typeof status.error === "string"
? status.error
: JSON.stringify(status.error)
: null;
+ const errorReason =
+ isError && status.error
+ ? typeof status.error === "string"
+ ? status.error
+ : JSON.stringify(status.error)
+ : null;
+
+ const Icon = getToolIcon(toolName);
+ const displayName = formatToolName(toolName);
return (