Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 62 additions & 11 deletions src/web-ui/src/flow_chat/tool-cards/CreatePlanDisplay.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--flowchat-inline-gap, 0.35rem);
padding: var(--flowchat-card-pad-y, 0.625rem) var(--flowchat-card-pad-x, 0.75rem);
background: linear-gradient(
90deg,
Expand All @@ -57,19 +58,32 @@
rgba(255, 255, 255, 0.02) 100%
);
border-bottom: 1px solid var(--border-base);

&--clickable {

.create-plan-header-main {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
padding: 0;
background: transparent;
border: none;
color: inherit;
text-align: left;

&--clickable {
cursor: pointer;
transition: background 0.15s ease;
}

&:disabled {
cursor: default;
}
}

.create-plan-header-main--clickable {
cursor: pointer;
transition: background 0.15s ease;


&:hover {
background: linear-gradient(
90deg,
rgba(245, 158, 11, 0.13) 0%,
rgba(245, 158, 11, 0.08) 50%,
rgba(255, 255, 255, 0.05) 100%
);

.file-name {
color: var(--tool-card-text-primary);
}
Expand All @@ -79,6 +93,7 @@
.header-left {
display: flex;
align-items: center;
min-width: 0;
gap: var(--flowchat-inline-gap, 0.35rem);
}

Expand All @@ -102,6 +117,42 @@
font-size: var(--flowchat-font-size-xs);
font-family: var(--tool-card-font-mono);
color: var(--tool-card-text-secondary);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.create-plan-header-folder-btn-wrapper {
display: inline-flex;
flex-shrink: 0;
}

.create-plan-header-folder-btn {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 30px;
height: 30px;
padding: 0;
border: 1px solid transparent;
border-radius: 6px;
background: transparent;
color: var(--tool-card-text-muted);
cursor: pointer;
transition: all 0.15s ease;

&:hover:not(:disabled) {
border-color: rgba(245, 158, 11, 0.35);
background: rgba(245, 158, 11, 0.07);
color: #f59e0b;
}

&:disabled {
opacity: 0.45;
cursor: default;
}
}

&--loading-shimmer {
Expand Down
76 changes: 59 additions & 17 deletions src/web-ui/src/flow_chat/tool-cards/CreatePlanDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { ClipboardList, Circle, Loader2, CheckCircle, CheckCircle2, PlayCircle, XCircle, ChevronsUpDown, ChevronsDownUp } from 'lucide-react';
import { ClipboardList, Circle, Loader2, CheckCircle, CheckCircle2, PlayCircle, XCircle, ChevronsUpDown, ChevronsDownUp, FolderOpen } from 'lucide-react';
import type { ToolCardProps } from '../types/flow-chat';
import { ideControl } from '@/shared/services/ide-control/api';
import { flowChatManager } from '@/flow_chat/services/FlowChatManager';
Expand All @@ -20,6 +20,8 @@ import { createLogger } from '@/shared/utils/logger';
import { useToolCardHeightContract } from './useToolCardHeightContract';
import { basenamePath, dirnameAbsolutePath } from '@/shared/utils/pathUtils';
import { createTodoRenderItems } from './todoRenderItems';
import { useOptionalCurrentWorkspace } from '@/infrastructure/contexts/WorkspaceContext';
import { isRemoteWorkspace } from '@/shared/types';
import './CreatePlanDisplay.scss';

const log = createLogger('PlanDisplay');
Expand Down Expand Up @@ -69,6 +71,7 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({
cacheKey,
}) => {
const { t } = useTranslation('flow-chat');
const { workspace: currentWorkspace } = useOptionalCurrentWorkspace();
const effectiveCacheKey = cacheKey || planFilePath;

const [refreshedData, setRefreshedData] = useState<PlanData | null>(() => {
Expand Down Expand Up @@ -108,6 +111,8 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({
() => createTodoRenderItems(planData?.todos ?? []),
[planData?.todos],
);
const planDirectoryPath = useMemo(() => dirnameAbsolutePath(planFilePath), [planFilePath]);
const isRevealPlanDisabled = !planFilePath || isRemoteWorkspace(currentWorkspace);

// Subscribe to shared build state service for cross-component sync.
useEffect(() => {
Expand Down Expand Up @@ -143,7 +148,6 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({
}

const normalizedPlanPath = planFilePath.replace(/\\/g, '/');
const dirPath = dirnameAbsolutePath(planFilePath);

const loadFromFile = async () => {
// Skip refresh while writing to avoid feedback loops.
Expand Down Expand Up @@ -181,13 +185,13 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({
loadFromFile();
}

if (!dirPath) {
if (!planDirectoryPath) {
return;
}

let debounceTimer: ReturnType<typeof setTimeout> | null = null;

const unwatch = fileSystemService.watchFileChanges(dirPath, (event) => {
const unwatch = fileSystemService.watchFileChanges(planDirectoryPath, (event) => {
const eventPath = event.path.replace(/\\/g, '/');
if (eventPath !== normalizedPlanPath) {
return;
Expand All @@ -212,7 +216,7 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({
clearTimeout(debounceTimer);
}
};
}, [effectiveCacheKey, planFilePath, initialName, initialOverview, initialTodos]);
}, [effectiveCacheKey, planFilePath, planDirectoryPath, initialName, initialOverview, initialTodos]);

// Build button status transitions: build -> building -> built.
const buildStatus = useMemo((): 'build' | 'building' | 'built' => {
Expand Down Expand Up @@ -246,6 +250,23 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({
}
}, [planFilePath]);

const handleRevealPlanInExplorer = useCallback(async (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();

if (isRevealPlanDisabled || !planFilePath) {
return;
}

try {
await workspaceAPI.revealInExplorer(planFilePath);
} catch (error) {
log.warn('Failed to reveal plan file in explorer', {
planFilePath,
error,
});
}
}, [isRevealPlanDisabled, planFilePath]);

const handleBuild = useCallback(async () => {
if (!planFilePath || buildStatus !== 'build') return;

Expand Down Expand Up @@ -306,6 +327,9 @@ ${JSON.stringify(simpleTodos, null, 2)}
}, [applyExpandedState, isTodosExpanded]);

const isLoading = status === 'preparing' || status === 'streaming' || status === 'running';
const revealPlanTooltip = isRevealPlanDisabled
? t('toolCards.plan.revealPlanUnavailable')
: t('toolCards.plan.revealPlanInExplorer');

if (!planData) {
return (
Expand All @@ -323,19 +347,37 @@ ${JSON.stringify(simpleTodos, null, 2)}
data-tool-card-id={toolCardId ?? ''}
className={`create-plan-display status-${status}${isLoading ? ' create-plan-display--plan-generating' : ''}`}
>
<Tooltip content={t('toolCards.plan.clickToOpenPlan')}>
<div
className={`create-plan-header create-plan-header--clickable${isLoading ? ' create-plan-header--loading-shimmer' : ''}`}
onClick={handleViewPlan}
>
<div className="header-left">
<div className="file-icon-wrapper">
<ClipboardList size={14} />
<div
className={`create-plan-header${isLoading ? ' create-plan-header--loading-shimmer' : ''}`}
>
<Tooltip content={t('toolCards.plan.clickToOpenPlan')}>
<button
type="button"
className="create-plan-header-main create-plan-header-main--clickable"
onClick={handleViewPlan}
>
<div className="header-left">
<div className="file-icon-wrapper">
<ClipboardList size={14} />
</div>
<span className="file-name">{planFileName}</span>
</div>
<span className="file-name">{planFileName}</span>
</div>
</div>
</Tooltip>
</button>
</Tooltip>
<Tooltip content={revealPlanTooltip}>
<span className="create-plan-header-folder-btn-wrapper">
<button
className="create-plan-header-folder-btn"
type="button"
onClick={handleRevealPlanInExplorer}
disabled={isRevealPlanDisabled}
aria-label={revealPlanTooltip}
>
<FolderOpen size={14} />
</button>
</span>
</Tooltip>
</div>

<div className="create-plan-content">
<div className="plan-content-left">
Expand Down
2 changes: 2 additions & 0 deletions src/web-ui/src/locales/en-US/flow-chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,8 @@
"loadingPlan": "Loading plan...",
"generating": "Generating...",
"clickToOpenPlan": "Click to open plan file",
"revealPlanInExplorer": "Reveal plan file in file manager",
"revealPlanUnavailable": "Plan files can only be revealed for local workspaces",
"remainingTodos": "{{count}} Remaining To-dos",
"viewPlan": "View Plan",
"build": "Build",
Expand Down
2 changes: 2 additions & 0 deletions src/web-ui/src/locales/zh-CN/flow-chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,8 @@
"loadingPlan": "正在加载计划...",
"generating": "生成中...",
"clickToOpenPlan": "点击打开计划文件",
"revealPlanInExplorer": "在文件管理器中定位计划文件",
"revealPlanUnavailable": "仅本地工作区支持在文件管理器中定位计划文件",
"remainingTodos": "剩余 {{count}} 个待办",
"viewPlan": "查看计划",
"build": "构建",
Expand Down
2 changes: 2 additions & 0 deletions src/web-ui/src/locales/zh-TW/flow-chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,8 @@
"loadingPlan": "正在載入計劃...",
"generating": "生成中...",
"clickToOpenPlan": "點擊開啟計劃檔案",
"revealPlanInExplorer": "在檔案管理器中定位計劃檔案",
"revealPlanUnavailable": "僅本機工作區支援在檔案管理器中定位計劃檔案",
"remainingTodos": "剩餘 {{count}} 個待辦",
"viewPlan": "查看計劃",
"build": "構建",
Expand Down
Loading