refactor: add typed content helpers for FlowPilot step card actions

Replace unsafe `as string` casts with type guard functions
(isScriptGenerationAction, isScriptBuilderAction, getActionType)
for compile-time safety on step content parsing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-03-21 21:02:12 -04:00
parent ed82c000f6
commit 5000e49cea
2 changed files with 36 additions and 8 deletions

View File

@@ -2,6 +2,7 @@ import { useState } from 'react'
import { MessageSquare, Zap, CheckCircle2, SkipForward, ChevronDown, ChevronUp } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { AISessionStepResponse, StepResponseRequest } from '@/types/ai-session'
import { isScriptGenerationAction, isScriptBuilderAction, getActionType } from '@/types/ai-session'
import { MarkdownContent } from '@/components/ui/MarkdownContent'
import { FlowPilotOptions } from './FlowPilotOptions'
import { InSessionScriptGenerator } from './InSessionScriptGenerator'
@@ -157,24 +158,24 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
)}
{/* In-session script generator */}
{!isResolutionSuggestion && (content.action_type as string) === 'script_generation' && sessionId && (
{!isResolutionSuggestion && isScriptGenerationAction(content) && sessionId && (
<InSessionScriptGenerator
templateId={(content.template_id as string) || ''}
preFilledParams={(content.pre_filled_params as Record<string, string>) || {}}
instructions={(content.instructions as string) || stepText}
templateId={content.template_id || ''}
preFilledParams={content.pre_filled_params || {}}
instructions={content.instructions || stepText}
sessionId={sessionId}
onRespond={onRespond}
/>
)}
{/* Script Builder handoff */}
{!isResolutionSuggestion && step.step_type === 'action' && (content.action_type as string) === 'open_script_builder' && (
{!isResolutionSuggestion && step.step_type === 'action' && isScriptBuilderAction(content) && (
<button
onClick={() => {
sessionStorage.setItem('scriptBuilderContext', JSON.stringify({
from_session: sessionId,
prompt: (content.script_prompt as string) || '',
language: (content.script_language as string) || 'powershell',
prompt: content.script_prompt || '',
language: content.script_language || 'powershell',
}))
window.open('/script-builder?from=flowpilot', '_blank')
onRespond({ action_result: { success: true, details: 'Opened Script Builder' } })
@@ -186,7 +187,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
)}
{/* Action step buttons */}
{!isResolutionSuggestion && step.step_type === 'action' && (content.action_type as string) !== 'script_generation' && (content.action_type as string) !== 'open_script_builder' && (
{!isResolutionSuggestion && step.step_type === 'action' && getActionType(content) !== 'script_generation' && getActionType(content) !== 'open_script_builder' && (
<div className="flex flex-col gap-2 sm:flex-row">
<button
onClick={() => handleActionComplete(true)}

View File

@@ -41,6 +41,33 @@ export interface AISessionStepResponse {
confidence_score: number
}
// ── Step content type guards ──
export interface ScriptGenerationContent {
action_type: 'script_generation'
template_id?: string
pre_filled_params?: Record<string, string>
instructions?: string
}
export interface ScriptBuilderContent {
action_type: 'open_script_builder'
script_prompt?: string
script_language?: string
}
export function isScriptGenerationAction(content: Record<string, unknown>): content is ScriptGenerationContent {
return content.action_type === 'script_generation'
}
export function isScriptBuilderAction(content: Record<string, unknown>): content is ScriptBuilderContent {
return content.action_type === 'open_script_builder'
}
export function getActionType(content: Record<string, unknown>): string | undefined {
return typeof content.action_type === 'string' ? content.action_type : undefined
}
export interface StepResponseRequest {
selected_option?: string | null
free_text_input?: string | null