From 031c1fb0fcca26fc3a897acd1f84e327858cc42a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 18 Jun 2026 17:05:56 +0530 Subject: [PATCH 1/2] fix: display llm robot create on reload or tab switch --- src/components/robot/RecordingsTable.tsx | 30 ++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index 3e204ac75..5d1b14841 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -7,7 +7,7 @@ import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TablePagination from '@mui/material/TablePagination'; import TableRow from '@mui/material/TableRow'; -import { memo, useCallback, useEffect, useMemo } from "react"; +import { memo, useCallback, useEffect, useMemo, useRef } from "react"; import { WorkflowFile } from "maxun-core"; import SearchIcon from '@mui/icons-material/Search'; import { @@ -300,7 +300,7 @@ export const RecordingsTable = ({ ...recording.recording_meta, content: recording.recording, parsedDate, - isLoading: recording.isLoading || false, + isLoading: recording.recording_meta?.status === 'creating' || recording.isLoading || false, isOptimistic: recording.isOptimistic || false }; } @@ -473,6 +473,32 @@ export const RecordingsTable = ({ } }, [rerenderRobots, setRerenderRobots, refetch]); + const pollingRef = useRef | null>(null); + + useEffect(() => { + const hasCreating = (recordingsData as any[]).some( + (r: any) => r?.recording_meta?.status === 'creating' + ); + + if (pollingRef.current) { + clearInterval(pollingRef.current); + pollingRef.current = null; + } + + if (hasCreating) { + pollingRef.current = setInterval(() => { + refetch(); + }, 8000); + } + + return () => { + if (pollingRef.current) { + clearInterval(pollingRef.current); + pollingRef.current = null; + } + }; + }, [recordingsData, refetch]); + function useDebounce(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = React.useState(value); From 38d66dd357e0da82d296d7175acafc6c93538368 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Sat, 20 Jun 2026 09:53:02 +0530 Subject: [PATCH 2/2] fix: create llm extract robot stub --- server/src/models/Robot.ts | 1 + server/src/routes/storage.ts | 112 +++++++++++++++++++++-------------- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/server/src/models/Robot.ts b/server/src/models/Robot.ts index 5e5970cf3..8ce470f3c 100644 --- a/server/src/models/Robot.ts +++ b/server/src/models/Robot.ts @@ -19,6 +19,7 @@ interface RobotMeta { promptLlmModel?: string; promptLlmApiKey?: string; promptLlmBaseUrl?: string; + status?: 'creating' | 'ready' | 'failed'; } interface RobotWorkflow { diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 8c21a6ce1..35a5e2c08 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -695,9 +695,6 @@ router.post('/recordings/llm', requireSignIn, async (req: AuthenticatedRequest, return res.status(409).json({ error: `A robot with the name "${finalRobotName}" already exists.` }); } - let workflowResult: any; - let finalUrl: string; - const llmConfig = { provider: llmProvider || 'ollama', model: llmModel, @@ -705,35 +702,10 @@ router.post('/recordings/llm', requireSignIn, async (req: AuthenticatedRequest, baseUrl: llmBaseUrl }; - if (url) { - logger.log('info', `Starting LLM workflow generation for provided URL: ${url}`); - workflowResult = await WorkflowEnricher.generateWorkflowFromPrompt(url, prompt, req.user.id, llmConfig); - finalUrl = workflowResult.url || url; - } else { - logger.log('info', `Starting LLM workflow generation with automatic URL detection for prompt: "${prompt}"`); - workflowResult = await WorkflowEnricher.generateWorkflowFromPromptWithSearch(prompt, req.user.id, llmConfig); - finalUrl = workflowResult.url || ''; - if (finalUrl) { - logger.log('info', `Auto-detected URL: ${finalUrl}`); - } - } - - if (finalUrl) { - finalUrl = normalizeRobotUrl(finalUrl); - } - - if (!workflowResult.success || !workflowResult.workflow) { - logger.log('error', `Failed to generate workflow: ${JSON.stringify(workflowResult.errors)}`); - return res.status(400).json({ - error: 'Failed to generate workflow from prompt', - details: workflowResult.errors - }); - } - const robotId = uuid(); const currentTimestamp = new Date().toISOString(); - const newRobot = await Robot.create({ + const stubRobot = await Robot.create({ id: uuid(), userId: req.user.id, recording_meta: { @@ -741,13 +713,14 @@ router.post('/recordings/llm', requireSignIn, async (req: AuthenticatedRequest, id: robotId, createdAt: currentTimestamp, updatedAt: currentTimestamp, - pairs: normalizeWorkflowUrls(workflowResult.workflow).length, + pairs: 0, params: [], type: 'extract', - url: finalUrl, + url: url || '', isLLM: true, + status: 'creating', }, - recording: { workflow: normalizeWorkflowUrls(workflowResult.workflow) }, + recording: { workflow: [] }, google_sheet_email: null, google_sheet_name: null, google_sheet_id: null, @@ -756,19 +729,70 @@ router.post('/recordings/llm', requireSignIn, async (req: AuthenticatedRequest, schedule: null, }); - logger.log('info', `LLM robot created with id: ${newRobot.id}`); - capture('maxun-oss-llm-robot-created', { - robot_meta: newRobot.recording_meta, - recording: newRobot.recording, - llm_provider: llmProvider || 'ollama', - prompt: prompt, - urlAutoDetected: !url, - }); + logger.log('info', `LLM robot stub created with id: ${stubRobot.id}, starting workflow generation`); - return res.status(201).json({ - message: 'LLM robot created successfully.', - robot: newRobot, - }); + let workflowResult: any; + let finalUrl: string; + + try { + if (url) { + logger.log('info', `Starting LLM workflow generation for provided URL: ${url}`); + workflowResult = await WorkflowEnricher.generateWorkflowFromPrompt(url, prompt, req.user.id, llmConfig); + finalUrl = workflowResult.url || url; + } else { + logger.log('info', `Starting LLM workflow generation with automatic URL detection for prompt: "${prompt}"`); + workflowResult = await WorkflowEnricher.generateWorkflowFromPromptWithSearch(prompt, req.user.id, llmConfig); + finalUrl = workflowResult.url || ''; + if (finalUrl) { + logger.log('info', `Auto-detected URL: ${finalUrl}`); + } + } + + if (finalUrl) { + finalUrl = normalizeRobotUrl(finalUrl); + } + + if (!workflowResult.success || !workflowResult.workflow) { + logger.log('error', `Failed to generate workflow: ${JSON.stringify(workflowResult.errors)}`); + await stubRobot.destroy(); + return res.status(400).json({ + error: 'Failed to generate workflow from prompt', + details: workflowResult.errors + }); + } + + const normalizedWorkflow = normalizeWorkflowUrls(workflowResult.workflow); + + await stubRobot.update({ + recording_meta: { + ...stubRobot.recording_meta, + pairs: normalizedWorkflow.length, + url: finalUrl, + updatedAt: new Date().toISOString(), + status: 'ready', + }, + recording: { workflow: normalizedWorkflow }, + }); + + await stubRobot.reload(); + + logger.log('info', `LLM robot ready with id: ${stubRobot.id}`); + capture('maxun-oss-llm-robot-created', { + robot_meta: stubRobot.recording_meta, + recording: stubRobot.recording, + llm_provider: llmProvider || 'ollama', + prompt: prompt, + urlAutoDetected: !url, + }); + + return res.status(201).json({ + message: 'LLM robot created successfully.', + robot: stubRobot, + }); + } catch (llmError) { + try { await stubRobot.destroy(); } catch (_) {} + throw llmError; + } } catch (error: any) { if (error.name === 'SequelizeUniqueConstraintError' || error.parent?.code === '23505') { return res.status(409).json({ error: 'A robot with this name already exists.' });