Skip to content

Commit bc9b21c

Browse files
author
GCWing
committed
feat(web-ui): unify tool card status slot and processing indicator
Add ToolCardStatusSlot and ToolProcessingDots; route flow-chat and library tool cards through them. Polish Git and terminal tool cards, ToolStatusIndicator, and Markdown link handling; minor GlobalAPI and MCP tools config updates.
1 parent d3499cb commit bc9b21c

30 files changed

Lines changed: 589 additions & 278 deletions

src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
*/
55

66
import React from 'react';
7-
import { Loader2, CheckCircle, XCircle, Clock } from 'lucide-react';
7+
import { Loader2, CheckCircle, XCircle } from 'lucide-react';
88
import { useI18n } from '@/infrastructure/i18n';
9+
import { ToolProcessingDots } from '../ToolProcessingDots';
910
import './BaseToolCard.scss';
1011

1112
export interface BaseToolCardProps {
@@ -63,7 +64,7 @@ export const BaseToolCard: React.FC<BaseToolCardProps> = ({
6364
case 'error':
6465
return <XCircle className="base-tool-card__status-error" size={12} />;
6566
default:
66-
return <Clock className="base-tool-card__status-pending" size={12} />;
67+
return <ToolProcessingDots className="base-tool-card__status-pending" size={12} />;
6768
}
6869
};
6970

src/web-ui/src/component-library/components/FlowChatCards/SearchCard/SearchCard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
*/
55

66
import React, { useState, useMemo } from 'react';
7-
import { Search, File, FolderOpen, ChevronDown, ChevronUp, Loader2, CheckCircle, XCircle, Clock } from 'lucide-react';
7+
import { Search, File, FolderOpen, ChevronDown, ChevronUp, Loader2, CheckCircle, XCircle } from 'lucide-react';
88
import { useI18n } from '@/infrastructure/i18n';
99
import { BaseToolCard, BaseToolCardProps } from '../BaseToolCard';
10+
import { ToolProcessingDots } from '../ToolProcessingDots';
1011
import './SearchCard.scss';
1112

1213
export interface SearchCardProps extends Omit<BaseToolCardProps, 'toolName' | 'displayName'> {
@@ -98,7 +99,7 @@ export const SearchCard: React.FC<SearchCardProps> = ({
9899
case 'error':
99100
return <XCircle className="search-card__status-error" size={12} />;
100101
default:
101-
return <Clock className="search-card__status-pending" size={12} />;
102+
return <ToolProcessingDots className="search-card__status-pending" size={12} />;
102103
}
103104
};
104105

src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
*/
55

66
import React from 'react';
7-
import { CheckCircle, XCircle, Maximize2, FileText, Loader2, Clock } from 'lucide-react';
7+
import { CheckCircle, XCircle, Maximize2, FileText, Loader2 } from 'lucide-react';
88
import { useI18n } from '@/infrastructure/i18n';
99
import { BaseToolCard, BaseToolCardProps } from '../BaseToolCard';
10+
import { ToolProcessingDots } from '../ToolProcessingDots';
1011
import './SnapshotCard.scss';
1112

1213
export interface SnapshotCardProps extends Omit<BaseToolCardProps, 'toolName' | 'displayName'> {
@@ -71,7 +72,7 @@ export const SnapshotCard: React.FC<SnapshotCardProps> = ({
7172
case 'error':
7273
return <XCircle className="snapshot-card__status-error" size={12} />;
7374
default:
74-
return <Clock className="snapshot-card__status-pending" size={12} />;
75+
return <ToolProcessingDots className="snapshot-card__status-pending" size={12} />;
7576
}
7677
};
7778

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Three-dot "processing" indicator for tool cards
3+
*/
4+
.bitfun-tool-processing-dots {
5+
display: inline-flex;
6+
align-items: center;
7+
justify-content: center;
8+
color: inherit;
9+
vertical-align: middle;
10+
}
11+
12+
.bitfun-tool-processing-dots__dot {
13+
flex-shrink: 0;
14+
border-radius: 50%;
15+
background: currentColor;
16+
animation: bitfun-tool-processing-dots-bounce 1.05s ease-in-out infinite;
17+
18+
&:nth-child(2) {
19+
animation-delay: 0.15s;
20+
}
21+
22+
&:nth-child(3) {
23+
animation-delay: 0.3s;
24+
}
25+
}
26+
27+
.bitfun-tool-processing-dots--s10 {
28+
gap: 1px;
29+
30+
.bitfun-tool-processing-dots__dot {
31+
width: 2px;
32+
height: 2px;
33+
}
34+
}
35+
36+
.bitfun-tool-processing-dots--s12 {
37+
gap: 2px;
38+
39+
.bitfun-tool-processing-dots__dot {
40+
width: 2px;
41+
height: 2px;
42+
}
43+
}
44+
45+
.bitfun-tool-processing-dots--s14 {
46+
gap: 2px;
47+
48+
.bitfun-tool-processing-dots__dot {
49+
width: 2.5px;
50+
height: 2.5px;
51+
}
52+
}
53+
54+
.bitfun-tool-processing-dots--s16 {
55+
gap: 3px;
56+
57+
.bitfun-tool-processing-dots__dot {
58+
width: 3px;
59+
height: 3px;
60+
}
61+
}
62+
63+
@keyframes bitfun-tool-processing-dots-bounce {
64+
0%,
65+
60%,
66+
100% {
67+
transform: translateY(0);
68+
opacity: 0.55;
69+
}
70+
71+
30% {
72+
transform: translateY(-2px);
73+
opacity: 1;
74+
}
75+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Compact three-dot pulse for tool "pending / parsing" states (replaces clock icon).
3+
*/
4+
5+
import React from 'react';
6+
import './ToolProcessingDots.scss';
7+
8+
export type ToolProcessingDotsSize = 10 | 12 | 14 | 16;
9+
10+
export interface ToolProcessingDotsProps {
11+
/** Visual scale aligned with common lucide-react icon sizes in tool headers */
12+
size?: ToolProcessingDotsSize;
13+
className?: string;
14+
}
15+
16+
export const ToolProcessingDots: React.FC<ToolProcessingDotsProps> = ({
17+
size = 14,
18+
className = '',
19+
}) => (
20+
<span
21+
className={`bitfun-tool-processing-dots bitfun-tool-processing-dots--s${size} ${className}`.trim()}
22+
aria-hidden
23+
role="presentation"
24+
>
25+
<span className="bitfun-tool-processing-dots__dot" />
26+
<span className="bitfun-tool-processing-dots__dot" />
27+
<span className="bitfun-tool-processing-dots__dot" />
28+
</span>
29+
);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { ToolProcessingDots } from './ToolProcessingDots';
2+
export type { ToolProcessingDotsProps, ToolProcessingDotsSize } from './ToolProcessingDots';

src/web-ui/src/component-library/components/FlowChatCards/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { i18nService } from '@/infrastructure/i18n';
88
export { BaseToolCard } from './BaseToolCard';
99
export type { BaseToolCardProps } from './BaseToolCard';
1010

11+
export { ToolProcessingDots } from './ToolProcessingDots';
12+
export type { ToolProcessingDotsProps, ToolProcessingDotsSize } from './ToolProcessingDots';
13+
1114
export { SnapshotCard } from './SnapshotCard';
1215
export type { SnapshotCardProps } from './SnapshotCard';
1316

src/web-ui/src/component-library/components/Markdown/Markdown.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ import './Markdown.scss';
3030
const log = createLogger('Markdown');
3131
const COMPUTER_LINK_PREFIX = 'computer://';
3232

33+
// Module-level cache so that all simultaneously-mounting Markdown instances
34+
// (e.g. dozens of history blocks after a workspace switch) share a single
35+
// IPC round-trip for the workspace path. The in-flight deduplication in
36+
// GlobalAPI already coalesces concurrent calls into one; this cache avoids
37+
// even triggering a new IPC call while the result is still fresh.
38+
let _cachedWorkspacePathResult: string | undefined;
39+
let _cachedWorkspacePathAt = 0;
40+
const WORKSPACE_PATH_CACHE_MS = 5000;
41+
42+
async function getWorkspacePathCached(): Promise<string | undefined> {
43+
const now = Date.now();
44+
if (_cachedWorkspacePathResult !== undefined && now - _cachedWorkspacePathAt < WORKSPACE_PATH_CACHE_MS) {
45+
return _cachedWorkspacePathResult;
46+
}
47+
const result = await globalAPI.getCurrentWorkspacePath();
48+
_cachedWorkspacePathResult = result;
49+
_cachedWorkspacePathAt = Date.now();
50+
return result;
51+
}
52+
3353
/** Catches render errors from react-markdown/remark-gfm (e.g. RegExp in transformGfmAutolinkLiterals) and shows plain text fallback. */
3454
class MarkdownErrorBoundary extends Component<
3555
{ children: ReactNode; fallbackContent: string },
@@ -577,7 +597,7 @@ export const Markdown = React.memo<MarkdownProps>(({
577597
useEffect(() => {
578598
let cancelled = false;
579599

580-
void globalAPI.getCurrentWorkspacePath()
600+
void getWorkspacePathCached()
581601
.then((workspacePath) => {
582602
if (!cancelled && workspacePath) {
583603
setCurrentWorkspacePath(workspacePath);

src/web-ui/src/flow_chat/components/ToolStatusIndicator.tsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
*/
44

55
import React from 'react';
6-
import { Loader2, CheckCircle, XCircle, Clock, AlertCircle } from 'lucide-react';
6+
import { Loader2, CheckCircle, XCircle, AlertCircle, type LucideIcon } from 'lucide-react';
7+
import { ToolProcessingDots } from '@/component-library';
78
import type { ToolExecutionStatus } from '../../shared/types/tool-events';
89

910
interface ToolStatusIndicatorProps {
@@ -13,13 +14,24 @@ interface ToolStatusIndicatorProps {
1314
showLabel?: boolean;
1415
}
1516

16-
const STATUS_CONFIG = {
17+
const STATUS_CONFIG: Record<
18+
ToolExecutionStatus,
19+
{
20+
icon: LucideIcon | null;
21+
color: string;
22+
bgColor: string;
23+
label: string;
24+
animate: boolean;
25+
useDots?: boolean;
26+
}
27+
> = {
1728
pending: {
18-
icon: Clock,
29+
icon: null,
1930
color: 'text-gray-500',
2031
bgColor: 'bg-gray-100',
2132
label: 'Waiting',
22-
animate: false
33+
animate: false,
34+
useDots: true,
2335
},
2436
receiving: {
2537
icon: Loader2,
@@ -29,11 +41,12 @@ const STATUS_CONFIG = {
2941
animate: true
3042
},
3143
starting: {
32-
icon: Clock,
33-
color: 'text-blue-500',
44+
icon: null,
45+
color: 'text-blue-500',
3446
bgColor: 'bg-blue-100',
3547
label: 'Starting',
36-
animate: true
48+
animate: false,
49+
useDots: true,
3750
},
3851
running: {
3952
icon: Loader2,
@@ -72,7 +85,6 @@ export const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
7285
showLabel = true
7386
}) => {
7487
const config = STATUS_CONFIG[status];
75-
const Icon = config.icon;
7688

7789
const formatDuration = (ms: number) => {
7890
if (ms < 1000) return `${ms}ms`;
@@ -83,9 +95,13 @@ export const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
8395
return (
8496
<div className={`inline-flex items-center gap-2 ${className}`}>
8597
<div className={`flex items-center justify-center w-5 h-5 rounded-full ${config.bgColor}`}>
86-
<Icon
87-
className={`w-3 h-3 ${config.color} ${config.animate ? 'animate-spin' : ''}`}
88-
/>
98+
{config.useDots ? (
99+
<ToolProcessingDots size={12} className={config.color} />
100+
) : config.icon ? (
101+
<config.icon
102+
className={`w-3 h-3 ${config.color} ${config.animate ? 'animate-spin' : ''}`}
103+
/>
104+
) : null}
89105
</div>
90106

91107
{showLabel && (

src/web-ui/src/flow_chat/tool-cards/CodeReviewToolCard.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ import {
1010
AlertTriangle,
1111
AlertCircle,
1212
Info,
13-
Clock,
1413
ChevronDown,
1514
ChevronUp,
1615
} from 'lucide-react';
1716
import { useTranslation } from 'react-i18next';
18-
import { Tooltip } from '@/component-library';
17+
import { Tooltip, ToolProcessingDots } from '@/component-library';
1918
import type { ToolCardProps } from '../types/flow-chat';
2019
import { BaseToolCard, ToolCardHeader } from './BaseToolCard';
2120
import { createLogger } from '@/shared/utils/logger';
@@ -168,7 +167,7 @@ export const CodeReviewToolCard: React.FC<ToolCardProps> = React.memo(({
168167
return null;
169168
case 'pending':
170169
default:
171-
return <Clock size={12} />;
170+
return <ToolProcessingDots size={12} />;
172171
}
173172
};
174173

0 commit comments

Comments
 (0)