diff --git a/apps/jingrow/frontend/src/core/features/flows/executors/flowExecutor.js b/apps/jingrow/frontend/src/core/features/flows/executors/flowExecutor.js index 98ce211..dbbef7c 100644 --- a/apps/jingrow/frontend/src/core/features/flows/executors/flowExecutor.js +++ b/apps/jingrow/frontend/src/core/features/flows/executors/flowExecutor.js @@ -24,7 +24,14 @@ export class FlowExecutor { * @returns {Promise} 执行结果 */ 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} 执行结果 */ 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; diff --git a/apps/jingrow/frontend/vite.config.ts b/apps/jingrow/frontend/vite.config.ts index 27aad87..bb2de73 100644 --- a/apps/jingrow/frontend/vite.config.ts +++ b/apps/jingrow/frontend/vite.config.ts @@ -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, diff --git a/apps/jingrow/jingrow/__init__.py b/apps/jingrow/jingrow/__init__.py index 3af92ce..31abec7 100644 --- a/apps/jingrow/jingrow/__init__.py +++ b/apps/jingrow/jingrow/__init__.py @@ -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() diff --git a/apps/jingrow/jingrow/api/nodes.py b/apps/jingrow/jingrow/api/nodes.py index 9938f3d..32cc359 100644 --- a/apps/jingrow/jingrow/api/nodes.py +++ b/apps/jingrow/jingrow/api/nodes.py @@ -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)) diff --git a/apps/jingrow/jingrow/utils/jingrow_cloud.py b/apps/jingrow/jingrow/utils/jingrow_cloud.py index 9b9578f..f048d7b 100644 --- a/apps/jingrow/jingrow/utils/jingrow_cloud.py +++ b/apps/jingrow/jingrow/utils/jingrow_cloud.py @@ -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: