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:
@@ -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'
|
||||
|
||||
16
frontend/src/api/sessionToFlow.ts
Normal file
16
frontend/src/api/sessionToFlow.ts
Normal 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
|
||||
},
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user