feat: integrate tool call icons with status indicators and daisy chaining (#4279)

Co-authored-by: dianed-square <73617011+dianed-square@users.noreply.github.com>
Co-authored-by: Zane <75694352+zanesq@users.noreply.github.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
Spence
2025-08-29 08:39:38 -04:00
committed by GitHub
parent 7599a3e56b
commit 69c29a0870
24 changed files with 920 additions and 73 deletions
+60
View File
@@ -0,0 +1,60 @@
import { Message, getToolRequests, getTextContent, getToolResponses } from '../types/message';
export function identifyConsecutiveToolCalls(messages: Message[]): number[][] {
const chains: number[][] = [];
let currentChain: number[] = [];
for (let i = 0; i < messages.length; i++) {
const message = messages[i];
const toolRequests = getToolRequests(message);
const toolResponses = getToolResponses(message);
const textContent = getTextContent(message);
const hasText = textContent.trim().length > 0;
if (toolResponses.length > 0 && toolRequests.length === 0) {
continue;
}
if (toolRequests.length > 0) {
if (hasText) {
if (currentChain.length > 0) {
if (currentChain.length > 1) {
chains.push([...currentChain]);
}
}
currentChain = [i];
} else {
currentChain.push(i);
}
} else if (hasText) {
if (currentChain.length > 1) {
chains.push([...currentChain]);
}
currentChain = [];
} else {
if (currentChain.length > 1) {
chains.push([...currentChain]);
}
currentChain = [];
}
}
if (currentChain.length > 1) {
chains.push(currentChain);
}
return chains;
}
export function shouldHideMessage(messageIndex: number, chains: number[][]): boolean {
for (const chain of chains) {
if (chain.includes(messageIndex)) {
return chain[0] !== messageIndex;
}
}
return false;
}
export function getChainForMessage(messageIndex: number, chains: number[][]): number[] | null {
return chains.find(chain => chain.includes(messageIndex)) || null;
}
+142
View File
@@ -0,0 +1,142 @@
import React from 'react';
import {
Archive,
Brain,
Camera,
Code2,
Eye,
FileEdit,
FilePlus,
FileText,
Globe,
Monitor,
Numbers,
Save,
Search,
Settings,
Terminal,
Tool,
} from '../components/icons/toolcalls';
export type ToolIconProps = {
className?: string;
};
/**
* Maps tool names to their corresponding icon components
* @param toolName - The name of the tool (extracted from toolCall.name)
* @returns React component for the tool icon
*/
export const getToolIcon = (toolName: string): React.ComponentType<ToolIconProps> => {
switch (toolName) {
// Developer Extension Tools
case 'text_editor':
return FileEdit;
case 'shell':
return Terminal;
// Memory Extension Tools
case 'remember_memory':
return Save;
case 'retrieve_memories':
return Brain;
// Computer Controller Extension Tools
case 'automation_script':
return Settings;
case 'computer_control':
return Monitor;
case 'web_scrape':
return Globe;
case 'screen_capture':
return Camera;
case 'pdf_tool':
return FileText;
case 'docx_tool':
return FileText;
case 'xlsx_tool':
return Numbers;
case 'cache':
return Archive;
// File Operations
case 'search':
return Search;
case 'read':
return Eye;
case 'create_file':
return FilePlus;
case 'update_file':
return FileEdit;
// Google Workspace Tools (if still supported)
case 'sheets_tool':
return Numbers;
case 'docs_tool':
return FileText;
// Special Tools
case 'final_output':
return Tool; // Could be a checkmark icon if we had one
// Default fallback for unknown tools
default:
return Tool;
}
};
/**
* Maps extension names to their corresponding icon components
* @param extensionName - The name of the extension
* @returns React component for the extension icon
*/
export const getExtensionIcon = (extensionName: string): React.ComponentType<ToolIconProps> => {
switch (extensionName) {
case 'developer':
return Code2;
case 'memory':
return Brain;
case 'computercontroller':
return Monitor;
default:
return Tool;
}
};
/**
* Helper function to extract tool name from full tool call name
* @param toolCallName - Full tool call name (e.g., "developer__text_editor")
* @returns Extracted tool name (e.g., "text_editor")
*/
export const extractToolName = (toolCallName: string): string => {
return toolCallName.substring(toolCallName.lastIndexOf('__') + 2);
};
/**
* Helper function to extract extension name from full tool call name
* @param toolCallName - Full tool call name (e.g., "developer__text_editor")
* @returns Extracted extension name (e.g., "developer")
*/
export const extractExtensionName = (toolCallName: string): string => {
const lastIndex = toolCallName.lastIndexOf('__');
return lastIndex === -1 ? '' : toolCallName.substring(0, lastIndex);
};
/**
* Main function to get the appropriate icon for a tool call
* @param toolCallName - Full tool call name (e.g., "developer__text_editor")
* @param useExtensionIcon - Whether to use extension icon instead of tool icon
* @returns React component for the icon
*/
export const getToolCallIcon = (
toolCallName: string,
useExtensionIcon: boolean = false
): React.ComponentType<ToolIconProps> => {
if (useExtensionIcon) {
const extensionName = extractExtensionName(toolCallName);
return getExtensionIcon(extensionName);
}
const toolName = extractToolName(toolCallName);
return getToolIcon(toolName);
};