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 { scriptsApi } from './scripts'
|
||||||
export { integrationsApi, sessionPsaApi } from './integrations'
|
export { integrationsApi, sessionPsaApi } from './integrations'
|
||||||
export { sidebarApi } from './sidebar'
|
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 { useEffect, useState } from 'react'
|
||||||
import { useParams, useNavigate } from 'react-router-dom'
|
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 { Button } from '@/components/ui/Button'
|
||||||
import { sessionsApi } from '@/api/sessions'
|
import { sessionsApi } from '@/api/sessions'
|
||||||
import { stepsApi } from '@/api/steps'
|
import { stepsApi } from '@/api/steps'
|
||||||
|
import { treesApi } from '@/api/trees'
|
||||||
|
import { sessionToFlowApi } from '@/api/sessionToFlow'
|
||||||
import { ExportPreviewModal } from '@/components/session/ExportPreviewModal'
|
import { ExportPreviewModal } from '@/components/session/ExportPreviewModal'
|
||||||
import { SaveSessionAsTreeModal } from '@/components/session/SaveSessionAsTreeModal'
|
import { SaveSessionAsTreeModal } from '@/components/session/SaveSessionAsTreeModal'
|
||||||
import { ShareSessionModal } from '@/components/session/ShareSessionModal'
|
import { ShareSessionModal } from '@/components/session/ShareSessionModal'
|
||||||
@@ -19,6 +21,7 @@ import { hasRatedSession, markSessionRated } from '@/lib/sessionRatings'
|
|||||||
import { Spinner } from '@/components/common/Spinner'
|
import { Spinner } from '@/components/common/Spinner'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { toast } from '@/lib/toast'
|
import { toast } from '@/lib/toast'
|
||||||
|
import { getTreeEditorPath } from '@/lib/routing'
|
||||||
|
|
||||||
export function SessionDetailPage() {
|
export function SessionDetailPage() {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
@@ -46,6 +49,7 @@ export function SessionDetailPage() {
|
|||||||
const [includeSummary, setIncludeSummary] = useState(false)
|
const [includeSummary, setIncludeSummary] = useState(false)
|
||||||
const [redactionMode, setRedactionMode] = useState<'none' | 'mask'>('none')
|
const [redactionMode, setRedactionMode] = useState<'none' | 'mask'>('none')
|
||||||
const [redactionSummary, setRedactionSummary] = useState<RedactionSummary | null>(null)
|
const [redactionSummary, setRedactionSummary] = useState<RedactionSummary | null>(null)
|
||||||
|
const [isGeneratingFlow, setIsGeneratingFlow] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
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 = () => {
|
const getDefaultTreeName = () => {
|
||||||
if (!session) return ''
|
if (!session) return ''
|
||||||
const treeName = session.tree_snapshot?.name || 'Tree'
|
const treeName = session.tree_snapshot?.name || 'Tree'
|
||||||
@@ -398,7 +424,27 @@ export function SessionDetailPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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 */
|
/* 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="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">
|
<div className="flex items-center gap-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user