summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/contracts/QuickActionButtons.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/components/contracts/QuickActionButtons.tsx')
-rw-r--r--makima/frontend/src/components/contracts/QuickActionButtons.tsx217
1 files changed, 217 insertions, 0 deletions
diff --git a/makima/frontend/src/components/contracts/QuickActionButtons.tsx b/makima/frontend/src/components/contracts/QuickActionButtons.tsx
new file mode 100644
index 0000000..4dbb90c
--- /dev/null
+++ b/makima/frontend/src/components/contracts/QuickActionButtons.tsx
@@ -0,0 +1,217 @@
+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;
+}