Skip to content

Commit bbd669f

Browse files
committed
feat: в лайв дебаге появилась функция которая дает опробовать ноды без запуска бота
1 parent 28387a9 commit bbd669f

16 files changed

Lines changed: 1627 additions & 231 deletions

backend/src/api/routes/eventGraphs.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,5 +458,151 @@ router.post('/:graphId/duplicate',
458458
}
459459
);
460460

461+
router.post('/test-run/:graphId',
462+
authorize('management:edit'),
463+
async (req, res) => {
464+
const { botId, graphId } = req.params;
465+
const { eventType = 'chat', eventArgs = {} } = req.body || {};
466+
467+
try {
468+
const eventGraph = await prisma.eventGraph.findFirst({
469+
where: { id: parseInt(graphId), botId: parseInt(botId) }
470+
});
471+
if (!eventGraph) {
472+
return res.status(404).json({ error: 'Event graph not found' });
473+
}
474+
if (!eventGraph.graphJson) {
475+
return res.status(400).json({ error: 'Graph has no data' });
476+
}
477+
478+
const parsed = JSON.parse(eventGraph.graphJson);
479+
const graph = {
480+
id: eventGraph.id,
481+
name: eventGraph.name,
482+
nodes: parsed.nodes || [],
483+
connections: parsed.connections || [],
484+
variables: parsed.variables || []
485+
};
486+
487+
const nodeRegistry = require('../../core/NodeRegistry');
488+
const GraphExecutionEngine = require('../../core/GraphExecutionEngine');
489+
const { getGlobalDebugManager } = require('../../core/services/DebugSessionManager');
490+
const { buildTestContext } = require('../../core/services/TestModeContext');
491+
492+
const debugManager = getGlobalDebugManager();
493+
const debugState = debugManager.getOrCreate(parseInt(botId), parseInt(graphId));
494+
debugState.enableTestMode({ botId: parseInt(botId), graphId: parseInt(graphId), eventType, eventArgs });
495+
496+
const engine = new GraphExecutionEngine(nodeRegistry, null);
497+
498+
const context = buildTestContext({
499+
botId: parseInt(botId),
500+
graphId: parseInt(graphId),
501+
eventArgs
502+
});
503+
504+
engine.execute(graph, context, eventType).catch(err => {
505+
if (!err) return;
506+
if (err.name === 'BreakLoopSignal') return;
507+
if (err.message === 'Execution stopped by debugger') return;
508+
logger.error('[Test Run] Execution error:', err.message);
509+
});
510+
511+
res.json({ success: true, message: 'Test run started in step mode' });
512+
} catch (error) {
513+
logger.error('[Test Run] Failed:', error);
514+
res.status(500).json({ error: error.message || 'Test run failed' });
515+
}
516+
}
517+
);
518+
519+
router.post('/run-node/:graphId/:nodeId',
520+
authorize('management:edit'),
521+
async (req, res) => {
522+
const { botId, graphId, nodeId } = req.params;
523+
const { inputs = {}, variables = {} } = req.body || {};
524+
525+
try {
526+
const eventGraph = await prisma.eventGraph.findFirst({
527+
where: { id: parseInt(graphId), botId: parseInt(botId) }
528+
});
529+
if (!eventGraph) {
530+
return res.status(404).json({ error: 'Event graph not found' });
531+
}
532+
const parsed = JSON.parse(eventGraph.graphJson || '{}');
533+
const node = (parsed.nodes || []).find(n => n.id === nodeId);
534+
if (!node) {
535+
return res.status(404).json({ error: 'Node not found in graph' });
536+
}
537+
538+
const nodeRegistry = require('../../core/NodeRegistry');
539+
const nodeConfig = nodeRegistry.getNodeConfig(node.type);
540+
if (!nodeConfig) {
541+
return res.status(400).json({ error: `Unknown node type: ${node.type}` });
542+
}
543+
544+
const GraphExecutionEngine = require('../../core/GraphExecutionEngine');
545+
const { buildTestContext } = require('../../core/services/TestModeContext');
546+
const engine = new GraphExecutionEngine(nodeRegistry, null);
547+
548+
engine.activeGraph = {
549+
id: parseInt(graphId),
550+
nodes: parsed.nodes || [],
551+
connections: parsed.connections || [],
552+
variables: parsed.variables || []
553+
};
554+
engine.context = {
555+
...buildTestContext({
556+
botId: parseInt(botId),
557+
graphId: parseInt(graphId),
558+
eventArgs: inputs
559+
}),
560+
variables
561+
};
562+
563+
for (const [pin, value] of Object.entries(inputs)) {
564+
engine.memo.set(`__forced:${node.id}:${pin}`, value);
565+
}
566+
const originalResolve = engine.resolvePinValue.bind(engine);
567+
engine.resolvePinValue = async function(targetNode, pinName) {
568+
if (targetNode.id === node.id && engine.memo.has(`__forced:${node.id}:${pinName}`)) {
569+
return engine.memo.get(`__forced:${node.id}:${pinName}`);
570+
}
571+
return originalResolve(targetNode, pinName);
572+
};
573+
574+
const startedAt = Date.now();
575+
const helpers = {
576+
resolvePinValue: engine.resolvePinValue.bind(engine),
577+
traverse: async () => {},
578+
memo: engine.memo,
579+
clearLoopBodyMemo: () => {}
580+
};
581+
582+
let outputs = {};
583+
let errorMsg = null;
584+
try {
585+
await nodeConfig.executor.call(engine, node, engine.context, helpers);
586+
outputs = await engine._captureNodeOutputs(node);
587+
} catch (e) {
588+
errorMsg = e.message;
589+
}
590+
591+
res.json({
592+
success: !errorMsg,
593+
nodeId: node.id,
594+
nodeType: node.type,
595+
executionTime: Date.now() - startedAt,
596+
outputs,
597+
error: errorMsg,
598+
variables: engine.context.variables
599+
});
600+
} catch (error) {
601+
logger.error('[Run Node] Failed:', error);
602+
res.status(500).json({ error: error.message || 'Run node failed' });
603+
}
604+
}
605+
);
606+
461607
module.exports = router;
462608

0 commit comments

Comments
 (0)