159 lines
11 KiB
XML
159 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": "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 + '/harnessed_reasoning/reasoning_console.wss';\n this.ws = new WebSocket(url);\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('./reasoning_console.wss')}}",
|
||
"with_session": true
|
||
}
|
||
}
|
||
]
|
||
}
|