mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-20 17:52:16 +00:00
188 lines
5.2 KiB
TypeScript
188 lines
5.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Shared layout components for tool call UI
|
|
* Platform-agnostic version using webui components
|
|
*/
|
|
|
|
import type { FC } from 'react';
|
|
import { FileLink } from '../../layout/FileLink.js';
|
|
import './LayoutComponents.css';
|
|
|
|
/**
|
|
* Props for ToolCallContainer
|
|
*/
|
|
export interface ToolCallContainerProps {
|
|
/** Operation label (e.g., "Read", "Write", "Search") */
|
|
label: string;
|
|
/** Status for bullet color: 'success' | 'error' | 'warning' | 'loading' | 'default' */
|
|
status?: 'success' | 'error' | 'warning' | 'loading' | 'default';
|
|
/** Main content to display (optional - some tool calls only show title) */
|
|
children?: React.ReactNode;
|
|
/** Tool call ID for debugging */
|
|
toolCallId?: string;
|
|
/** Optional trailing content rendered next to label (e.g., clickable filename) */
|
|
labelSuffix?: React.ReactNode;
|
|
/** Optional custom class name */
|
|
className?: string;
|
|
/** Whether this is the first item in an AI response sequence (for timeline) */
|
|
isFirst?: boolean;
|
|
/** Whether this is the last item in an AI response sequence (for timeline) */
|
|
isLast?: boolean;
|
|
}
|
|
|
|
/**
|
|
* ToolCallContainer - Main container for tool call displays
|
|
* Features timeline connector line and status bullet
|
|
*/
|
|
export const ToolCallContainer: FC<ToolCallContainerProps> = ({
|
|
label,
|
|
status = 'success',
|
|
children,
|
|
toolCallId: _toolCallId,
|
|
labelSuffix,
|
|
className: _className,
|
|
isFirst = false,
|
|
isLast = false,
|
|
}) => (
|
|
<div
|
|
className={`qwen-message message-item ${_className || ''} relative pl-[30px] py-2 select-text toolcall-container toolcall-status-${status}`}
|
|
data-first={isFirst}
|
|
data-last={isLast}
|
|
>
|
|
<div className="toolcall-content-wrapper flex flex-col min-w-0 max-w-full">
|
|
<div className="flex items-baseline gap-1.5 relative min-w-0">
|
|
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
|
{label}
|
|
</span>
|
|
<span className="text-[11px] text-[var(--app-secondary-foreground)]">
|
|
{labelSuffix}
|
|
</span>
|
|
</div>
|
|
{children && (
|
|
<div className="text-[var(--app-secondary-foreground)]">{children}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
/**
|
|
* Props for ToolCallCard
|
|
*/
|
|
interface ToolCallCardProps {
|
|
icon: string;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* ToolCallCard - Legacy card wrapper for complex layouts like diffs
|
|
*/
|
|
export const ToolCallCard: FC<ToolCallCardProps> = ({
|
|
icon: _icon,
|
|
children,
|
|
}) => (
|
|
<div className="grid grid-cols-[auto_1fr] gap-medium bg-[var(--app-input-background)] border border-[var(--app-input-border)] rounded-medium p-large my-medium items-start animate-[fadeIn_0.2s_ease-in] toolcall-card">
|
|
<div className="flex flex-col gap-medium min-w-0">{children}</div>
|
|
</div>
|
|
);
|
|
|
|
/**
|
|
* Props for ToolCallRow
|
|
*/
|
|
interface ToolCallRowProps {
|
|
label: string;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* ToolCallRow - A single row in the tool call grid (legacy - for complex layouts)
|
|
*/
|
|
export const ToolCallRow: FC<ToolCallRowProps> = ({ label, children }) => (
|
|
<div className="grid grid-cols-[80px_1fr] gap-medium min-w-0">
|
|
<div className="text-xs text-[var(--app-secondary-foreground)] font-medium pt-[2px]">
|
|
{label}
|
|
</div>
|
|
<div className="text-[var(--app-primary-foreground)] min-w-0 break-words">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
/**
|
|
* Props for StatusIndicator
|
|
*/
|
|
interface StatusIndicatorProps {
|
|
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
|
text: string;
|
|
}
|
|
|
|
/**
|
|
* Get status color class for StatusIndicator
|
|
*/
|
|
const getStatusColorClass = (
|
|
status: 'pending' | 'in_progress' | 'completed' | 'failed',
|
|
): string => {
|
|
switch (status) {
|
|
case 'pending':
|
|
return 'bg-[#ffc107]';
|
|
case 'in_progress':
|
|
return 'bg-[#2196f3]';
|
|
case 'completed':
|
|
return 'bg-[#4caf50]';
|
|
case 'failed':
|
|
return 'bg-[#f44336]';
|
|
default:
|
|
return 'bg-gray-500';
|
|
}
|
|
};
|
|
|
|
/**
|
|
* StatusIndicator - Status indicator with colored dot
|
|
*/
|
|
export const StatusIndicator: FC<StatusIndicatorProps> = ({ status, text }) => (
|
|
<div className="inline-block font-medium relative" title={status}>
|
|
<span
|
|
className={`inline-block w-1.5 h-1.5 rounded-full mr-1.5 align-middle ${getStatusColorClass(status)}`}
|
|
/>
|
|
{text}
|
|
</div>
|
|
);
|
|
|
|
/**
|
|
* Props for CodeBlock
|
|
*/
|
|
interface CodeBlockProps {
|
|
children: string;
|
|
}
|
|
|
|
/**
|
|
* CodeBlock - Code block for displaying formatted code or output
|
|
*/
|
|
export const CodeBlock: FC<CodeBlockProps> = ({ children }) => (
|
|
<pre className="font-mono text-[var(--app-monospace-font-size)] bg-[var(--app-primary-background)] border border-[var(--app-input-border)] rounded-small p-medium overflow-x-auto mt-1 whitespace-pre-wrap break-words max-h-[300px] overflow-y-auto">
|
|
{children}
|
|
</pre>
|
|
);
|
|
|
|
/**
|
|
* Props for LocationsList
|
|
*/
|
|
interface LocationsListProps {
|
|
locations: Array<{
|
|
path: string;
|
|
line?: number | null;
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* LocationsList - List of file locations with clickable links
|
|
*/
|
|
export const LocationsList: FC<LocationsListProps> = ({ locations }) => (
|
|
<div className="toolcall-locations-list flex flex-col gap-1 max-w-full">
|
|
{locations.map((loc, idx) => (
|
|
<FileLink key={idx} path={loc.path} line={loc.line} showFullPath={true} />
|
|
))}
|
|
</div>
|
|
);
|