修复流程编排界面点击执行无效的问题

This commit is contained in:
jingrow 2026-03-16 04:09:29 +08:00
parent ffa30beaa8
commit a392f71dd7
5 changed files with 98 additions and 23 deletions

View File

@ -24,7 +24,14 @@ export class FlowExecutor {
* @returns {Promise<Object>} 执行结果
*/
async executeFlow(nodes, edges, initialData = {}) {
console.group('[FlowExecutor] executeFlow START');
console.log('Nodes count:', nodes?.length);
console.log('Edges count:', edges?.length);
console.log('Initial data:', initialData);
if (this.isExecuting) {
console.error('[FlowExecutor] Flow already executing');
console.groupEnd();
throw new Error('流程正在执行中,请等待完成');
}
@ -37,12 +44,20 @@ export class FlowExecutor {
const executionNodes = JSON.parse(JSON.stringify(nodes));
const executionEdges = JSON.parse(JSON.stringify(edges));
console.log('Execution nodes:', executionNodes.map(n => ({ id: n.id, type: n.type })));
console.log('Execution edges:', executionEdges);
// 预构建执行图(一次性构建,避免重复计算)
this.buildExecutionGraph(executionNodes, executionEdges);
console.log('Execution order:', this.executionOrder);
// 执行流程
const result = await this.executeGraph();
console.log('[FlowExecutor] executeFlow SUCCESS');
console.log('Final results:', result);
console.groupEnd();
return {
success: true,
result: result,
@ -50,6 +65,9 @@ export class FlowExecutor {
context: this.executionContext
};
} catch (error) {
console.error('[FlowExecutor] executeFlow FAILED:', error);
console.error('Error stack:', error.stack);
console.groupEnd();
return {
success: false,
error: error.message,
@ -172,6 +190,9 @@ export class FlowExecutor {
* @returns {Promise<Object>} 执行结果
*/
async executeGraph() {
console.log('[FlowExecutor] executeGraph START');
console.log('Execution order:', this.executionOrder);
const context = {
node_results: {},
flow_id: this.executionContext.agent_name
@ -183,14 +204,19 @@ export class FlowExecutor {
// 按拓扑排序执行节点
for (const nodeId of this.executionOrder) {
const node = this.nodes.get(nodeId);
console.log(`[FlowExecutor] Processing node: ${nodeId} (${node?.type})`);
// 检查节点是否应该被执行O(1)时间复杂度)
if (!this.shouldExecuteNode(node, context, executedNodes)) {
console.log(`[FlowExecutor] Skipping node ${nodeId} (shouldExecuteNode=false)`);
continue;
}
console.log(`[FlowExecutor] Executing node ${nodeId}...`);
// 执行节点
const result = await this.executeNode(node, context);
console.log(`[FlowExecutor] Node ${nodeId} result:`, result);
results[nodeId] = result;
context.node_results[nodeId] = result;
executedNodes.add(nodeId);
@ -199,6 +225,7 @@ export class FlowExecutor {
this.recordExecutionHistory(node, result);
}
console.log('[FlowExecutor] executeGraph COMPLETE, results:', results);
return results;
}
@ -267,23 +294,44 @@ export class FlowExecutor {
edges: this.edges
};
const response = await fetch(`/api/action/jingrow.ai.nodes.${node.type}.${node.type}.execute`, {
// Debug: log request details
console.group(`[FlowExecutor] Executing node: ${node.type} (${node.id})`);
console.log('Node data:', JSON.parse(JSON.stringify(node)));
console.log('Node inputs:', nodeInputs);
console.log('Node config:', config);
console.log('Context:', JSON.parse(JSON.stringify(context)));
console.log('Request URL:', `/jingrow/nodes/${node.type}/execute`);
console.log('CSRF Token:', window.jingrow?.csrf_token);
const requestBody = JSON.stringify({
context,
inputs: nodeInputs,
config
});
console.log('Request body size:', requestBody.length, 'bytes');
const response = await fetch(`/jingrow/nodes/${node.type}/execute`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Jingrow-CSRF-Token': window.jingrow?.csrf_token
},
body: JSON.stringify({
context,
inputs: nodeInputs,
config
})
body: requestBody
});
// Debug: log response details
console.log('Response status:', response.status, response.statusText);
console.log('Response headers:', Object.fromEntries(response.headers.entries()));
const resJson = await response.json();
console.log('Response JSON:', resJson);
console.groupEnd();
if (!resJson.message || resJson.message.success === false) {
throw new Error(resJson.message?.error || '节点执行失败');
const errorMsg = resJson.message?.error || resJson.error || '节点执行失败';
console.error('[FlowExecutor] Node execution failed:', errorMsg);
console.error('[FlowExecutor] Full response:', resJson);
throw new Error(errorMsg);
}
// 更新节点状态
@ -294,6 +342,9 @@ export class FlowExecutor {
return resJson.message;
} catch (error) {
console.groupEnd();
console.error('[FlowExecutor] Node execution error:', error);
console.error('[FlowExecutor] Error stack:', error.stack);
// 处理执行失败
node.status = 'failed';
node.error = error.message;

View File

@ -108,8 +108,8 @@ export default defineConfig(({ mode, command }) => {
allow: [appsDir]
},
proxy: {
// jlocal 只代理 API 请求,静态资源由 vite/Caddy 直接托管
'^/api': {
// Proxy both /api and /jingrow routes to backend
'^/(api|jingrow)': {
target: BACKEND_URL,
changeOrigin: true,
secure: false,

View File

@ -133,6 +133,14 @@ def get_single(pagetype: str):
return {'success': True, 'config': data}
def get_single_value(pagetype: str, fieldname: str):
"""获取 single 类型 pagetype 的单个字段值"""
result = get_single(pagetype)
if result.get('success') and result.get('config'):
return result['config'].get(fieldname)
return None
def get_module_app(pagetype: str):
"""获取指定 pagetype 的模块应用信息,返回后端适配器的原始结果结构。"""
return get_page_instance(pagetype).get_module_app()

View File

@ -24,7 +24,8 @@ async def execute_node(node_type: str, request: Request, request_data: Dict[str,
executor = NodeExecutor()
result = await executor.execute_node(node_type, flow_id, context, inputs, config, session_cookie)
return result
# 统一响应格式,与 SaaS 版保持一致
return {"message": result}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@ -4,10 +4,22 @@ Jingrow Cloud API 调用工具
"""
import requests
from jingrow.config import Config
from functools import lru_cache
from jingrow.utils.auth import get_jingrow_cloud_api_headers, get_jingrow_cloud_api_url
@lru_cache(maxsize=1)
def _get_cached_ai_settings():
"""缓存 AI Settings 配置(含密码字段解密),避免重复请求"""
from jingrow.utils.jingrow_api import get_ai_settings_from_jingrow
return get_ai_settings_from_jingrow() or {}
def clear_ai_settings_cache():
"""清除 AI Settings 缓存,配置更新后调用"""
_get_cached_ai_settings.cache_clear()
def call_jingrow_model(prompt: str,
image_urls: list = None,
ai_temperature: float = None,
@ -86,11 +98,12 @@ def call_chatgpt_model(prompt: str,
model: str = None):
"""调用 ChatGPT 模型"""
try:
api_url = getattr(Config, 'CHATGPT_API_URL', None) or "https://api.openai.com/v1/chat/completions"
api_key = getattr(Config, 'CHATGPT_API_KEY', None)
model_name = model or getattr(Config, 'CHATGPT_API_MODEL', None) or "gpt-4o"
ai_settings = _get_cached_ai_settings()
api_url = ai_settings.get('chatgpt_api_url') or "https://api.openai.com/v1/chat/completions"
api_key = ai_settings.get('chatgpt_api_key')
model_name = model or ai_settings.get('chatgpt_api_model') or "gpt-4o"
if not api_key:
return {"success": False, "error": "ChatGPT API 未配置"}
return {"success": False, "error": "ChatGPT API 未配置,请在 Ai Settings 中设置 chatgpt_api_key"}
content = [{"type": "text", "text": prompt}]
if image_urls:
for img in image_urls:
@ -132,11 +145,12 @@ def call_deepseek_model(prompt: str,
model: str = None):
"""调用 DeepSeek 模型"""
try:
api_url = getattr(Config, 'DEEPSEEK_API_URL', None)
api_key = getattr(Config, 'DEEPSEEK_API_KEY', None)
model_name = model or getattr(Config, 'DEEPSEEK_API_MODEL', None) or "deepseek-chat"
ai_settings = _get_cached_ai_settings()
api_url = ai_settings.get('deepseek_api_url')
api_key = ai_settings.get('deepseek_api_key')
model_name = model or ai_settings.get('deepseek_api_model') or "deepseek-chat"
if not api_url or not api_key:
return {"success": False, "error": "DeepSeek API 未配置"}
return {"success": False, "error": "DeepSeek API 未配置,请在 Ai Settings 中设置 deepseek_api_url 和 deepseek_api_key"}
content = [{"type": "text", "text": prompt}]
if image_urls:
for img in image_urls:
@ -178,11 +192,12 @@ def call_doubao_model(prompt: str,
model: str = None):
"""调用豆包模型"""
try:
api_url = getattr(Config, 'DOUBAO_API_URL', None) or getattr(Config, 'DOUBAO_API_URL'.upper(), None)
api_key = getattr(Config, 'DOUBAO_API_KEY', None) or getattr(Config, 'DOUBAO_API_KEY'.upper(), None)
model_name = model or getattr(Config, 'DOUBAO_API_MODEL', None) or "doubao-pro-32k-241215"
ai_settings = _get_cached_ai_settings()
api_url = ai_settings.get('doubao_api_url')
api_key = ai_settings.get('doubao_api_key')
model_name = model or ai_settings.get('doubao_api_model') or "doubao-pro-32k-241215"
if not api_url or not api_key:
return {"success": False, "error": "豆包 API 未配置"}
return {"success": False, "error": "豆包 API 未配置,请在 Ai Settings 中设置 doubao_api_url 和 doubao_api_key"}
content = [{"type": "text", "text": prompt}]
if image_urls:
for img in image_urls: