mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-06 22:12:12 +02:00
feat: enhance tool display names for better user experience in chat UI
This commit is contained in:
parent
c110f5b955
commit
9a114a2d45
7 changed files with 267 additions and 32 deletions
|
|
@ -622,6 +622,95 @@ async def _stream_agent_events(
|
||||||
status="in_progress",
|
status="in_progress",
|
||||||
items=last_active_step_items,
|
items=last_active_step_items,
|
||||||
)
|
)
|
||||||
|
elif tool_name == "rm":
|
||||||
|
rm_path = (
|
||||||
|
tool_input.get("path", "")
|
||||||
|
if isinstance(tool_input, dict)
|
||||||
|
else str(tool_input)
|
||||||
|
)
|
||||||
|
display_path = rm_path if len(rm_path) <= 80 else "…" + rm_path[-77:]
|
||||||
|
last_active_step_title = "Deleting file"
|
||||||
|
last_active_step_items = [display_path] if display_path else []
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=tool_step_id,
|
||||||
|
title="Deleting file",
|
||||||
|
status="in_progress",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
|
elif tool_name == "rmdir":
|
||||||
|
rmdir_path = (
|
||||||
|
tool_input.get("path", "")
|
||||||
|
if isinstance(tool_input, dict)
|
||||||
|
else str(tool_input)
|
||||||
|
)
|
||||||
|
display_path = (
|
||||||
|
rmdir_path if len(rmdir_path) <= 80 else "…" + rmdir_path[-77:]
|
||||||
|
)
|
||||||
|
last_active_step_title = "Deleting folder"
|
||||||
|
last_active_step_items = [display_path] if display_path else []
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=tool_step_id,
|
||||||
|
title="Deleting folder",
|
||||||
|
status="in_progress",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
|
elif tool_name == "mkdir":
|
||||||
|
mkdir_path = (
|
||||||
|
tool_input.get("path", "")
|
||||||
|
if isinstance(tool_input, dict)
|
||||||
|
else str(tool_input)
|
||||||
|
)
|
||||||
|
display_path = (
|
||||||
|
mkdir_path if len(mkdir_path) <= 80 else "…" + mkdir_path[-77:]
|
||||||
|
)
|
||||||
|
last_active_step_title = "Creating folder"
|
||||||
|
last_active_step_items = [display_path] if display_path else []
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=tool_step_id,
|
||||||
|
title="Creating folder",
|
||||||
|
status="in_progress",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
|
elif tool_name == "move_file":
|
||||||
|
src = (
|
||||||
|
tool_input.get("source_path", "")
|
||||||
|
if isinstance(tool_input, dict)
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
dst = (
|
||||||
|
tool_input.get("destination_path", "")
|
||||||
|
if isinstance(tool_input, dict)
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
display_src = src if len(src) <= 60 else "…" + src[-57:]
|
||||||
|
display_dst = dst if len(dst) <= 60 else "…" + dst[-57:]
|
||||||
|
last_active_step_title = "Moving file"
|
||||||
|
last_active_step_items = (
|
||||||
|
[f"{display_src} → {display_dst}"] if src or dst else []
|
||||||
|
)
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=tool_step_id,
|
||||||
|
title="Moving file",
|
||||||
|
status="in_progress",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
|
elif tool_name == "write_todos":
|
||||||
|
todos = (
|
||||||
|
tool_input.get("todos", []) if isinstance(tool_input, dict) else []
|
||||||
|
)
|
||||||
|
todo_count = len(todos) if isinstance(todos, list) else 0
|
||||||
|
last_active_step_title = "Planning tasks"
|
||||||
|
last_active_step_items = (
|
||||||
|
[f"{todo_count} task{'s' if todo_count != 1 else ''}"]
|
||||||
|
if todo_count
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=tool_step_id,
|
||||||
|
title="Planning tasks",
|
||||||
|
status="in_progress",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
elif tool_name == "save_document":
|
elif tool_name == "save_document":
|
||||||
doc_title = (
|
doc_title = (
|
||||||
tool_input.get("title", "")
|
tool_input.get("title", "")
|
||||||
|
|
@ -729,7 +818,15 @@ async def _stream_agent_events(
|
||||||
items=last_active_step_items,
|
items=last_active_step_items,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
last_active_step_title = f"Using {tool_name.replace('_', ' ')}"
|
# Fallback for tools without a curated thinking-step title
|
||||||
|
# (typically connector tools, MCP-registered tools, or
|
||||||
|
# newly added tools that haven't been wired up here yet).
|
||||||
|
# Render the snake_cased name as a sentence-cased phrase
|
||||||
|
# so non-technical users see e.g. "Send gmail email"
|
||||||
|
# rather than the raw identifier "send_gmail_email".
|
||||||
|
last_active_step_title = (
|
||||||
|
tool_name.replace("_", " ").strip().capitalize() or tool_name
|
||||||
|
)
|
||||||
last_active_step_items = []
|
last_active_step_items = []
|
||||||
yield streaming_service.format_thinking_step(
|
yield streaming_service.format_thinking_step(
|
||||||
step_id=tool_step_id,
|
step_id=tool_step_id,
|
||||||
|
|
@ -885,6 +982,41 @@ async def _stream_agent_events(
|
||||||
status="completed",
|
status="completed",
|
||||||
items=last_active_step_items,
|
items=last_active_step_items,
|
||||||
)
|
)
|
||||||
|
elif tool_name == "rm":
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=original_step_id,
|
||||||
|
title="Deleting file",
|
||||||
|
status="completed",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
|
elif tool_name == "rmdir":
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=original_step_id,
|
||||||
|
title="Deleting folder",
|
||||||
|
status="completed",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
|
elif tool_name == "mkdir":
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=original_step_id,
|
||||||
|
title="Creating folder",
|
||||||
|
status="completed",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
|
elif tool_name == "move_file":
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=original_step_id,
|
||||||
|
title="Moving file",
|
||||||
|
status="completed",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
|
elif tool_name == "write_todos":
|
||||||
|
yield streaming_service.format_thinking_step(
|
||||||
|
step_id=original_step_id,
|
||||||
|
title="Planning tasks",
|
||||||
|
status="completed",
|
||||||
|
items=last_active_step_items,
|
||||||
|
)
|
||||||
elif tool_name == "save_document":
|
elif tool_name == "save_document":
|
||||||
result_str = (
|
result_str = (
|
||||||
tool_output.get("result", "")
|
tool_output.get("result", "")
|
||||||
|
|
@ -1136,9 +1268,14 @@ async def _stream_agent_events(
|
||||||
items=completed_items,
|
items=completed_items,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# Fallback completion title — see the matching in-progress
|
||||||
|
# branch above for the wording rationale.
|
||||||
|
fallback_title = (
|
||||||
|
tool_name.replace("_", " ").strip().capitalize() or tool_name
|
||||||
|
)
|
||||||
yield streaming_service.format_thinking_step(
|
yield streaming_service.format_thinking_step(
|
||||||
step_id=original_step_id,
|
step_id=original_step_id,
|
||||||
title=f"Using {tool_name.replace('_', ' ')}",
|
title=fallback_title,
|
||||||
status="completed",
|
status="completed",
|
||||||
items=last_active_step_items,
|
items=last_active_step_items,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,12 @@ import {
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { getToolIcon } from "@/contracts/enums/toolIcons";
|
import { getToolDisplayName, getToolIcon } from "@/contracts/enums/toolIcons";
|
||||||
import { type AgentAction, agentActionsApiService } from "@/lib/apis/agent-actions-api.service";
|
import { type AgentAction, agentActionsApiService } from "@/lib/apis/agent-actions-api.service";
|
||||||
import { AppError } from "@/lib/error";
|
import { AppError } from "@/lib/error";
|
||||||
import { formatRelativeDate } from "@/lib/format-date";
|
import { formatRelativeDate } from "@/lib/format-date";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function formatToolName(name: string): string {
|
|
||||||
return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ActionLogItemProps {
|
interface ActionLogItemProps {
|
||||||
action: AgentAction;
|
action: AgentAction;
|
||||||
threadId: number;
|
threadId: number;
|
||||||
|
|
@ -43,7 +39,7 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt
|
||||||
const hasError = action.error !== null && action.error !== undefined;
|
const hasError = action.error !== null && action.error !== undefined;
|
||||||
|
|
||||||
const Icon = getToolIcon(action.tool_name);
|
const Icon = getToolIcon(action.tool_name);
|
||||||
const displayName = formatToolName(action.tool_name);
|
const displayName = getToolDisplayName(action.tool_name);
|
||||||
|
|
||||||
const argsPreview = action.args ? JSON.stringify(action.args, null, 2) : null;
|
const argsPreview = action.args ? JSON.stringify(action.args, null, 2) : null;
|
||||||
const truncatedArgs =
|
const truncatedArgs =
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import {
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { getToolDisplayName } from "@/contracts/enums/toolIcons";
|
||||||
import {
|
import {
|
||||||
agentActionsApiService,
|
agentActionsApiService,
|
||||||
type RevertTurnActionResult,
|
type RevertTurnActionResult,
|
||||||
|
|
@ -48,10 +49,6 @@ interface RevertTurnButtonProps {
|
||||||
chatTurnId: string | null | undefined;
|
chatTurnId: string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatToolName(name: string): string {
|
|
||||||
return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty-array sentinel so the per-turn ``selectAtom`` slice returns a
|
// Empty-array sentinel so the per-turn ``selectAtom`` slice returns a
|
||||||
// stable reference when the turn has no recorded actions yet. Without
|
// stable reference when the turn has no recorded actions yet. Without
|
||||||
// this every render allocates a fresh ``[]`` and Jotai's
|
// this every render allocates a fresh ``[]`` and Jotai's
|
||||||
|
|
@ -218,7 +215,7 @@ function RevertResultRow({ result }: { result: RevertTurnActionResult }) {
|
||||||
/>
|
/>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="font-medium truncate">
|
<p className="font-medium truncate">
|
||||||
{formatToolName(result.tool_name)}{" "}
|
{getToolDisplayName(result.tool_name)}{" "}
|
||||||
<span className="ml-1 text-xs text-muted-foreground">
|
<span className="ml-1 text-xs text-muted-foreground">
|
||||||
{result.status.replace(/_/g, " ")}
|
{result.status.replace(/_/g, " ")}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import {
|
import {
|
||||||
CONNECTOR_ICON_TO_TYPES,
|
CONNECTOR_ICON_TO_TYPES,
|
||||||
CONNECTOR_TOOL_ICON_PATHS,
|
CONNECTOR_TOOL_ICON_PATHS,
|
||||||
|
getToolDisplayName,
|
||||||
getToolIcon,
|
getToolIcon,
|
||||||
} from "@/contracts/enums/toolIcons";
|
} from "@/contracts/enums/toolIcons";
|
||||||
import type { Document } from "@/contracts/types/document.types";
|
import type { Document } from "@/contracts/types/document.types";
|
||||||
|
|
@ -1317,12 +1318,14 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Convert snake_case tool names to human-readable labels */
|
/**
|
||||||
|
* Friendly tool name for display in the chat UI. Delegates to the
|
||||||
|
* shared map in ``contracts/enums/toolIcons`` so unix-style identifiers
|
||||||
|
* (``rm``, ``ls``, ``grep`` …) and snake_cased function names render as
|
||||||
|
* plain English (e.g. "Delete file", "List files", "Search in files").
|
||||||
|
*/
|
||||||
function formatToolName(name: string): string {
|
function formatToolName(name: string): string {
|
||||||
return name
|
return getToolDisplayName(name);
|
||||||
.split("_")
|
|
||||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
||||||
.join(" ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToolGroup {
|
interface ToolGroup {
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,12 @@ import {
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { getToolIcon } from "@/contracts/enums/toolIcons";
|
import { getToolDisplayName, getToolIcon } from "@/contracts/enums/toolIcons";
|
||||||
import { agentActionsApiService } from "@/lib/apis/agent-actions-api.service";
|
import { agentActionsApiService } from "@/lib/apis/agent-actions-api.service";
|
||||||
import { AppError } from "@/lib/error";
|
import { AppError } from "@/lib/error";
|
||||||
import { isInterruptResult } from "@/lib/hitl";
|
import { isInterruptResult } from "@/lib/hitl";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function formatToolName(name: string): string {
|
|
||||||
return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline Revert button rendered on a tool card when the matching
|
* Inline Revert button rendered on a tool card when the matching
|
||||||
* ``AgentActionLog`` row is reversible and hasn't been reverted yet.
|
* ``AgentActionLog`` row is reversible and hasn't been reverted yet.
|
||||||
|
|
@ -104,9 +100,10 @@ function ToolCardRevertButton({ toolCallId }: { toolCallId: string }) {
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Revert this action?</AlertDialogTitle>
|
<AlertDialogTitle>Revert this action?</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
This will undo <span className="font-medium">{formatToolName(action.toolName)}</span>{" "}
|
This will undo{" "}
|
||||||
and append a new audit entry. Chat history is preserved — only the tool's effects on
|
<span className="font-medium">{getToolDisplayName(action.toolName)}</span> and add a
|
||||||
your knowledge base or connectors will be reversed where possible.
|
new entry to the history. Your chat is preserved — only the changes the agent made to
|
||||||
|
your knowledge base or connected apps will be rolled back where possible.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
|
|
@ -164,7 +161,7 @@ const DefaultToolFallbackInner: ToolCallMessagePartComponent = ({
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const Icon = getToolIcon(toolName);
|
const Icon = getToolIcon(toolName);
|
||||||
const displayName = formatToolName(toolName);
|
const displayName = getToolDisplayName(toolName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -215,7 +212,7 @@ const DefaultToolFallbackInner: ToolCallMessagePartComponent = ({
|
||||||
? `Failed: ${displayName}`
|
? `Failed: ${displayName}`
|
||||||
: displayName}
|
: displayName}
|
||||||
</p>
|
</p>
|
||||||
{isRunning && <p className="text-xs text-muted-foreground mt-0.5">Running...</p>}
|
{isRunning && <p className="text-xs text-muted-foreground mt-0.5">Working…</p>}
|
||||||
{cancelledReason && (
|
{cancelledReason && (
|
||||||
<p className="text-xs text-muted-foreground mt-0.5 truncate">{cancelledReason}</p>
|
<p className="text-xs text-muted-foreground mt-0.5 truncate">{cancelledReason}</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -241,7 +238,7 @@ const DefaultToolFallbackInner: ToolCallMessagePartComponent = ({
|
||||||
<div className="px-5 py-3 space-y-3">
|
<div className="px-5 py-3 space-y-3">
|
||||||
{argsText && (
|
{argsText && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-muted-foreground mb-1">Arguments</p>
|
<p className="text-xs font-medium text-muted-foreground mb-1">Inputs</p>
|
||||||
<pre className="text-xs text-foreground/80 whitespace-pre-wrap break-all">
|
<pre className="text-xs text-foreground/80 whitespace-pre-wrap break-all">
|
||||||
{argsText}
|
{argsText}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { TextShimmerLoader } from "@/components/prompt-kit/loader";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { getToolDisplayName } from "@/contracts/enums/toolIcons";
|
||||||
import { useHitlPhase } from "@/hooks/use-hitl-phase";
|
import { useHitlPhase } from "@/hooks/use-hitl-phase";
|
||||||
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
|
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
|
||||||
import type { HitlDecision, InterruptResult } from "@/lib/hitl";
|
import type { HitlDecision, InterruptResult } from "@/lib/hitl";
|
||||||
|
|
@ -77,7 +78,7 @@ function GenericApprovalCard({
|
||||||
const [editedParams, setEditedParams] = useState<Record<string, unknown>>(args);
|
const [editedParams, setEditedParams] = useState<Record<string, unknown>>(args);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
const displayName = toolName.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
const displayName = getToolDisplayName(toolName);
|
||||||
|
|
||||||
const mcpServer = interruptData.context?.mcp_server as string | undefined;
|
const mcpServer = interruptData.context?.mcp_server as string | undefined;
|
||||||
const toolDescription = interruptData.context?.tool_description as string | undefined;
|
const toolDescription = interruptData.context?.tool_description as string | undefined;
|
||||||
|
|
@ -186,12 +187,11 @@ function GenericApprovalCard({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Parameters */}
|
|
||||||
{Object.keys(args).length > 0 && (
|
{Object.keys(args).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="mx-5 h-px bg-border/50" />
|
<div className="mx-5 h-px bg-border/50" />
|
||||||
<div className="px-5 py-4 space-y-2">
|
<div className="px-5 py-4 space-y-2">
|
||||||
<p className="text-xs font-medium text-muted-foreground">Parameters</p>
|
<p className="text-xs font-medium text-muted-foreground">Inputs</p>
|
||||||
{phase === "pending" && isEditing ? (
|
{phase === "pending" && isEditing ? (
|
||||||
<ParamEditor
|
<ParamEditor
|
||||||
params={editedParams}
|
params={editedParams}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,111 @@ export function getToolIcon(name: string): LucideIcon {
|
||||||
return TOOL_ICONS[name] ?? Wrench;
|
return TOOL_ICONS[name] ?? Wrench;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Friendly display names for tools shown in the chat UI.
|
||||||
|
*
|
||||||
|
* Most users aren't engineers; they shouldn't see raw unix-style
|
||||||
|
* identifiers like ``rm`` / ``rmdir`` / ``ls`` / ``grep`` / ``glob`` or
|
||||||
|
* snake_cased function names. The map below renders each tool with
|
||||||
|
* plain English wording (verb + object) so non-technical users
|
||||||
|
* understand what the agent is doing at a glance.
|
||||||
|
*
|
||||||
|
* Unmapped tool names fall back to a snake_case-to-Title-Case
|
||||||
|
* conversion via :func:`getToolDisplayName`.
|
||||||
|
*/
|
||||||
|
const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
||||||
|
// Filesystem / knowledge base
|
||||||
|
read_file: "Read file",
|
||||||
|
write_file: "Write file",
|
||||||
|
edit_file: "Edit file",
|
||||||
|
move_file: "Move file",
|
||||||
|
rm: "Delete file",
|
||||||
|
rmdir: "Delete folder",
|
||||||
|
mkdir: "Create folder",
|
||||||
|
ls: "List files",
|
||||||
|
glob: "Find files",
|
||||||
|
grep: "Search in files",
|
||||||
|
write_todos: "Plan tasks",
|
||||||
|
save_document: "Save document",
|
||||||
|
// Generators
|
||||||
|
generate_podcast: "Generate podcast",
|
||||||
|
generate_video_presentation: "Generate video presentation",
|
||||||
|
generate_report: "Generate report",
|
||||||
|
generate_resume: "Generate resume",
|
||||||
|
generate_image: "Generate image",
|
||||||
|
display_image: "Show image",
|
||||||
|
// Web / search
|
||||||
|
scrape_webpage: "Read webpage",
|
||||||
|
web_search: "Search the web",
|
||||||
|
search_surfsense_docs: "Search knowledge base",
|
||||||
|
// Memory
|
||||||
|
update_memory: "Update memory",
|
||||||
|
// Calendar
|
||||||
|
search_calendar_events: "Search calendar",
|
||||||
|
create_calendar_event: "Create event",
|
||||||
|
update_calendar_event: "Update event",
|
||||||
|
delete_calendar_event: "Delete event",
|
||||||
|
// Gmail
|
||||||
|
search_gmail: "Search Gmail",
|
||||||
|
read_gmail_email: "Read email",
|
||||||
|
create_gmail_draft: "Draft email",
|
||||||
|
update_gmail_draft: "Update draft",
|
||||||
|
send_gmail_email: "Send email",
|
||||||
|
trash_gmail_email: "Move email to trash",
|
||||||
|
// Notion
|
||||||
|
create_notion_page: "Create Notion page",
|
||||||
|
update_notion_page: "Update Notion page",
|
||||||
|
delete_notion_page: "Delete Notion page",
|
||||||
|
// Confluence
|
||||||
|
create_confluence_page: "Create Confluence page",
|
||||||
|
update_confluence_page: "Update Confluence page",
|
||||||
|
delete_confluence_page: "Delete Confluence page",
|
||||||
|
// Linear
|
||||||
|
create_linear_issue: "Create Linear issue",
|
||||||
|
update_linear_issue: "Update Linear issue",
|
||||||
|
delete_linear_issue: "Delete Linear issue",
|
||||||
|
// Jira
|
||||||
|
create_jira_issue: "Create Jira issue",
|
||||||
|
update_jira_issue: "Update Jira issue",
|
||||||
|
delete_jira_issue: "Delete Jira issue",
|
||||||
|
// Drive-like file connectors
|
||||||
|
create_google_drive_file: "Create Google Drive file",
|
||||||
|
delete_google_drive_file: "Delete Google Drive file",
|
||||||
|
create_dropbox_file: "Create Dropbox file",
|
||||||
|
delete_dropbox_file: "Delete Dropbox file",
|
||||||
|
create_onedrive_file: "Create OneDrive file",
|
||||||
|
delete_onedrive_file: "Delete OneDrive file",
|
||||||
|
// Discord
|
||||||
|
list_discord_channels: "List Discord channels",
|
||||||
|
read_discord_messages: "Read Discord messages",
|
||||||
|
send_discord_message: "Send Discord message",
|
||||||
|
// Teams
|
||||||
|
list_teams_channels: "List Teams channels",
|
||||||
|
read_teams_messages: "Read Teams messages",
|
||||||
|
send_teams_message: "Send Teams message",
|
||||||
|
// Luma
|
||||||
|
list_luma_events: "List Luma events",
|
||||||
|
read_luma_event: "Read Luma event",
|
||||||
|
create_luma_event: "Create Luma event",
|
||||||
|
// Misc
|
||||||
|
get_connected_accounts: "Check connected accounts",
|
||||||
|
execute: "Run command",
|
||||||
|
execute_code: "Run code",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a tool's canonical (snake_case) name for display in the chat UI.
|
||||||
|
*
|
||||||
|
* Looks up :data:`TOOL_DISPLAY_NAMES` first; falls back to a
|
||||||
|
* snake_case-to-Title-Case rewrite for tools that don't have a curated
|
||||||
|
* label (e.g. dynamically registered MCP tools).
|
||||||
|
*/
|
||||||
|
export function getToolDisplayName(name: string): string {
|
||||||
|
const friendly = TOOL_DISPLAY_NAMES[name];
|
||||||
|
if (friendly) return friendly;
|
||||||
|
return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
export const CONNECTOR_TOOL_ICON_PATHS: Record<string, { src: string; alt: string }> = {
|
export const CONNECTOR_TOOL_ICON_PATHS: Record<string, { src: string; alt: string }> = {
|
||||||
gmail: { src: "/connectors/google-gmail.svg", alt: "Gmail" },
|
gmail: { src: "/connectors/google-gmail.svg", alt: "Gmail" },
|
||||||
google_calendar: { src: "/connectors/google-calendar.svg", alt: "Google Calendar" },
|
google_calendar: { src: "/connectors/google-calendar.svg", alt: "Google Calendar" },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue