Skip to content
Open
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
96 changes: 96 additions & 0 deletions frontend/src/components/CustomIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 简化的图标组件 - 使用内联SVG
// 这些是为了演示暂停/继续/回溯功能所需要的基本图标

export function PauseCircleIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10" />
<line x1="10" y1="15" x2="10" y2="9" />
<line x1="14" y1="15" x2="14" y2="9" />
</svg>
);
}

export function PlayCircleIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10" />
<polygon points="10 8 16 12 10 16 10 8" />
</svg>
);
}

export function HistoryIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 12a9 9 0 1 0 9-9" />
<path d="M3 3v6h6" />
<path d="M12 7v5l3 3" />
</svg>
);
}

export function CodeIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="16 18 22 12 16 6" />
<polyline points="8 6 2 12 8 18" />
</svg>
);
}

export function EditIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
</svg>
);
}

export function RefreshIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="23 4 23 10 17 10" />
<polyline points="1 20 1 14 7 14" />
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
</svg>
);
}

export function InfoIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="16" x2="12" y2="12" />
<line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
);
}

export function XIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
);
}

export function CheckCircleIcon({ size = 24 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
);
}

export function PanelChevronIcon({ direction = "left", size = 16 }: { direction?: "left" | "right"; size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
{direction === "left" && <polyline points="15 18 9 12 15 6" />}
{direction === "right" && <polyline points="9 18 15 12 9 6" />}
</svg>
);
}
175 changes: 175 additions & 0 deletions frontend/src/components/InteractiveControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { useState, useEffect } from "react";
import {
HistoryIcon,
CodeIcon,
EditIcon,
InfoIcon,
CheckCircleIcon,
PauseCircleIcon,
XIcon,
} from "./CustomIcons";

/**
* Workflow Controls - provides quick access to useful actions when execution is paused
*/
export function InteractiveControls({
status,
onRewind,
onEditCode,
onEditRequirements,
}: {
status: string;
onRewind?: () => void;
onEditCode?: () => void;
onEditRequirements?: () => void;
}) {
const isPaused = status === "PAUSED";
const isFinished = status === "PASSED" || status === "FAILED" || status === "INTERRUPTED";

if (!isPaused && !isFinished) return null;

return (
<div className="interactive-controls-panel">
<div className="control-header">
<h3>Workflow Actions</h3>
</div>

<div className="control-tips">
{isPaused && (
<div className="control-tip-item info">
<InfoIcon size={14} />
<p>Execution is paused. What would you like to do?</p>
</div>
)}
</div>

<div className="control-buttons-group">
{isPaused && (
<>
<button
type="button"
onClick={onRewind}
className="control-button"
title="Rewind to a previous commit"
>
<HistoryIcon size={18} />
<span>Rewind</span>
</button>

<button
type="button"
onClick={onEditCode}
className="control-button"
title="Edit code or tests"
>
<CodeIcon size={18} />
<span>Edit Code</span>
</button>

<button
type="button"
onClick={onEditRequirements}
className="control-button"
title="Edit requirements"
>
<EditIcon size={18} />
<span>Edit Requirements</span>
</button>
</>
)}

{isFinished && (
<button
type="button"
onClick={onRewind}
className="control-button"
title="Rewind to a previous commit"
>
<HistoryIcon size={18} />
<span>Rewind</span>
</button>
)}
</div>
</div>
);
}

/**
* Confirmation dialog for important actions
*/
export function ConfirmDialog({
title,
message,
confirmText,
cancelText,
onConfirm,
onCancel,
isOpen,
type = "info",
}: {
title: string;
message: string;
confirmText: string;
cancelText: string;
onConfirm: () => void;
onCancel: () => void;
isOpen: boolean;
type?: "info" | "warning" | "danger";
}) {
if (!isOpen) return null;

return (
<div className="confirm-dialog-overlay" onClick={onCancel}>
<div className="confirm-dialog" onClick={(e) => e.stopPropagation()}>
<h3>{title}</h3>
<p>{message}</p>

<div className="confirm-actions">
<button type="button" onClick={onCancel} className="confirm-button secondary">
{cancelText}
</button>
<button type="button" onClick={onConfirm} className={`confirm-button ${type}`}>
{confirmText}
</button>
</div>
</div>
</div>
);
}

/**
* Simple toast notifications
*/
export function Toast({
message,
type = "info",
isVisible,
onClose,
}: {
message: string;
type?: "info" | "success" | "error" | "warning";
isVisible: boolean;
onClose: () => void;
}) {
useEffect(() => {
if (isVisible) {
const timer = setTimeout(onClose, 4000);
return () => clearTimeout(timer);
}
}, [isVisible, onClose]);

if (!isVisible) return null;

return (
<div className={`toast-notification ${type}`}>
{type === "success" && <CheckCircleIcon size={18} />}
{type === "error" && <PauseCircleIcon size={18} />}
{type === "warning" && <InfoIcon size={18} />}
{type === "info" && <InfoIcon size={18} />}
<span>{message}</span>
<button type="button" onClick={onClose} className="toast-close">
<XIcon size={14} />
</button>
</div>
);
}
Loading