Skip to content

Commit

Permalink
refactor: decouple workflow execution from Header.tsx
Browse files Browse the repository at this point in the history
  • Loading branch information
JeanKaddour committed Jan 8, 2025
1 parent 9b75e06 commit 91bfe11
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 306 deletions.
16 changes: 8 additions & 8 deletions backend/templates/joke_generator.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
"temperature": 0.7,
"top_p": 1
},
"system_message": "<p>You are a stand-up comedian who uses dark humor like Ricky Gervais or Jimmy Carr.</p><p></p><p>The user will provide you with a topic and audience, and you have to devise a short joke for that.</p><p></p><p>You can roast the person if a person is mentioned, it's only among friends.</p>",
"user_message": "<p>Your audience is: {{input_node.audience}}</p><p>The topic should be about {{input_node.topic}} </p>",
"system_message": "You are a stand-up comedian who uses dark humor like Ricky Gervais or Jimmy Carr.\n\nThe user will provide you with a topic and audience, and you have to devise a short joke for that.\n\nYou can roast the person if a person is mentioned, it's only among friends.",
"user_message": "Your audience is: {{input_node.audience}}\nThe topic should be about {{input_node.topic}}",
"few_shot_examples": null,
"samples": 10,
"rating_prompt": "Rate the following joke on a scale from 0 to 10, where 0 is poor and 10 is excellent. \nConsider factors such as surprise, relatability, and punchiness. Respond with only a number.",
Expand All @@ -65,8 +65,8 @@
"temperature": 0.7,
"top_p": 1
},
"system_message": "<p>Your goal is to refine a joke to make it more vulgar and concise. It's just among friends, so you can get roasty.</p><ul><li><p>Be mean</p></li><li><p>Have dark humour</p></li><li><p>Be very punchy</p></li></ul>",
"user_message": "<p>{{JokeDrafter.initial_joke}}</p>",
"system_message": "Your goal is to refine a joke to make it more vulgar and concise. It's just among friends, so you can get roasty.\n\n- Be mean\n- Have dark humour\n- Be very punchy",
"user_message": "{{JokeDrafter.initial_joke}}",
"few_shot_examples": null,
"samples": 3,
"rating_prompt": "Rate the following response on a scale from 0 to 10, where 0 is poor and 10 is excellent. Consider factors such as relevance, coherence, and helpfulness. Respond with only a number.",
Expand All @@ -93,8 +93,8 @@
"temperature": 0.7,
"top_p": 1
},
"system_message": "<p>You are a stand-up comedian who uses dark humor like Ricky Gervais or Jimmy Carr.</p><p></p><p>The user will provide you with a topic and audience, and you have to devise a short joke for that.</p><p></p><p>You can roast the person if a person is mentioned, it's only among friends.</p>",
"user_message": "<p>Your audience is: {{input_node.audience}}</p><p>The topic should be about {{input_node.topic}} </p>",
"system_message": "You are a stand-up comedian who uses dark humor like Ricky Gervais or Jimmy Carr.\n\nThe user will provide you with a topic and audience, and you have to devise a short joke for that.\n\nYou can roast the person if a person is mentioned, it's only among friends.",
"user_message": "Your audience is: {{input_node.audience}}\nThe topic should be about {{input_node.topic}}",
"few_shot_examples": null
},
"coordinates": {
Expand All @@ -120,8 +120,8 @@
"test_inputs": [
{
"id": 1732123761259,
"topic": "<p>A bald guy called Jean in his twenties</p>",
"audience": "<p>Young and Educated</p>"
"topic": "Emacs vs. Vim",
"audience": "Software Engineers"
}
]
},
Expand Down
215 changes: 39 additions & 176 deletions frontend/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useState, useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {
Input,
Expand All @@ -18,37 +18,29 @@ import {
} from '@nextui-org/react'
import { Icon } from '@iconify/react'
import SettingsCard from './modals/SettingsModal'
import { setProjectName, updateNodeDataOnly, resetRun } from '../store/flowSlice'
import { setProjectName } from '../store/flowSlice'
import RunModal from './modals/RunModal'
import { getRunStatus, startRun, getWorkflow } from '../utils/api'
import { Toaster, toast } from 'sonner'
import { getWorkflowRuns } from '../utils/api'
import { getWorkflow } from '../utils/api'
import { useRouter } from 'next/router'
import DeployModal from './modals/DeployModal'
import { formatDistanceStrict } from 'date-fns'
import { useHotkeys } from 'react-hotkeys-hook'
import store from '../store/store'
import { useWorkflowExecution } from '../hooks/useWorkflowExecution'
import { AlertState } from '../types/alert'

interface HeaderProps {
activePage: 'dashboard' | 'workflow' | 'evals' | 'trace'
associatedWorkflowId?: string
}


import { RootState } from '../store/store'
interface AlertState {
message: string
color: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger'
isVisible: boolean
}

const Header: React.FC<HeaderProps> = ({ activePage }) => {
const Header: React.FC<HeaderProps> = ({ activePage, associatedWorkflowId }) => {
const dispatch = useDispatch()
const nodes = useSelector((state: RootState) => state.flow.nodes)
const projectName = useSelector((state: RootState) => state.flow.projectName)
const [isRunning, setIsRunning] = useState<boolean>(false)
const [isDebugModalOpen, setIsDebugModalOpen] = useState<boolean>(false)
const [isDeployModalOpen, setIsDeployModalOpen] = useState<boolean>(false)
const [workflowRuns, setWorkflowRuns] = useState<any[]>([])
const [isHistoryOpen, setIsHistoryOpen] = useState<boolean>(false)
const workflowId = useSelector((state: RootState) => state.flow.workflowID)
const [alert, setAlert] = useState<AlertState>({
Expand All @@ -58,157 +50,45 @@ const Header: React.FC<HeaderProps> = ({ activePage }) => {
})
const testInputs = useSelector((state: RootState) => state.flow.testInputs)
const [selectedRow, setSelectedRow] = useState<number | null>(null)
const [isUpdatingStatus, setIsUpdatingStatus] = useState<boolean>(false)
const [completionPercentage, setCompletionPercentage] = useState<number>(0)

const router = useRouter()
const { id } = router.query
const isRun = id && id[0] == 'R'

let currentStatusInterval: NodeJS.Timeout | null = null

const fetchWorkflowRuns = async () => {
try {
const response = await getWorkflowRuns(workflowId)
setWorkflowRuns(response)
} catch (error) {
console.error('Error fetching workflow runs:', error)
}
const showAlert = (message: string, color: AlertState['color']) => {
setAlert({ message, color, isVisible: true })
setTimeout(() => setAlert((prev) => ({ ...prev, isVisible: false })), 3000)
}

useEffect(() => {
if (workflowId) {
fetchWorkflowRuns()
}
}, [workflowId])
const {
isRunning,
completionPercentage,
workflowRuns,
isUpdatingStatus,
executeWorkflow,
stopWorkflow,
updateRunStatuses,
} = useWorkflowExecution({ onAlert: showAlert })

useEffect(() => {
if (testInputs.length > 0 && !selectedRow) {
setSelectedRow(testInputs[0].id)
}
}, [testInputs])

const showAlert = (message: string, color: AlertState['color']) => {
setAlert({ message, color, isVisible: true })
setTimeout(() => setAlert((prev) => ({ ...prev, isVisible: false })), 3000)
}

const updateWorkflowStatus = async (runID: string): Promise<void> => {
let pollCount = 0
if (currentStatusInterval) {
clearInterval(currentStatusInterval)
}
currentStatusInterval = setInterval(async () => {
try {
const statusResponse = await getRunStatus(runID)
const tasks = statusResponse.tasks

if (statusResponse.percentage_complete !== undefined) {
setCompletionPercentage(statusResponse.percentage_complete)
}

if (statusResponse.status === 'FAILED' || tasks.some((task) => task.status === 'FAILED')) {
setIsRunning(false)
setCompletionPercentage(0)
clearInterval(currentStatusInterval)
showAlert('Workflow run failed.', 'danger')
return
}

if (tasks.length > 0) {
tasks.forEach((task) => {
const nodeId = task.node_id
let node = nodes.find((node) => node.id === nodeId)
if (!node) {
// find the node by title in nodeConfigs
const state = store.getState()
const correspondingNodeId = Object.keys(state.flow.nodeConfigs).find(
(key) => state.flow.nodeConfigs[key].title === nodeId
)
if (correspondingNodeId) {
node = nodes.find((node) => node.id === correspondingNodeId)
}
}
if (!node) {
return
}
const output_values = task.outputs || {}
const nodeTaskStatus = task.status
if (node) {
// Check if the task output or status is different from current node data
const isOutputDifferent = JSON.stringify(output_values) !== JSON.stringify(node.data?.run)
const isStatusDifferent = nodeTaskStatus !== node.data?.taskStatus

if (isOutputDifferent || isStatusDifferent) {
dispatch(
updateNodeDataOnly({
id: node.id,
data: {
run: { ...node.data.run, ...output_values },
taskStatus: nodeTaskStatus,
},
})
)
}
}
})
}

if (statusResponse.status !== 'RUNNING') {
setIsRunning(false)
setCompletionPercentage(0)
clearInterval(currentStatusInterval)
showAlert('Workflow run completed.', 'success')
}

pollCount += 1
} catch (error) {
console.error('Error fetching workflow status:', error)
clearInterval(currentStatusInterval)
}
}, 1000)
}

const workflowID = typeof window !== 'undefined' ? window.location.pathname.split('/').pop() : null

const executeWorkflow = async (inputValues: Record<string, any>): Promise<void> => {
if (!workflowID) return

try {
dispatch(resetRun())
showAlert('Starting workflow run...', 'default')
const result = await startRun(workflowId, inputValues, null, 'interactive')
setIsRunning(true)
fetchWorkflowRuns()
updateWorkflowStatus(result.id)
} catch (error) {
console.error('Error starting workflow run:', error)
showAlert('Error starting workflow run.', 'danger')
}
}

const handleRunWorkflow = async (): Promise<void> => {
setIsDebugModalOpen(true)
}

const handleStopWorkflow = (): void => {
setIsRunning(false)
setCompletionPercentage(0)
if (currentStatusInterval) {
clearInterval(currentStatusInterval)
}
showAlert('Workflow run stopped.', 'warning')
}

const handleProjectNameChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
dispatch(setProjectName(e.target.value))
}

const handleDownloadWorkflow = async (): Promise<void> => {
if (!workflowID) return
if (!workflowId) return

try {
const workflow = await getWorkflow(workflowID)
const workflow = await getWorkflow(workflowId)

const workflowDetails = {
name: workflow.name,
Expand Down Expand Up @@ -247,6 +127,11 @@ const Header: React.FC<HeaderProps> = ({ activePage }) => {
return `${baseUrl}/api/wf/${workflowId}/start_run/?run_type=non_blocking`
}

useEffect(() => {
if (isHistoryOpen) {
updateRunStatuses()
}
}, [isHistoryOpen])

useHotkeys(
['mod+enter'],
Expand Down Expand Up @@ -279,40 +164,6 @@ const Header: React.FC<HeaderProps> = ({ activePage }) => {
}
)

const updateRunStatuses = async () => {
if (!workflowId || !isHistoryOpen) return

setIsUpdatingStatus(true)
try {
// First fetch the latest workflow runs
const latestRuns = await getWorkflowRuns(workflowId)
setWorkflowRuns(latestRuns)

// Then update the status of running/pending runs
const updatedRuns = await Promise.all(
latestRuns.map(async (run) => {
if (run.status.toLowerCase() === 'running' || run.status.toLowerCase() === 'pending') {
const statusResponse = await getRunStatus(run.id)
return { ...run, status: statusResponse.status }
}
return run
})
)

setWorkflowRuns(updatedRuns)
} catch (error) {
console.error('Error updating run statuses:', error)
} finally {
setIsUpdatingStatus(false)
}
}

useEffect(() => {
if (isHistoryOpen) {
updateRunStatuses()
}
}, [isHistoryOpen])

return (
<>
{alert.isVisible && (
Expand Down Expand Up @@ -409,7 +260,7 @@ const Header: React.FC<HeaderProps> = ({ activePage }) => {
isIconOnly
radius="full"
variant="light"
onClick={handleStopWorkflow}
onClick={stopWorkflow}
>
<Icon
className="text-foreground/60"
Expand Down Expand Up @@ -478,6 +329,18 @@ const Header: React.FC<HeaderProps> = ({ activePage }) => {
</NavbarItem>
</NavbarContent>
)}
{activePage === 'trace' && associatedWorkflowId && (
<NavbarContent
className="ml-auto flex h-12 max-w-fit items-center gap-0 rounded-full p-0 lg:bg-content2 lg:px-1 lg:dark:bg-content1"
justify="end"
>
<NavbarItem>
<Link href={`/workflows/${associatedWorkflowId}`}>
<Button variant="light">Go To Workflow</Button>
</Link>
</NavbarItem>
</NavbarContent>
)}
<NavbarContent
className="ml-2 flex h-12 max-w-fit items-center gap-0 rounded-full p-0 lg:bg-content2 lg:px-1 lg:dark:bg-content1"
justify="end"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/modals/RunModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ const RunModal: React.FC<RunModalProps> = ({ isOpen, onOpenChange, onRun, onSave
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col gap-1">Select Test Input To Run or Save</ModalHeader>
<ModalHeader className="flex flex-col gap-1">Run Test Cases</ModalHeader>
<ModalBody>
<div className="overflow-x-auto">
<Table
Expand Down
Loading

0 comments on commit 91bfe11

Please sign in to comment.