harnessed_reasoning/wwwroot/reasoning_console.ui
yumoqing fd8930ef43 fix: wss URL添加/wss前缀
- WebSocket widget ws_url: /wss/harnessed_reasoning/reasoning_console.wss
- HTML中硬编码的JS connect() URL同步添加/wss前缀
nginx将/wss前缀路由到.wss处理器,URL必须包含此前缀
2026-05-13 14:45:06 +08:00

159 lines
11 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"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": "Input",
"id": "reasoning_input",
"options": {
"text": "",
"placeholder": "输入你的请求AI 将自动推理、规划并执行..."
}
},
{
"widgettype": "HBox",
"options": {
"width": "100%",
"spacing": 10
},
"subwidgets": [
{
"widgettype": "Button",
"id": "start_btn",
"options": {
"text": "开始推理",
"bgcolor": "#2196F3"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "script",
"script": "startReasoning()"
}
]
},
{
"widgettype": "Button",
"id": "clear_btn",
"options": {
"text": "清空日志",
"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": "Scroll",
"options": {
"width": "100%",
"height": "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>\nwindow.reasoningWS = {\n ws: null,\n stepCount: 0,\n\n connect: function() {\n var self = this;\n var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n var url = protocol + '//' + window.location.host + '/wss/harnessed_reasoning/reasoning_console.wss';\n var cookie = document.cookie || '';\n this.ws = new WebSocket(url, cookie);\n this.ws.onopen = function() {\n self.updateStatus('connected', '已连接');\n self.ws.send(JSON.stringify({cmd: 'connect', user_id: 'current_user'}));\n };\n this.ws.onmessage = function(e) {\n try { self.handleMessage(JSON.parse(e.data)); } catch(err) {}\n };\n this.ws.onerror = function() { self.updateStatus('disconnected', '连接错误'); };\n this.ws.onclose = function() {\n self.updateStatus('disconnected', '已断开');\n setTimeout(function() { self.connect(); }, 3000);\n };\n },\n\n updateStatus: function(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] || state);\n }\n },\n\n handleMessage: function(msg) {\n if (msg.type === 'connected' || msg.type === 'pong') return;\n if (msg.type === 'error') {\n this.addStep('error', msg.data ? msg.data.message : msg.message || '错误', msg.data || {});\n var btn = bricks.app.find_widget_by_id('start_btn');\n if (btn) btn.options.disabled = false;\n return;\n }\n var event = msg.event || msg.type;\n var data = msg.data || {};\n var message = data.message || event;\n this.addStep(event, message, data);\n if (event === 'reasoning_complete' || event === 'execution_complete') {\n var btn = bricks.app.find_widget_by_id('start_btn');\n if (btn) btn.options.disabled = false;\n }\n },\n\n addStep: function(event, message, data) {\n var container = bricks.app.find_widget_by_id('steps_container');\n if (!container) return;\n this.stepCount++;\n\n var dotClass = 'info', 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;\">请求: ' + this.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;\">分析: ' + this.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;\">结果: ' + this.escapeHtml(String(data.result).substring(0, 500)) + '</div>';\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 #' +\n (typeClass === 'result' ? '4caf50' : typeClass === 'error' ? 'f44336' : typeClass === 'tool' ? 'ce93d8' : typeClass === 'execute' ? 'ffcc80' : typeClass === 'plan' ? '90caf9' : '2196F3') +\n ';background:' + (typeClass === 'result' ? '4caf50' : typeClass === 'error' ? 'f44336' : typeClass === 'tool' ? '7b1fa2' : typeClass === 'execute' ? 'e65100' : typeClass === 'plan' ? '0d47a1' : '1565c0') + '\"></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:' +\n (typeClass === 'context' ? '#1b5e20' : typeClass === 'plan' ? '#0d47a1' : typeClass === 'tool' ? '#4a148c' : typeClass === 'execute' ? '#e65100' : typeClass === 'result' ? '#1b5e20' : typeClass === 'error' ? '#b71c1c' : '#1565c0') +\n ';color:' + (typeClass === 'context' ? '#a5d6a7' : typeClass === 'plan' ? '#90caf9' : typeClass === 'tool' ? '#ce93d8' : typeClass === 'execute' ? '#ffcc80' : typeClass === 'result' ? '#a5d6a7' : typeClass === 'error' ? '#ef9a9a' : '#bbdefb') + '\">' + 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;\">' + this.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 escapeHtml: function(text) {\n var div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n};\n\nwindow.startReasoning = function() {\n var ws = window.reasoningWS;\n if (!ws.ws || ws.ws.readyState !== WebSocket.OPEN) {\n alert('WebSocket 未连接');\n return;\n }\n var input = bricks.app.find_widget_by_id('reasoning_input');\n if (!input || !input.options.text.trim()) return;\n var btn = bricks.app.find_widget_by_id('start_btn');\n if (btn) btn.options.disabled = true;\n ws.ws.send(JSON.stringify({cmd: 'start_reasoning', request: input.options.text.trim(), user_id: 'current_user'}));\n};\n\nwindow.clearSteps = function() {\n var container = bricks.app.find_widget_by_id('steps_container');\n if (container) container.el.innerHTML = '';\n window.reasoningWS.stepCount = 0;\n var currentWidget = bricks.app.find_widget_by_id('current_step');\n if (currentWidget) currentWidget.set_text('');\n var btn = bricks.app.find_widget_by_id('start_btn');\n if (btn) btn.options.disabled = false;\n};\n\nwindow.reasoningWS.connect();\n</script>"
}
},
{
"widgettype": "WebSocket",
"id": "reasoning_ws",
"options": {
"ws_url": "{{entire_url('/wss/harnessed_reasoning/reasoning_console.wss')}}",
"with_session": true
}
}
]
}