refactor: remove display_image tool and associated UI components to streamline chat functionality

This commit is contained in:
Anish Sarkar 2026-03-24 19:00:55 +05:30
parent 337bab3650
commit c926c3f62e
6 changed files with 2 additions and 285 deletions

View file

@ -1,111 +0,0 @@
"""
Display image tool for the SurfSense agent.
This module provides a tool for displaying images in the chat UI
with metadata like title, description, and source attribution.
"""
import hashlib
from typing import Any
from urllib.parse import urlparse
from langchain_core.tools import tool
def extract_domain(url: str) -> str:
"""Extract the domain from a URL."""
try:
parsed = urlparse(url)
domain = parsed.netloc
# Remove 'www.' prefix if present
if domain.startswith("www."):
domain = domain[4:]
return domain
except Exception:
return ""
def generate_image_id(src: str) -> str:
"""Generate a unique ID for an image."""
hash_val = hashlib.md5(src.encode()).hexdigest()[:12]
return f"image-{hash_val}"
def create_display_image_tool():
"""
Factory function to create the display_image tool.
Returns:
A configured tool function for displaying images.
"""
@tool
async def display_image(
src: str,
alt: str = "Image",
title: str | None = None,
description: str | None = None,
) -> dict[str, Any]:
"""
Display an image in the chat with metadata.
Use this tool when you want to show an image to the user.
This displays the image with an optional title, description,
and source attribution.
Common use cases:
- Showing an image from a URL the user mentioned
- Displaying a diagram or chart you're referencing
- Showing example images when explaining concepts
Args:
src: The URL of the image to display (must be a valid HTTP/HTTPS URL)
alt: Alternative text describing the image (for accessibility)
title: Optional title to display below the image
description: Optional description providing context about the image
Returns:
A dictionary containing image metadata for the UI to render:
- id: Unique identifier for this image
- assetId: The image URL (for deduplication)
- src: The image URL
- alt: Alt text for accessibility
- title: Image title (if provided)
- description: Image description (if provided)
- domain: Source domain
"""
image_id = generate_image_id(src)
# Ensure URL has protocol
if not src.startswith(("http://", "https://")):
src = f"https://{src}"
domain = extract_domain(src)
# Determine aspect ratio based on image source
# AI-generated images should use "auto" to preserve their native ratio
is_generated = "/image-generations/" in src
if is_generated:
ratio = "auto"
domain = "ai-generated"
elif "unsplash.com" in src or "pexels.com" in src:
ratio = "16:9"
elif (
"imgur.com" in src or "github.com" in src or "githubusercontent.com" in src
):
ratio = "auto"
else:
ratio = "auto"
return {
"id": image_id,
"assetId": src,
"src": src,
"alt": alt,
"title": title,
"description": description,
"domain": domain,
"ratio": ratio,
}
return display_image

View file

