From d70f097d899d871fd860337405fa4758ae20e9d3 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Mon, 16 Mar 2026 01:19:05 -0400 Subject: [PATCH] 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) --- frontend/src/api/index.ts | 1 + frontend/src/api/sessionToFlow.ts | 16 ++++++++ frontend/src/pages/SessionDetailPage.tsx | 50 +++++++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 frontend/src/api/sessionToFlow.ts diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 628cf68b..80dd3bb8 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -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' diff --git a/frontend/src/api/sessionToFlow.ts b/frontend/src/api/sessionToFlow.ts new file mode 100644 index 00000000..e4476ed7 --- /dev/null +++ b/frontend/src/api/sessionToFlow.ts @@ -0,0 +1,16 @@ +import { apiClient } from './client' + +interface SessionToFlowResponse { + name: string + description: string + tree_type: string + tags: string[] + tree_structure: Record +} + +export const sessionToFlowApi = { + generate: async (sessionId: string): Promise => { + const { data } = await apiClient.post('/ai/session-to-flow', { session_id: sessionId }) + return data + }, +} diff --git a/frontend/src/pages/SessionDetailPage.tsx b/frontend/src/pages/SessionDetailPage.tsx index 0645806d..54932112 100644 --- a/frontend/src/pages/SessionDetailPage.tsx +++ b/frontend/src/pages/SessionDetailPage.tsx @@ -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(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() { - ) : !session.completed_at ? ( + ) : null} + + {/* Create Flow from Session — only for completed sessions */} + {session.completed_at && ( +
+ +
+ )} + + {!session.completed_at ? ( /* In-progress banner */