- Modified connect() function to pass session as URL parameter - Fallback to cookie-based auth if session not available - Improves compatibility with httponly cookie settings
162 lines
11 KiB
XML
162 lines
11 KiB
XML
{
|
||
"widgettype": "VBox",
|
||
"options": {
|
||
"width": "100%",
|
||
"height": "100%",
|
||
"spacing": 12
|
||
},
|
||
"subwidgets": [
|
||
{
|
||
"widgettype": "HBox",
|
||
"options": {
|
||
"width": "100%",
|
||
"padding": "16px 20px",
|
||
"spacing": 12
|
||
},
|
||
"subwidgets": [
|
||
{
|
||
"widgettype": "Title2",
|
||
"options": {
|
||
"text": "AI 推理控制台",
|
||
"halign": "left"
|
||
}
|
||
},
|
||
{
|
||
"widgettype": "Text",
|
||
"id": "ws_status",
|
||
"options": {
|
||
"text": "未连接",
|
||
"halign": "right"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"widgettype": "VBox",
|
||
"options": {
|
||
"width": "calc(100% - 40px)",
|
||
"margin": "0 20px",
|
||
"padding": "16px",
|
||
"spacing": 12
|
||
},
|
||
"subwidgets": [
|
||
{
|
||
"widgettype": "Form",
|
||
"id": "input_form",
|
||
"options": {
|
||
"layout": "vertical",
|
||
"fields": [
|
||
{
|
||
"name": "user_input",
|
||
"label": "输入请求",
|
||
"uitype": "text",
|
||
"height": "120px",
|
||
"placeholder": "输入你的请求,AI 将自动推理、规划并执行..."
|
||
}
|
||
]
|
||
}
|
||
},
|
||
{
|
||
"widgettype": "HBox",
|
||
"options": {
|
||
"width": "100%",
|
||
"spacing": 10
|
||
},
|
||
"subwidgets": [
|
||
{
|
||
"widgettype": "Button",
|
||
"id": "start_btn",
|
||
"options": {
|
||
"label": "开始推理",
|
||
"variant": "primary",
|
||
"bgcolor": "#2196F3"
|
||
},
|
||
"binds": [
|
||
{
|
||
"wid": "self",
|
||
"event": "click",
|
||
"actiontype": "script",
|
||
"script": "startReasoning()"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"widgettype": "Button",
|
||
"id": "clear_btn",
|
||
"options": {
|
||
"label": "清空日志",
|
||
"variant": "secondary",
|
||
"bgcolor": "#607D8B"
|
||
},
|
||
"binds": [
|
||
{
|
||
"wid": "self",
|
||
"event": "click",
|
||
"actiontype": "script",
|
||
"script": "clearSteps()"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"widgettype": "VBox",
|
||
"id": "status_bar",
|
||
"options": {
|
||
"width": "calc(100% - 40px)",
|
||
"margin": "0 20px",
|
||
"padding": "12px 16px",
|
||
"spacing": 4
|
||
},
|
||
"subwidgets": [
|
||
{
|
||
"widgettype": "Text",
|
||
"id": "status_text",
|
||
"options": {
|
||
"text": "等待连接...",
|
||
"halign": "left"
|
||
}
|
||
},
|
||
{
|
||
"widgettype": "Text",
|
||
"id": "current_step",
|
||
"options": {
|
||
"text": "",
|
||
"halign": "left"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"widgettype": "VBox",
|
||
"options": {
|
||
"width": "100%",
|
||
"height": "400px",
|
||
"style": "overflow-y: auto;"
|
||
},
|
||
"subwidgets": [
|
||
{
|
||
"widgettype": "VBox",
|
||
"id": "steps_container",
|
||
"options": {
|
||
"width": "calc(100% - 40px)",
|
||
"margin": "0 20px",
|
||
"spacing": 8,
|
||
"padding": "8px 0"
|
||
},
|
||
"subwidgets": []
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"widgettype": "Html",
|
||
"id": "ws_logic",
|
||
"options": {
|
||
"html": "<script>\n(function() {\n var userId = '{{ get_user() }}';\n var wsPath = '{{ entire_url('/wss/harnessed_reasoning/reasoning_console.wss') }}';\n var ws = null;\n var connected = false;\n var stepCount = 0;\n\n function updateStatus(state, text) {\n var statusWidget = bricks.app.find_widget_by_id('status_text');\n var badgeWidget = bricks.app.find_widget_by_id('ws_status');\n if (statusWidget) statusWidget.set_text(text);\n if (badgeWidget) {\n var labels = {connected: '已连接', connecting: '连接中', disconnected: '未连接'};\n badgeWidget.set_text(labels[state] || text);\n }\n }\n\n function handleMessage(msg) {\n if (msg.type === 'connected') {\n updateStatus('connected', '已连接');\n return;\n }\n if (msg.type === 'pong') return;\n if (msg.type === 'error') {\n addStep('error', msg.data ? msg.data.message : msg.message || '错误', msg.data || {});\n return;\n }\n var event = msg.event || msg.type;\n var data = msg.data || {};\n var message = data.message || event;\n addStep(event, message, data);\n }\n\n function addStep(event, message, data) {\n var container = bricks.app.find_widget_by_id('steps_container');\n if (!container) return;\n stepCount++;\n\n var typeClass = 'info', typeLabel = event;\n if (event.indexOf('context') >= 0) { typeClass = 'context'; typeLabel = '上下文'; }\n else if (event.indexOf('plan') >= 0 || event.indexOf('thinking') >= 0) { typeClass = 'plan'; typeLabel = '思考'; }\n else if (event.indexOf('tool_call') >= 0) { typeClass = 'tool'; typeLabel = data.success === false ? '工具失败' : '工具调用'; }\n else if (event.indexOf('step_') >= 0) { typeClass = 'execute'; typeLabel = '执行步骤'; }\n else if (event.indexOf('complete') >= 0) { typeClass = 'result'; typeLabel = '完成'; }\n else if (event.indexOf('error') >= 0) { typeClass = 'error'; typeLabel = '错误'; }\n else if (event.indexOf('safety') >= 0) { typeClass = 'plan'; typeLabel = '安全检查'; }\n\n var now = new Date();\n var timeStr = now.getHours().toString().padStart(2,'0') + ':' + now.getMinutes().toString().padStart(2,'0') + ':' + now.getSeconds().toString().padStart(2,'0');\n\n var detailsHtml = '';\n if (data.request) detailsHtml += '<div style=\"margin-top:6px;padding:6px 10px;background:#0f1923;border-radius:3px;font-size:12px;color:#78909c;word-break:break-all;\">请求: ' + escapeHtml(data.request) + '</div>';\n if (data.analysis) detailsHtml += '<div style=\"margin-top:6px;padding:6px 10px;background:#0f1923;border-radius:3px;font-size:12px;color:#78909c;word-break:break-all;\">分析: ' + escapeHtml(data.analysis) + '</div>';\n if (data.tool) detailsHtml += '<div style=\"margin-top:6px;padding:6px 10px;background:#0f1923;border-radius:3px;font-size:12px;color:#78909c;word-break:break-all;\">工具: ' + data.tool + (data.parameters ? ' 参数: ' + JSON.stringify(data.parameters) : '') + '</div>';\n if (data.result) detailsHtml += '<div style=\"margin-top:6px;padding:6px 10px;background:#0f1923;border-radius:3px;font-size:12px;color:#78909c;word-break:break-all;max-height:100px;overflow-y:auto;\">结果: ' + escapeHtml(String(data.result).substring(0, 500)) + '</div>';\n\n var borderColor = typeClass === 'result' ? '#4caf50' : typeClass === 'error' ? '#f44336' : typeClass === 'tool' ? '#ce93d8' : typeClass === 'execute' ? '#ffcc80' : typeClass === 'plan' ? '#90caf9' : '#2196F3';\n var bgColor = typeClass === 'result' ? '#4caf50' : typeClass === 'error' ? '#f44336' : typeClass === 'tool' ? '#7b1fa2' : typeClass === 'execute' ? '#e65100' : typeClass === 'plan' ? '#0d47a1' : '#1565c0';\n var labelBg = typeClass === 'context' ? '#1b5e20' : typeClass === 'plan' ? '#0d47a1' : typeClass === 'tool' ? '#4a148c' : typeClass === 'execute' ? '#e65100' : typeClass === 'result' ? '#1b5e20' : typeClass === 'error' ? '#b71c1c' : '#1565c0';\n var labelColor = typeClass === 'context' ? '#a5d6a7' : typeClass === 'plan' ? '#90caf9' : typeClass === 'tool' ? '#ce93d8' : typeClass === 'execute' ? '#ffcc80' : typeClass === 'result' ? '#a5d6a7' : typeClass === 'error' ? '#ef9a9a' : '#bbdefb';\n\n var stepHtml = '<div style=\"position:relative;margin-bottom:10px;padding-left:20px;\">' +\n '<div style=\"position:absolute;left:4px;top:10px;width:10px;height:10px;border-radius:50%;border:2px solid ' + borderColor + ';background:' + bgColor + '\"></div>' +\n '<div style=\"background:#1a2733;border:1px solid #2d4a5e;border-radius:6px;padding:10px 14px;\">' +\n '<div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;\">' +\n '<span style=\"font-size:10px;padding:2px 6px;border-radius:3px;font-weight:600;text-transform:uppercase;background:' + labelBg + ';color:' + labelColor + '\">' + typeLabel + '</span>' +\n '<span style=\"font-size:10px;color:#546e7a;\">' + timeStr + '</span></div>' +\n '<div style=\"font-size:13px;color:#b0bec5;line-height:1.5;\">' + escapeHtml(message) + '</div>' +\n detailsHtml + '</div></div>';\n\n container.el.insertAdjacentHTML('beforeend', stepHtml);\n var currentWidget = bricks.app.find_widget_by_id('current_step');\n if (currentWidget) currentWidget.set_text(message);\n container.el.scrollTop = container.el.scrollHeight;\n }\n\n function escapeHtml(text) {\n var div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n window.startReasoning = function() {\n if (!connected) {\n alert('WebSocket 未连接');\n return;\n }\n var form = bricks.app.find_widget_by_id('input_form');\n if (!form) return;\n var inputVal = form.get_value('user_input');\n if (!inputVal || !inputVal.trim()) return;\n \n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({cmd: 'start_reasoning', request: inputVal.trim(), user_id: userId}));\n }\n };\n\n window.clearSteps = function() {\n var container = bricks.app.find_widget_by_id('steps_container');\n if (container) container.el.innerHTML = '';\n stepCount = 0;\n var currentWidget = bricks.app.find_widget_by_id('current_step');\n if (currentWidget) currentWidget.set_text('');\n };\n\n function connect() {\n if (ws && ws.readyState === WebSocket.OPEN) return;\n\n var url = wsPath;\n if (url.indexOf('http') !== 0) {\n var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n url = protocol + '//' + window.location.host + url;\n }\n\n // 修复:使用URL参数传递session,而不是作为WebSocket协议\n // 浏览器会自动携带当前域的cookie\n var session = '';\n try {\n if (window.bricks && window.bricks.app && window.bricks.app.get_session) {\n session = window.bricks.app.get_session();\n }\n } catch(e) { }\n\n try {\n // 如果session存在,将其作为URL参数传递\n var wsUrl = session ? url + '?session=' + encodeURIComponent(session) : url;\n ws = new WebSocket(wsUrl);\n\n ws.onopen = function() {\n console.log('WS Open');\n connected = true;\n updateStatus('connected', '已连接');\n ws.send(JSON.stringify({cmd: 'connect', user_id: userId}));\n };\n\n ws.onmessage = function(e) {\n try {\n var msg = JSON.parse(e.data);\n handleMessage(msg);\n } catch(err) { console.error('Parse error', err); }\n };\n\n ws.onerror = function(e) {\n console.error('WS Error', e);\n connected = false;\n updateStatus('disconnected', '连接错误');\n };\n\n ws.onclose = function() {\n console.log('WS Close');\n connected = false;\n updateStatus('disconnected', '已断开');\n setTimeout(connect, 3000);\n };\n } catch(e) {\n console.error('Connect failed', e);\n }\n }\n\n setTimeout(connect, 500);\n})();\n</script>"
|
||
}
|
||
}
|
||
]
|
||
}
|