feat: add Create Flow from Session button to session detail page (Task 22)

Adds sessionToFlow API client, exports from api/index.ts, and integrates
a prominent "Create Flow from Session" button on SessionDetailPage for
completed sessions. Generates a procedural flow via AI then navigates
to the procedural editor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-16 01:19:05 -04:00
parent df1a828e0c
commit d70f097d89
3 changed files with 65 additions and 2 deletions

View File

@@ -23,3 +23,4 @@ export { kbAcceleratorApi } from './kbAccelerator'
export { scriptsApi } from './scripts'
export { integrationsApi, sessionPsaApi } from './integrations'
export { sidebarApi } from './sidebar'
export { sessionToFlowApi } from './sessionToFlow'

View File

@@ -0,0 +1,16 @@
import { apiClient } from './client'
interface SessionToFlowResponse {
name: string
description: string
tree_type: string
tags: string[]
tree_structure: Record<string, unknown>
}
export const sessionToFlowApi = {
generate: async (sessionId: string): Promise<SessionToFlowResponse> => {
const { data } = await apiClient.post('/ai/session-to-flow', { session_id: sessionId })
return data
},
}

View File

@@ -1,9 +1,11 @@
import { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { Copy, Check, Eye, Save, Share2, CheckCircle2, AlertTriangle, ArrowUpRight, HelpCircle, Flag } from 'lucide-react'
import { Copy, Check, Eye, Save, Share2, CheckCircle2, AlertTriangle, ArrowUpRight, HelpCircle, Flag, Sparkles, Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/Button'
import { sessionsApi } from '@/api/sessions'
import { stepsApi } from '@/api/steps'
import { treesApi } from '@/api/trees'
import { sessionToFlowApi } from '@/api/sessionToFlow'
import { ExportPreviewModal } from '@/components/session/ExportPreviewModal'
import { SaveSessionAsTreeModal } from '@/components/session/SaveSessionAsTreeModal'
import { ShareSessionModal } from '@/components/session/ShareSessionModal'
@@ -19,6 +21,7 @@ import { hasRatedSession, markSessionRated } from '@/lib/sessionRatings'
import { Spinner } from '@/components/common/Spinner'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
import { getTreeEditorPath } from '@/lib/routing'
export function SessionDetailPage() {
const { id } = useParams<{ id: string }>()
@@ -46,6 +49,7 @@ export function SessionDetailPage() {
const [includeSummary, setIncludeSummary] = useState(false)
const [redactionMode, setRedactionMode] = useState<'none' | 'mask'>('none')
const [redactionSummary, setRedactionSummary] = useState<RedactionSummary | null>(null)
const [isGeneratingFlow, setIsGeneratingFlow] = useState(false)
useEffect(() => {
if (id) {
@@ -247,6 +251,28 @@ export function SessionDetailPage() {
}
}
const handleCreateFlowFromSession = async () => {
if (!session) return
setIsGeneratingFlow(true)
try {
const flowData = await sessionToFlowApi.generate(session.id)
const tree = await treesApi.create({
name: flowData.name,
description: flowData.description,
tree_type: flowData.tree_type,
tree_structure: flowData.tree_structure,
tags: flowData.tags,
})
toast.success('Flow generated! Opening editor...')
navigate(getTreeEditorPath(tree.id, 'procedural'))
} catch (err) {
console.error('Failed to generate flow from session:', err)
toast.error('Failed to generate flow. Please try again.')
} finally {
setIsGeneratingFlow(false)
}
}
const getDefaultTreeName = () => {
if (!session) return ''
const treeName = session.tree_snapshot?.name || 'Tree'
@@ -398,7 +424,27 @@ export function SessionDetailPage() {
</Button>
</div>
</div>
) : !session.completed_at ? (
) : null}
{/* Create Flow from Session — only for completed sessions */}
{session.completed_at && (
<div className="mb-4">
<Button
onClick={handleCreateFlowFromSession}
disabled={isGeneratingFlow}
className="bg-gradient-brand text-[#101114] font-semibold rounded-[10px] hover:opacity-90 active:scale-[0.97] disabled:opacity-60"
>
{isGeneratingFlow ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Sparkles className="h-4 w-4" />
)}
{isGeneratingFlow ? 'Generating Flow...' : 'Create Flow from Session'}
</Button>
</div>
)}
{!session.completed_at ? (
/* In-progress banner */
<div className="mb-6 flex items-center justify-between gap-4 rounded-xl border border-amber-500/20 bg-amber-500/5 px-5 py-4">
<div className="flex items-center gap-3">