summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/contracts/QuickActionButtons.tsx
blob: 4dbb90c121b80661f981ad430986c61e71c6b91e (plain) (tree)
























































































































































































































                                                                                   
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;
}