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
+119 -47
View File
@@ -7,6 +7,12 @@ import { extractImagePaths, removeImagePathsFromText } from '../utils/imageUtils
import { formatMessageTimestamp } from '../utils/timeUtils';
import MarkdownContent from './MarkdownContent';
import ToolCallWithResponse from './ToolCallWithResponse';
import ToolCallChain from './ToolCallChain';
import {
identifyConsecutiveToolCalls,
shouldHideMessage,
getChainForMessage,
} from '../utils/toolCallChaining';
import {
Message,
getTextContent,
@@ -18,6 +24,7 @@ import {
import ToolCallConfirmation from './ToolCallConfirmation';
import MessageCopyLink from './MessageCopyLink';
import { NotificationEvent } from '../hooks/useMessageStream';
import { cn } from '../utils';
interface GooseMessageProps {
// messages up to this index are presumed to be "history" from a resumed session, this is used to track older tool confirmation requests
@@ -60,18 +67,23 @@ export default function GooseMessage({
}
const cotRaw = match[1].trim();
const visible = text.replace(match[0], '').trim();
return { visibleText: visible, cotText: cotRaw.length > 0 ? cotRaw : null };
const visibleText = text.replace(regex, '').trim();
return {
visibleText,
cotText: cotRaw || null,
};
};
const { visibleText: textWithoutCot, cotText } = splitChainOfThought(textContent);
// Split out Chain-of-Thought
const { visibleText, cotText } = splitChainOfThought(textContent);
// Extract image paths from the visible part of the message (exclude CoT)
const imagePaths = extractImagePaths(textWithoutCot);
// Extract image paths from the message content
const imagePaths = extractImagePaths(visibleText);
// Remove image paths from text for display
const displayText =
imagePaths.length > 0 ? removeImagePathsFromText(textWithoutCot, imagePaths) : textWithoutCot;
imagePaths.length > 0 ? removeImagePathsFromText(visibleText, imagePaths) : visibleText;
// Memoize the timestamp
const timestamp = useMemo(() => formatMessageTimestamp(message.created), [message.created]);
@@ -79,11 +91,53 @@ export default function GooseMessage({
// Get tool requests from the message
const toolRequests = getToolRequests(message);
// Get current message index
const messageIndex = messages.findIndex((msg) => msg.id === message.id);
// Enhanced chain detection that works during streaming
const toolCallChains = useMemo(() => {
// Always run chain detection, but handle streaming messages specially
const chains = identifyConsecutiveToolCalls(messages);
// If this message is streaming and has tool calls but no text,
// check if it should extend an existing chain
if (isStreaming && toolRequests.length > 0 && !displayText.trim()) {
// Look for an existing chain that this message could extend
const previousMessage = messageIndex > 0 ? messages[messageIndex - 1] : null;
if (previousMessage) {
const prevToolRequests = getToolRequests(previousMessage);
// If previous message has tool calls (with or without text), extend its chain
if (prevToolRequests.length > 0) {
// Find if previous message is part of a chain
const prevChain = chains.find((chain) => chain.includes(messageIndex - 1));
if (prevChain) {
// Extend the existing chain to include this streaming message
const extendedChains = chains.map((chain) =>
chain === prevChain ? [...chain, messageIndex] : chain
);
return extendedChains;
} else {
// Create a new chain with previous and current message
return [...chains, [messageIndex - 1, messageIndex]];
}
}
}
}
return chains;
}, [messages, isStreaming, messageIndex, toolRequests, displayText]);
// Check if this message should be hidden (part of chain but not first)
const shouldHide = shouldHideMessage(messageIndex, toolCallChains);
// Get the chain this message belongs to
const messageChain = getChainForMessage(messageIndex, toolCallChains);
// Extract URLs under a few conditions
// 1. The message is purely text
// 2. The link wasn't also present in the previous message
// 3. The message contains the explicit http:// or https:// protocol at the beginning
const messageIndex = messages?.findIndex((msg) => msg.id === message.id);
const previousMessage = messageIndex > 0 ? messages[messageIndex - 1] : null;
const previousUrls = previousMessage ? extractUrls(getTextContent(previousMessage)) : [];
const urls = toolRequests.length === 0 ? extractUrls(displayText, previousUrls) : [];
@@ -145,10 +199,17 @@ export default function GooseMessage({
appendMessage,
]);
// If this message should be hidden (part of chain but not first), don't render it
if (shouldHide) {
return null;
}
// Determine rendering logic based on chain membership and content
const isFirstInChain = messageChain && messageChain[0] === messageIndex;
return (
<div className="goose-message flex w-[90%] justify-start min-w-0">
<div className="flex flex-col w-full min-w-0">
{/* Chain-of-Thought (hidden by default) */}
{cotText && (
<details className="bg-bgSubtle border border-borderSubtle rounded p-2 mb-2">
<summary className="cursor-pointer text-sm text-textSubtle select-none">
@@ -160,61 +221,73 @@ export default function GooseMessage({
</details>
)}
{/* Visible assistant response */}
{displayText && (
<div className="flex flex-col group">
<div className={`goose-message-content py-2`}>
<div ref={contentRef}>{<MarkdownContent content={displayText} />}</div>
<div ref={contentRef} className="w-full">
<MarkdownContent content={displayText} />
</div>
{/* Render images if any */}
{/* Image previews */}
{imagePaths.length > 0 && (
<div className="flex flex-wrap gap-2 mt-2 mb-2">
<div className="mt-4">
{imagePaths.map((imagePath, index) => (
<ImagePreview key={index} src={imagePath} alt={`Image ${index + 1}`} />
<ImagePreview key={index} src={imagePath} />
))}
</div>
)}
{/* Only show timestamp and copy link when not streaming */}
<div className="relative flex justify-start">
{toolRequests.length === 0 && !isStreaming && (
<div className="text-xs font-mono text-text-muted pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
{timestamp}
</div>
)}
{displayText &&
message.content.every((content) => content.type === 'text') &&
!isStreaming && (
{toolRequests.length === 0 && (
<div className="relative flex justify-start">
{!isStreaming && (
<div className="text-xs font-mono text-text-muted pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
{timestamp}
</div>
)}
{message.content.every((content) => content.type === 'text') && !isStreaming && (
<div className="absolute left-0 pt-1">
<MessageCopyLink text={displayText} contentRef={contentRef} />
</div>
)}
</div>
</div>
)}
</div>
)}
{toolRequests.length > 0 && (
<div className="relative flex flex-col w-full">
{toolRequests.map((toolRequest) => (
<div className={`goose-message-tool pb-2`} key={toolRequest.id}>
<ToolCallWithResponse
// If the message is resumed and not matched tool response, it means the tool is broken or cancelled.
isCancelledMessage={
messageIndex < messageHistoryIndex &&
toolResponsesMap.get(toolRequest.id) == undefined
}
toolRequest={toolRequest}
toolResponse={toolResponsesMap.get(toolRequest.id)}
notifications={toolCallNotifications.get(toolRequest.id)}
isStreamingMessage={isStreaming}
append={append}
/>
<div className={cn(displayText && 'mt-2')}>
{isFirstInChain ? (
<ToolCallChain
messages={messages}
chainIndices={messageChain}
toolCallNotifications={toolCallNotifications}
toolResponsesMap={toolResponsesMap}
messageHistoryIndex={messageHistoryIndex}
isStreaming={isStreaming}
/>
) : !messageChain ? (
<div className="relative flex flex-col w-full">
<div className="flex flex-col gap-3">
{toolRequests.map((toolRequest) => (
<div className="goose-message-tool" key={toolRequest.id}>
<ToolCallWithResponse
isCancelledMessage={
messageIndex < messageHistoryIndex &&
toolResponsesMap.get(toolRequest.id) == undefined
}
toolRequest={toolRequest}
toolResponse={toolResponsesMap.get(toolRequest.id)}
notifications={toolCallNotifications.get(toolRequest.id)}
isStreamingMessage={isStreaming}
append={append}
/>
</div>
))}
</div>
<div className="text-xs text-text-muted pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
{!isStreaming && timestamp}
</div>
</div>
))}
<div className="text-xs text-text-muted pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
{!isStreaming && timestamp}
</div>
) : null}
</div>
)}
@@ -229,9 +302,8 @@ export default function GooseMessage({
</div>
{/* TODO(alexhancock): Re-enable link previews once styled well again */}
{/* eslint-disable-next-line no-constant-binary-expression */}
{false && urls.length > 0 && (
<div className="flex flex-wrap mt-[16px]">
{urls.length > 0 && (
<div className="mt-4">
{urls.map((url, index) => (
<LinkPreview key={index} url={url} />
))}
@@ -0,0 +1,56 @@
import { formatMessageTimestamp } from '../utils/timeUtils';
import { Message, getToolRequests } from '../types/message';
import { NotificationEvent } from '../hooks/useMessageStream';
import ToolCallWithResponse from './ToolCallWithResponse';
interface ToolCallChainProps {
messages: Message[];
chainIndices: number[];
toolCallNotifications: Map<string, NotificationEvent[]>;
toolResponsesMap: Map<string, import('../types/message').ToolResponseMessageContent>;
messageHistoryIndex: number;
isStreaming?: boolean;
}
export default function ToolCallChain({
messages,
chainIndices,
toolCallNotifications,
toolResponsesMap,
messageHistoryIndex,
isStreaming = false,
}: ToolCallChainProps) {
const lastMessageIndex = chainIndices[chainIndices.length - 1];
const lastMessage = messages[lastMessageIndex];
const timestamp = lastMessage ? formatMessageTimestamp(lastMessage.created) : '';
return (
<div className="relative flex flex-col w-full">
<div className="flex flex-col gap-3">
{chainIndices.map((messageIndex) => {
const message = messages[messageIndex];
const toolRequests = getToolRequests(message);
return toolRequests.map((toolRequest) => (
<div key={toolRequest.id} className="goose-message-tool">
<ToolCallWithResponse
isCancelledMessage={
messageIndex < messageHistoryIndex &&
toolResponsesMap.get(toolRequest.id) == undefined
}
toolRequest={toolRequest}
toolResponse={toolResponsesMap.get(toolRequest.id)}
notifications={toolCallNotifications.get(toolRequest.id)}
isStreamingMessage={isStreaming}
/>
</div>
));
})}
</div>
<div className="text-xs text-text-muted pt-1 transition-all duration-200 group-hover:-translate-y-4 group-hover:opacity-0">
{!isStreaming && timestamp}
</div>
</div>
);
}
@@ -0,0 +1,61 @@
import React from 'react';
import { cn } from '../utils';
export type ToolCallStatus = 'pending' | 'loading' | 'success' | 'error';
interface ToolCallStatusIndicatorProps {
status: ToolCallStatus;
className?: string;
}
export const ToolCallStatusIndicator: React.FC<ToolCallStatusIndicatorProps> = ({
status,
className,
}) => {
const getStatusStyles = () => {
switch (status) {
case 'success':
return 'bg-green-500';
case 'error':
return 'bg-red-500';
case 'loading':
return 'bg-yellow-500 animate-pulse';
case 'pending':
default:
return 'bg-gray-400';
}
};
return (
<div
className={cn(
'absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full border border-background-default',
getStatusStyles(),
className
)}
aria-label={`Tool status: ${status}`}
/>
);
};
/**
* Wrapper component that adds a status indicator to a tool icon
*/
interface ToolIconWithStatusProps {
ToolIcon: React.ComponentType<{ className?: string }>;
status: ToolCallStatus;
className?: string;
}
export const ToolIconWithStatus: React.FC<ToolIconWithStatusProps> = ({
ToolIcon,
status,
className,
}) => {
return (
<div className={cn('relative inline-block', className)}>
<ToolIcon className="w-3 h-3 flex-shrink-0" />
<ToolCallStatusIndicator status={status} />
</div>
);
};
@@ -1,12 +1,14 @@
import { ToolIconWithStatus, ToolCallStatus } from './ToolCallStatusIndicator';
import { getToolCallIcon } from '../utils/toolIconMapping';
import React, { useEffect, useRef, useState } from 'react';
import { Button } from './ui/button';
import { ToolCallArguments, ToolCallArgumentValue } from './ToolCallArguments';
import MarkdownContent from './MarkdownContent';
import { Content, ToolRequestMessageContent, ToolResponseMessageContent } from '../types/message';
import { cn, snakeToTitleCase } from '../utils';
import Dot, { LoadingStatus } from './ui/Dot';
import { LoadingStatus } from './ui/Dot';
import { NotificationEvent } from '../hooks/useMessageStream';
import { ChevronRight, FlaskConical, LoaderCircle } from 'lucide-react';
import { ChevronRight, FlaskConical } from 'lucide-react';
import { TooltipWrapper } from './settings/providers/subcomponents/buttons/TooltipWrapper';
import MCPUIResourceRenderer from './MCPUIResourceRenderer';
import { isUIResource } from '@mcp-ui/client';
@@ -173,16 +175,13 @@ function ToolCallView({
}: ToolCallViewProps) {
const [responseStyle, setResponseStyle] = useState(() => localStorage.getItem('response_style'));
// Listen for localStorage changes to update the response style
useEffect(() => {
const handleStorageChange = () => {
setResponseStyle(localStorage.getItem('response_style'));
};
// Listen for storage events (changes from other tabs/windows)
window.addEventListener('storage', handleStorageChange);
// Listen for custom events (changes from same tab)
window.addEventListener('responseStyleChanged', handleStorageChange);
return () => {
@@ -283,12 +282,10 @@ function ToolCallView({
const args = toolCall.arguments as Record<string, ToolCallArgumentValue>;
const toolName = toolCall.name.substring(toolCall.name.lastIndexOf('__') + 2);
// Helper function to get string value safely
const getStringValue = (value: ToolCallArgumentValue): string => {
return typeof value === 'string' ? value : JSON.stringify(value);
};
// Helper function to truncate long values
const truncate = (str: string, maxLength: number = 50): string => {
return str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
};
@@ -442,34 +439,45 @@ function ToolCallView({
// Fallback tool name formatting
return snakeToTitleCase(toolCall.name.substring(toolCall.name.lastIndexOf('__') + 2));
};
// Map LoadingStatus to ToolCallStatus
const getToolCallStatus = (loadingStatus: LoadingStatus): ToolCallStatus => {
switch (loadingStatus) {
case 'success':
return 'success';
case 'error':
return 'error';
case 'loading':
return 'loading';
default:
return 'pending';
}
};
const toolCallStatus = getToolCallStatus(loadingStatus);
const toolLabel = (
<span className={cn('ml-2', extensionTooltip && 'cursor-pointer hover:opacity-80')}>
{getToolLabelContent()}
<span
className={cn(
'flex items-center gap-2',
extensionTooltip && 'cursor-pointer hover:opacity-80'
)}
>
<ToolIconWithStatus ToolIcon={getToolCallIcon(toolCall.name)} status={toolCallStatus} />
<span>{getToolLabelContent()}</span>
</span>
);
return (
<ToolCallExpandable
isStartExpanded={isRenderingProgress}
isForceExpand={isShouldExpand}
label={
<>
<div className="w-2 flex items-center justify-center">
{loadingStatus === 'loading' ? (
<LoaderCircle className="animate-spin text-text-muted" size={3} />
) : (
<Dot size={2} loadingStatus={loadingStatus} />
)}
</div>
{extensionTooltip ? (
<TooltipWrapper tooltipContent={extensionTooltip} side="top" align="start">
{toolLabel}
</TooltipWrapper>
) : (
toolLabel
)}
</>
extensionTooltip ? (
<TooltipWrapper tooltipContent={extensionTooltip} side="top" align="start">
{toolLabel}
</TooltipWrapper>
) : (
toolLabel
)
}
>
{/* Tool Details */}
@@ -0,0 +1,23 @@
export const Archive = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="#1E1E20" />
<path
d="M1.375 6.1875H9.625V8.25C9.625 8.6297 9.3172 8.9375 8.9375 8.9375H2.0625C1.6828 8.9375 1.375 8.6297 1.375 8.25V6.1875Z"
fill="#DBD7CE"
/>
<path d="M1.375 6.1875H9.625V7.5625H1.375V6.1875Z" fill="#434343" />
<path d="M1.375 4.8125H9.625V6.1875H1.375V4.8125Z" fill="white" />
<path d="M1.375 3.4375H9.625V4.8125H1.375V3.4375Z" fill="#616161" />
<path
d="M1.375 2.75C1.375 2.3703 1.6828 2.0625 2.0625 2.0625H8.9375C9.3172 2.0625 9.625 2.3703 9.625 2.75V3.4375H1.375V2.75Z"
fill="#9F9F9F"
/>
</svg>
);
@@ -0,0 +1,27 @@
export const Brain = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="#3E3E3E" />
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_782)" />
<path d="M5.5 2.0625L1.375 5.5L5.5 8.9375L9.625 5.5L5.5 2.0625Z" fill="#E74786" />
<defs>
<linearGradient
id="paint0_linear_6313_782"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop />
<stop offset="1" stopColor="#323232" />
</linearGradient>
</defs>
</svg>
);
@@ -0,0 +1,33 @@
export const Camera = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_741)" />
<path
d="M1.375 3.4375C1.375 3.0578 1.6828 2.75 2.0625 2.75H8.9375C9.3172 2.75 9.625 3.0578 9.625 3.4375V7.5625C9.625 7.9422 9.3172 8.25 8.9375 8.25H2.0625C1.6828 8.25 1.375 7.9422 1.375 7.5625V3.4375Z"
fill="#2F2F31"
/>
<path
d="M6.53125 5.5C6.53125 6.06954 6.06954 6.53125 5.5 6.53125C4.93046 6.53125 4.46875 6.06954 4.46875 5.5C4.46875 4.93046 4.93046 4.46875 5.5 4.46875C6.06954 4.46875 6.53125 4.93046 6.53125 5.5Z"
fill="white"
/>
<defs>
<linearGradient
id="paint0_linear_6313_741"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#E2E3F7" />
<stop offset="1" stopColor="#978E8F" />
</linearGradient>
</defs>
</svg>
);
@@ -0,0 +1,13 @@
export const Code2 = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="#1C1C1E" />
<rect x="2" y="5" width="7" height="1" rx="0.5" fill="#19FF4F" />
</svg>
);
@@ -0,0 +1,33 @@
export const Eye = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_805)" />
<path
d="M1.375 5.5C1.375 4.36091 2.29841 3.4375 3.4375 3.4375H7.5625C8.70159 3.4375 9.625 4.36091 9.625 5.5V5.5C9.625 6.63909 8.70159 7.5625 7.5625 7.5625H3.4375C2.29841 7.5625 1.375 6.63909 1.375 5.5V5.5Z"
fill="white"
/>
<path
d="M6.53125 5.5C6.53125 6.06954 6.06954 6.53125 5.5 6.53125C4.93046 6.53125 4.46875 6.06954 4.46875 5.5C4.46875 4.93046 4.93046 4.46875 5.5 4.46875C6.06954 4.46875 6.53125 4.93046 6.53125 5.5Z"
fill="black"
/>
<defs>
<linearGradient
id="paint0_linear_6313_805"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#00A2FF" />
<stop offset="1" stopColor="#5A6DFF" />
</linearGradient>
</defs>
</svg>
);
@@ -0,0 +1,33 @@
export const FileEdit = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_755)" />
<path
d="M8.96875 2.03125L8.96875 4.78125L2.09375 4.78125L2.09375 3.40625C2.09375 2.64686 2.70936 2.03125 3.46875 2.03125L8.96875 2.03125Z"
fill="white"
/>
<path
d="M5.03125 6.03125L5.03125 9.03125L2.03125 9.03125L2.03125 7.53125C2.03125 6.70282 2.29988 6.03125 2.63125 6.03125L5.03125 6.03125Z"
fill="white"
/>
<defs>
<linearGradient
id="paint0_linear_6313_755"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop />
<stop offset="1" stopColor="#383838" />
</linearGradient>
</defs>
</svg>
);
@@ -0,0 +1,27 @@
export const FilePlus = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_774)" />
<rect x="2" y="5" width="7" height="1" rx="0.5" fill="white" />
<rect x="6" y="2" width="7" height="1" rx="0.5" transform="rotate(90 6 2)" fill="white" />
<defs>
<linearGradient
id="paint0_linear_6313_774"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FF9A00" />
<stop offset="1" stopColor="#FFC800" />
</linearGradient>
</defs>
</svg>
);
@@ -0,0 +1,31 @@
export const FileText = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_764)" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.73614 2.26386C9.00462 2.53235 9.00462 2.96765 8.73614 3.23614L3.23614 8.73614C2.96765 9.00462 2.53235 9.00462 2.26386 8.73614C1.99538 8.46765 1.99538 8.03235 2.26386 7.76386L7.76386 2.26386C8.03235 1.99538 8.46765 1.99538 8.73614 2.26386Z"
fill="white"
/>
<defs>
<linearGradient
id="paint0_linear_6313_764"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FFB645" />
<stop offset="1" stopColor="#FF8735" />
</linearGradient>
</defs>
</svg>
);
@@ -0,0 +1,34 @@
export const Globe = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_6313_720)">
<rect width="11" height="11" rx="2" fill="white" />
<path
d="M10.3125 5.5C10.3125 8.15787 8.15787 10.3125 5.5 10.3125C2.84213 10.3125 0.6875 8.15787 0.6875 5.5C0.6875 2.84213 2.84213 0.6875 5.5 0.6875C8.15787 0.6875 10.3125 2.84213 10.3125 5.5Z"
fill="url(#paint0_linear_6313_720)"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_6313_720"
x1="5.5"
y1="0.6875"
x2="5.5"
y2="10.3125"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#00CAF7" />
<stop offset="1" stopColor="#0B54DE" />
</linearGradient>
<clipPath id="clip0_6313_720">
<rect width="11" height="11" rx="2" fill="white" />
</clipPath>
</defs>
</svg>
);
@@ -0,0 +1,16 @@
export const Harddrive = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="#1E1E20" />
<path
d="M5 3.5C5 4.32843 4.32843 5 3.5 5C2.67157 5 2 4.32843 2 3.5C2 2.67157 2.67157 2 3.5 2C4.32843 2 5 2.67157 5 3.5Z"
fill="white"
/>
</svg>
);
@@ -0,0 +1,16 @@
export const Monitor = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="white" />
<path
d="M9.625 5.5C9.625 7.77817 7.77817 9.625 5.5 9.625C3.22183 9.625 1.375 7.77817 1.375 5.5C1.375 3.22183 3.22183 1.375 5.5 1.375C7.77817 1.375 9.625 3.22183 9.625 5.5Z"
fill="#E60023"
/>
</svg>
);
@@ -0,0 +1,37 @@
export const Numbers = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_717)" />
<path
d="M4.8125 3.4375C4.8125 3.0578 5.1203 2.75 5.5 2.75C5.8797 2.75 6.1875 3.0578 6.1875 3.4375V7.5625C6.1875 7.9422 5.8797 8.25 5.5 8.25C5.1203 8.25 4.8125 7.9422 4.8125 7.5625V3.4375Z"
fill="white"
/>
<path
d="M2.0625 6.1875C2.0625 5.8078 2.3703 5.5 2.75 5.5C3.1297 5.5 3.4375 5.8078 3.4375 6.1875V7.5625C3.4375 7.9422 3.1297 8.25 2.75 8.25C2.3703 8.25 2.0625 7.9422 2.0625 7.5625V6.1875Z"
fill="white"
/>
<path
d="M8.25 4.125C7.8703 4.125 7.5625 4.4328 7.5625 4.8125V7.5625C7.5625 7.9422 7.8703 8.25 8.25 8.25C8.6297 8.25 8.9375 7.9422 8.9375 7.5625V4.8125C8.9375 4.4328 8.6297 4.125 8.25 4.125Z"
fill="white"
/>
<defs>
<linearGradient
id="paint0_linear_6313_717"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#73FA80" />
<stop offset="1" stopColor="#00D648" />
</linearGradient>
</defs>
</svg>
);
@@ -0,0 +1,13 @@
export const Save = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="#C32361" />
<path d="M5.5 2.0625L1.375 5.5L5.5 8.9375L9.625 5.5L5.5 2.0625Z" fill="#E74786" />
</svg>
);
@@ -0,0 +1,29 @@
export const Search = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_760)" />
<path
d="M7.5625 5.5C7.5625 6.63909 6.63909 7.5625 5.5 7.5625C4.36091 7.5625 3.4375 6.63909 3.4375 5.5C3.4375 4.36091 4.36091 3.4375 5.5 3.4375C6.63909 3.4375 7.5625 4.36091 7.5625 5.5Z"
fill="#2D2D2E"
/>
<defs>
<linearGradient
id="paint0_linear_6313_760"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#D2D5DA" />
<stop offset="1" stopColor="#8B8E95" />
</linearGradient>
</defs>
</svg>
);
@@ -0,0 +1,40 @@
export const Settings = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_6313_731)">
<rect width="11" height="11" rx="2" fill="url(#paint0_linear_6313_731)" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.5 8.9375C7.39848 8.9375 8.9375 7.39848 8.9375 5.5C8.9375 3.60152 7.39848 2.0625 5.5 2.0625C3.60152 2.0625 2.0625 3.60152 2.0625 5.5C2.0625 7.39848 3.60152 8.9375 5.5 8.9375ZM5.5 10.3125C8.15787 10.3125 10.3125 8.15787 10.3125 5.5C10.3125 2.84213 8.15787 0.6875 5.5 0.6875C2.84213 0.6875 0.6875 2.84213 0.6875 5.5C0.6875 8.15787 2.84213 10.3125 5.5 10.3125Z"
fill="#2D2D2E"
/>
<path
d="M7.5625 5.5C7.5625 6.63909 6.63909 7.5625 5.5 7.5625C4.36091 7.5625 3.4375 6.63909 3.4375 5.5C3.4375 4.36091 4.36091 3.4375 5.5 3.4375C6.63909 3.4375 7.5625 4.36091 7.5625 5.5Z"
fill="#2D2D2E"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_6313_731"
x1="5.5"
y1="0"
x2="5.5"
y2="11"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#D2D5DA" />
<stop offset="1" stopColor="#8B8E95" />
</linearGradient>
<clipPath id="clip0_6313_731">
<rect width="11" height="11" rx="2" fill="white" />
</clipPath>
</defs>
</svg>
);
@@ -0,0 +1,13 @@
export const Terminal = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="#1C1C1E" />
<path d="M2 3L2 2L6 2L6 3L2 3Z" fill="#19FF4F" />
</svg>
);
@@ -0,0 +1,13 @@
export const Tool = ({ className }: { className?: string }) => (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="11" height="11" rx="2" fill="#1E1E20" />
<rect x="2" y="5" width="7" height="1" rx="0.5" fill="white" />
</svg>
);
@@ -0,0 +1,17 @@
export { Archive } from './Archive';
export { Brain } from './Brain';
export { Camera } from './Camera';
export { Code2 } from './Code2';
export { Eye } from './Eye';
export { FileEdit } from './FileEdit';
export { FilePlus } from './FilePlus';
export { FileText } from './FileText';
export { Globe } from './Globe';
export { Harddrive } from './Harddrive';
export { Monitor } from './Monitor';
export { Numbers } from './Numbers';
export { Save } from './Save';
export { Search } from './Search';
export { Settings } from './Settings';
export { Terminal } from './Terminal';
export { Tool } from './Tool';