Files
qwen-code/packages/vscode-ide-companion/src/webview/components/Timeline.tsx
yiliang114 a33187ed7a feat(vscode-ide-companion): 新增时间线组件
- 新增 Timeline 组件用于显示会话历史
- 支持展示消息、工具调用等事件
- 提供清晰的时间轴视图

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 01:53:05 +08:00

127 lines
3.2 KiB
TypeScript

/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { useState } from 'react';
import './Timeline.css';
export type TimelineItemType =
| 'user-message'
| 'assistant-message'
| 'tool-call'
| 'tool-output'
| 'thinking';
export interface TimelineItemProps {
type: TimelineItemType;
children: React.ReactNode;
/** 是否可折叠(主要用于工具输出) */
collapsible?: boolean;
/** 默认是否展开 */
defaultExpanded?: boolean;
/** 自定义标题(用于折叠时显示) */
title?: string;
/** 是否是最后一个项目 */
isLast?: boolean;
}
/**
* Timeline 项目组件 - 统一展示消息和工具调用
*/
export const TimelineItem: React.FC<TimelineItemProps> = ({
type,
children,
collapsible = false,
defaultExpanded = true,
title,
isLast = false,
}) => {
const [expanded, setExpanded] = useState(defaultExpanded);
const getDotColor = (): string => {
switch (type) {
case 'user-message':
return 'blue'; // 用户消息 - 蓝色
case 'assistant-message':
return 'gray'; // LLM 输出 - 灰色
case 'tool-call':
return 'green'; // 工具调用 - 绿色
case 'tool-output':
return 'gray'; // 工具输出 - 灰色
case 'thinking':
return 'purple'; // 思考 - 紫色
default:
return 'gray';
}
};
const getItemLabel = (): string | null => {
switch (type) {
case 'user-message':
return 'You';
case 'assistant-message':
return 'Qwen';
case 'tool-call':
return 'Tool Call';
case 'tool-output':
return 'Output';
case 'thinking':
return 'Thinking';
default:
return null;
}
};
const dotColor = getDotColor();
const itemLabel = getItemLabel();
return (
<div className={`timeline-item ${type} ${isLast ? 'last' : ''}`}>
{/* 时间线连接线 - 暂时禁用 */}
{/* {!isLast && <div className="timeline-line" />} */}
{/* 状态圆点 */}
<div className={`timeline-dot ${dotColor}`}>
<span className="dot-inner" />
</div>
{/* 内容区域 */}
<div className="timeline-content">
{/* 标签(可选) */}
{itemLabel && <div className="timeline-label">{itemLabel}</div>}
{/* 可折叠内容 */}
{collapsible ? (
<>
<button
className="timeline-toggle"
onClick={() => setExpanded(!expanded)}
aria-expanded={expanded}
>
<span className="timeline-toggle-icon">
{expanded ? '▼' : '▶'}
</span>
<span className="timeline-toggle-text">
{title || (expanded ? 'Hide details' : 'Show details')}
</span>
</button>
{expanded && <div className="timeline-body">{children}</div>}
</>
) : (
<div className="timeline-body">{children}</div>
)}
</div>
</div>
);
};
/**
* Timeline 容器组件
*/
export const Timeline: React.FC<{ children: React.ReactNode }> = ({
children,
}) => <div className="timeline-container">{children}</div>;