@ -131,7 +131,6 @@ const TOOLS_WITH_UI = new Set([
"generate_podcast",
"generate_report",
"generate_video_presentation",
"display_image",
"generate_image",
"delete_notion_page",
"create_notion_page",

View file

@ -17,7 +17,6 @@ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button
import { CommentPanelContainer } from "@/components/chat-comments/comment-panel-container/comment-panel-container";
import { CommentSheet } from "@/components/chat-comments/comment-sheet/comment-sheet";
import { CreateConfluencePageToolUI, DeleteConfluencePageToolUI, UpdateConfluencePageToolUI } from "@/components/tool-ui/confluence";
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
import { GenerateImageToolUI } from "@/components/tool-ui/generate-image";
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
import { GenerateReportToolUI } from "@/components/tool-ui/generate-report";
@ -56,7 +55,7 @@ const AssistantMessageInner: FC = () => {
generate_report: GenerateReportToolUI,
generate_podcast: GeneratePodcastToolUI,
generate_video_presentation: GenerateVideoPresentationToolUI,
display_image: DisplayImageToolUI,
display_image: () => null,
generate_image: GenerateImageToolUI,
save_memory: SaveMemoryToolUI,
recall_memory: RecallMemoryToolUI,

View file

@ -12,7 +12,6 @@ import { type FC, type ReactNode, useState } from "react";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
import { GenerateImageToolUI } from "@/components/tool-ui/generate-image";
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
import { GenerateReportToolUI } from "@/components/tool-ui/generate-report";
@ -149,7 +148,7 @@ const PublicAssistantMessage: FC = () => {
generate_podcast: GeneratePodcastToolUI,
generate_report: GenerateReportToolUI,
generate_video_presentation: GenerateVideoPresentationToolUI,
display_image: DisplayImageToolUI,
display_image: () => null,
generate_image: GenerateImageToolUI,
link_preview: () => null,
multi_link_preview: () => null,

View file

@ -1,162 +0,0 @@
"use client";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { AlertCircleIcon, ImageIcon } from "lucide-react";
import { z } from "zod";
import {
Image,
ImageErrorBoundary,
ImageLoading,
parseSerializableImage,
} from "@/components/tool-ui/image";
// ============================================================================
// Zod Schemas
// ============================================================================
/**
* Schema for display_image tool arguments
*/
const DisplayImageArgsSchema = z.object({
src: z.string(),
alt: z.string().nullish(),
title: z.string().nullish(),
description: z.string().nullish(),
});
/**
* Schema for display_image tool result
*/
const DisplayImageResultSchema = z.object({
id: z.string(),
assetId: z.string(),
src: z.string(),
alt: z.string().nullish(),
title: z.string().nullish(),
description: z.string().nullish(),
domain: z.string().nullish(),
ratio: z.string().nullish(),
error: z.string().nullish(),
});
// ============================================================================
// Types
// ============================================================================
type DisplayImageArgs = z.infer<typeof DisplayImageArgsSchema>;
type DisplayImageResult = z.infer<typeof DisplayImageResultSchema>;
/**
* Error state component shown when image display fails
*/
function ImageErrorState({ src, error }: { src: string; error: string }) {
return (
<div className="my-4 overflow-hidden rounded-xl border border-destructive/20 bg-destructive/5 p-4 max-w-md">
<div className="flex items-center gap-4">
<div className="flex size-12 shrink-0 items-center justify-center rounded-lg bg-destructive/10">
<AlertCircleIcon className="size-6 text-destructive" />
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-destructive text-sm">Failed to display image</p>
<p className="text-muted-foreground text-xs mt-0.5 truncate">{src}</p>
<p className="text-muted-foreground text-xs mt-1">{error}</p>
</div>
</div>
</div>
);
}
/**
* Cancelled state component
*/
function ImageCancelledState({ src }: { src: string }) {
return (
<div className="my-4 rounded-xl border border-muted p-4 text-muted-foreground max-w-md">
<p className="flex items-center gap-2">
<ImageIcon className="size-4" />
<span className="line-through truncate">Image: {src}</span>
</p>
</div>
);
}
/**
* Parsed Image component with error handling
* Note: Image component has built-in click handling via href/src,
* so no additional responseActions needed.
*/
function ParsedImage({ result }: { result: unknown }) {
const image = parseSerializableImage(result);
return <Image {...image} maxWidth="512px" />;
}
/**
* Display Image Tool UI Component
*
* This component is registered with assistant-ui to render an image
* when the display_image tool is called by the agent.
*
* It displays images with:
* - Title and description
* - Source attribution
* - Hover overlay effects
* - Click to open full size
*/
export const DisplayImageToolUI = ({ args, result, status }: ToolCallMessagePartProps<DisplayImageArgs, DisplayImageResult>) => {
const src = args.src || "Unknown";
// Loading state - tool is still running
if (status.type === "running" || status.type === "requires-action") {
return (
<div className="my-4">
<ImageLoading title={`Loading image...`} />
</div>
);
}
// Incomplete/cancelled state
if (status.type === "incomplete") {
if (status.reason === "cancelled") {
return <ImageCancelledState src={src} />;
}
if (status.reason === "error") {
return (
<ImageErrorState
src={src}
error={typeof status.error === "string" ? status.error : "An error occurred"}
/>
);
}
}
// No result yet
if (!result) {
return (
<div className="my-4">
<ImageLoading title="Preparing image..." />
</div>
);
}
// Error result from the tool
if (result.error) {
return <ImageErrorState src={src} error={result.error} />;
}
// Success - render the image
return (
<div className="my-4">
<ImageErrorBoundary>
<ParsedImage result={result} />
</ImageErrorBoundary>
</div>
);
};
export {
DisplayImageArgsSchema,
DisplayImageResultSchema,
type DisplayImageArgs,
type DisplayImageResult,
};

View file

@ -7,13 +7,6 @@
*/
export { Audio } from "./audio";
export {
type DisplayImageArgs,
DisplayImageArgsSchema,
type DisplayImageResult,
DisplayImageResultSchema,
DisplayImageToolUI,
} from "./display-image";
export {
type GenerateImageArgs,
GenerateImageArgsSchema,