mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-29 19:35:20 +02:00
refactor: remove display_image tool and associated UI components to streamline chat functionality
This commit is contained in:
parent
337bab3650
commit
c926c3f62e
6 changed files with 2 additions and 285 deletions
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -7,13 +7,6 @@
|
|||
*/
|
||||
|
||||
export { Audio } from "./audio";
|
||||
export {
|
||||
type DisplayImageArgs,
|
||||
DisplayImageArgsSchema,
|
||||
type DisplayImageResult,
|
||||
DisplayImageResultSchema,
|
||||
DisplayImageToolUI,
|
||||
} from "./display-image";
|
||||
export {
|
||||
type GenerateImageArgs,
|
||||
GenerateImageArgsSchema,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue