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:
@@ -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';
|
||||
Reference in New Issue
Block a user