"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { IconPlus } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { Checkbox } from "@kandev/ui/checkbox"; import { Label } from "@kandev/ui/label"; import { useAvailableAgents } from "@/hooks/domains/settings/use-available-agents"; import { useHealthyAgentProfiles } from "@/hooks/domains/settings/use-healthy-agent-profiles"; import type { WorkflowStep } from "@/lib/types/http"; import type { ConfigureSessionOperation, ConfigureSessionRule } from "@/lib/types/workflow-actions"; import { analyzeSessionConfigCarryForward, type SessionConfigCarryWarning, } from "@/lib/workflows/session-config-carry-analysis"; import { HelpTip } from "./workflow-pipeline-editor-helpers"; import { isWorkflowStepValueDirty } from "./workflow-dirty-state"; import { SessionConfigCarryWarningPanel } from "./workflow-session-config-carry-warning"; import { SessionConfigRuleCard } from "./workflow-session-config-rule-card"; import { buildAgentChoices, configureSessionAction, defaultConfigureSessionRule, defaultModelForAgent, withoutConfigureSession, withConfigureSessionRules, type AgentChoice, } from "./workflow-session-config-shared"; type SessionConfigEditorProps = { step: WorkflowStep; savedStep?: WorkflowStep; steps: WorkflowStep[]; onUpdate: (updates: Partial) => void; readOnly: boolean; onResolutionPendingChange?: (pending: boolean) => void; }; export function SessionConfigToggle({ step, savedStep, onUpdate, readOnly, }: { step: WorkflowStep; savedStep?: WorkflowStep; onUpdate: (updates: Partial) => void; readOnly: boolean; }) { const { t } = useTranslation(); const profiles = useHealthyAgentProfiles(step.agent_profile_id); const availableAgents = useAvailableAgents(); const enabled = !!configureSessionAction(step); const isDirty = isWorkflowStepValueDirty(step, savedStep, (item) => JSON.stringify(configureSessionAction(item)?.config?.rules ?? []), ); const disabled = readOnly || !!step.agent_profile_id || (!enabled && profiles.length === 0); const setEnabled = (nextEnabled: boolean) => { if (readOnly) return; if (!nextEnabled) { onUpdate(withoutConfigureSession(step)); return; } const rule = defaultConfigureSessionRule(profiles, availableAgents.items); if (!rule) return; onUpdate(withConfigureSessionRules(step, [rule])); }; return (
setEnabled(checked === true)} disabled={disabled} data-settings-dirty={isDirty} data-testid={`${step.id}-override-original-session`} />
); } export function SessionConfigEditor({ step, savedStep, steps, onUpdate, readOnly, onResolutionPendingChange, }: SessionConfigEditorProps) { const profiles = useHealthyAgentProfiles(step.agent_profile_id); const availableAgents = useAvailableAgents(); const action = configureSessionAction(step); const rules = action?.config?.rules ?? []; const warnings = analyzeSessionConfigCarryForward(steps, step.id); const choices = useMemo(() => buildAgentChoices(profiles), [profiles]); const availableChoices = choices.filter( (choice) => !rules.some((rule) => rule.agent_name === choice.name), ); const isDirty = isWorkflowStepValueDirty(step, savedStep, (item) => JSON.stringify(configureSessionAction(item)?.config?.rules ?? []), ); const [pendingRuleKeys, setPendingRuleKeys] = useState>(() => new Set()); const updateRuleResolutionPending = useCallback((key: string, pending: boolean) => { setPendingRuleKeys((current) => { const next = new Set(current); if (pending) next.add(key); else next.delete(key); if (next.size === current.size && [...next].every((item) => current.has(item))) { return current; } return next; }); }, []); useEffect(() => { onResolutionPendingChange?.(pendingRuleKeys.size > 0); }, [onResolutionPendingChange, pendingRuleKeys]); const updateRules = (nextRules: ConfigureSessionRule[]) => { onUpdate(withConfigureSessionRules(step, nextRules)); }; const addRule = (agentName?: string, operation: ConfigureSessionOperation = "set") => { const selectedAgent = availableChoices.find((choice) => choice.name === agentName)?.name ?? availableChoices[0]?.name ?? ""; if (!selectedAgent) return; updateRules([ ...rules, { agent_name: selectedAgent, operation, ...(operation === "set" ? { model: defaultModelForAgent(selectedAgent, availableAgents.items) } : {}), }, ]); }; const createCarryRule = ( warning: SessionConfigCarryWarning, operation: ConfigureSessionOperation, ) => { if (rules.some((rule) => rule.agent_name === warning.agentName)) return; addRule(warning.agentName, operation); }; if (!action && warnings.length === 0) return null; return (
{action && ( addRule()} canAddRule={availableChoices.length > 0} /> )}
); } function SessionConfigBody({ step, action, rules, warnings, choices, availableAgents, readOnly, fixedProfile, onChooseCarryRule, onUpdateRules, onResolutionPendingChange, }: { step: WorkflowStep; action: ReturnType; rules: ConfigureSessionRule[]; warnings: SessionConfigCarryWarning[]; choices: AgentChoice[]; availableAgents: ReturnType["items"]; readOnly: boolean; fixedProfile: boolean; onChooseCarryRule: ( warning: SessionConfigCarryWarning, operation: ConfigureSessionOperation, ) => void; onUpdateRules: (rules: ConfigureSessionRule[]) => void; onResolutionPendingChange: (key: string, pending: boolean) => void; }) { const { t } = useTranslation(); return ( <> {step.agent_profile_id && action && (

{t("workflows:sessionConfigFixedProfileWarning")}

)} {!action && warnings.length > 0 && !step.agent_profile_id && ( )} {action && ( )} ); } function SessionConfigRuleList({ rules, warnings, choices, availableAgents, readOnly, fixedProfile, onChooseCarryRule, onUpdateRules, onResolutionPendingChange, }: { rules: ConfigureSessionRule[]; warnings: SessionConfigCarryWarning[]; choices: AgentChoice[]; availableAgents: ReturnType["items"]; readOnly: boolean; fixedProfile: boolean; onChooseCarryRule: ( warning: SessionConfigCarryWarning, operation: ConfigureSessionOperation, ) => void; onUpdateRules: (rules: ConfigureSessionRule[]) => void; onResolutionPendingChange: (key: string, pending: boolean) => void; }) { const { t } = useTranslation(); return (
{warnings.length > 0 && ( )} {rules.map((rule, index) => ( choice.name === rule.agent_name || !rules.some( (candidate, candidateIndex) => candidateIndex !== index && candidate.agent_name === choice.name, ), )} availableAgents={availableAgents} readOnly={readOnly} onResolutionPendingChange={onResolutionPendingChange} onChange={(nextRule) => { if ( nextRule.agent_name !== rule.agent_name && rules.some( (candidate, candidateIndex) => candidateIndex !== index && candidate.agent_name === nextRule.agent_name, ) ) { return; } const nextRules = rules.slice(); nextRules[index] = nextRule; onUpdateRules(nextRules); }} onRemove={() => onUpdateRules(rules.filter((_, candidateIndex) => candidateIndex !== index)) } /> ))} {rules.length === 0 && (

{t("workflows:sessionConfigEmptyRules")}

)}
); } function SessionConfigOptionsHeader({ step, isDirty, readOnly, onAddRule, canAddRule, }: { step: WorkflowStep; isDirty: boolean; readOnly: boolean; onAddRule: () => void; canAddRule: boolean; }) { const { t } = useTranslation(); const disabled = readOnly || !!step.agent_profile_id; return (

{t("workflows:configureOriginalSessionOptionsDescription")}

{!readOnly && !step.agent_profile_id && ( )}
); }