import { useCallback } from "react";
export type QuickActionType =
| "create_file"
| "create_task"
| "run_task"
| "advance_phase"
| "derive_tasks"
| "update_file";
export interface QuickAction {
type: QuickActionType;
label: string;
description?: string;
data?: Record<string, unknown>;
}
interface QuickActionButtonsProps {
actions: QuickAction[];
onAction: (action: QuickAction) => void;
loading?: boolean;
}
const ACTION_ICONS: Record<QuickActionType, string> = {
create_file: "[+]",
create_task: "[T]",
run_task: "[>]",
advance_phase: "[→]",
derive_tasks: "[≡]",
update_file: "[*]",
};
const ACTION_COLORS: Record<QuickActionType, string> = {
create_file: "border-blue-400/30 hover:border-blue-400/60 text-blue-400",
create_task: "border-green-400/30 hover:border-green-400/60 text-green-400",
run_task: "border-yellow-400/30 hover:border-yellow-400/60 text-yellow-400",
advance_phase: "border-purple-400/30 hover:border-purple-400/60 text-purple-400",
derive_tasks: "border-cyan-400/30 hover:border-cyan-400/60 text-cyan-400",
update_file: "border-orange-400/30 hover:border-orange-400/60 text-orange-400",
};
export function QuickActionButtons({
actions,
onAction,
loading = false,
}: QuickActionButtonsProps) {
const handleClick = useCallback(
(action: QuickAction) => {
if (!loading) {
onAction(action);
}
},
[onAction, loading]
);
if (actions.length === 0) return null;
return (
<div className="flex flex-wrap gap-2 mt-2">
{actions.map((action, index) => (
<button
key={`${action.type}-${index}`}
onClick={() => handleClick(action)}
disabled={loading}
className={`
flex items-center gap-1.5 px-2 py-1
font-mono text-[10px] uppercase
border transition-colors
disabled:opacity-50 disabled:cursor-not-allowed
${ACTION_COLORS[action.type]}
`}
title={action.description}
>
<span>{ACTION_ICONS[action.type]}</span>
<span>{action.label}</span>
</button>
))}
</div>
);
}
/**
* Parse tool call results to extract suggested quick actions.
* This is used by ContractCliInput to detect actionable results.
*/
export function parseActionsFromToolCalls(
toolCalls: { name: string; success: boolean; message: string }[]
): QuickAction[] {
const actions: QuickAction[] = [];
for (const tc of toolCalls) {
if (!tc.success) continue;
switch (tc.name) {
case "derive_tasks_from_file":
// When tasks are parsed, offer to create them
if (tc.message.includes("task") || tc.message.includes("Found")) {
actions.push({
type: "derive_tasks",
label: "Review & Create Tasks",
description: "Review parsed tasks and create them with chaining",
});
}
break;
case "process_task_completion":
// Check for suggested actions in the result
if (tc.message.includes("next task")) {
actions.push({
type: "run_task",
label: "Run Next Task",
description: "Continue with the next chained task",
});
}
if (tc.message.includes("advance") || tc.message.includes("phase")) {
actions.push({
type: "advance_phase",
label: "Advance Phase",
description: "Move to the next contract phase",
});
}
break;
case "get_phase_checklist":
// When checklist shows missing items, offer to create them
if (tc.message.includes("missing") || tc.message.includes("not created")) {
actions.push({
type: "create_file",
label: "Create Missing Files",
description: "Create files from recommended templates",
});
}
break;
case "advance_phase":
// After phase transition, suggest creating files
actions.push({
type: "create_file",
label: "Create Phase Files",
description: "Create recommended files for this phase",
});
break;
}
}
return actions;
}
/**
* Parse LLM response text to detect suggested actions.
* Used as a fallback when structured action data isn't available.
*/
export function parseActionsFromText(text: string): QuickAction[] {
const actions: QuickAction[] = [];
const lower = text.toLowerCase();
// Detect file creation suggestions
if (
lower.includes("create a file") ||
lower.includes("create the file") ||
lower.includes("should i create")
) {
actions.push({
type: "create_file",
label: "Create File",
description: "Create the suggested file",
});
}
// Detect task creation suggestions
if (
lower.includes("create tasks") ||
lower.includes("create these tasks") ||
lower.includes("create chained tasks")
) {
actions.push({
type: "create_task",
label: "Create Tasks",
description: "Create the suggested tasks",
});
}
// Detect phase advancement suggestions
if (
lower.includes("advance to") ||
lower.includes("ready to move to") ||
lower.includes("transition to")
) {
const phases = ["specify", "plan", "execute", "review"];
for (const phase of phases) {
if (lower.includes(phase)) {
actions.push({
type: "advance_phase",
label: `Advance to ${phase.charAt(0).toUpperCase() + phase.slice(1)}`,
description: `Move to the ${phase} phase`,
data: { phase },
});
break;
}
}
}
// Detect run task suggestions
if (
lower.includes("run the task") ||
lower.includes("start the task") ||
lower.includes("run task")
) {
actions.push({
type: "run_task",
label: "Run Task",
description: "Start the suggested task",
});
}
return actions;
}