diff --git a/wwwroot/reasoning_console.js b/wwwroot/reasoning_console.js
new file mode 100644
index 0000000..99ef3c2
--- /dev/null
+++ b/wwwroot/reasoning_console.js
@@ -0,0 +1,171 @@
+// harnessed_reasoning/reasoning_console.js
+// JavaScript for the reasoning console UI.
+// Loaded automatically by Sage from wwwroot/.
+
+(function() {
+ var reasoningWS = {
+ ws: null,
+ stepCount: 0,
+ userId: null,
+
+ connect: function() {
+ var self = this;
+ // Get user_id from session if available
+ try {
+ if (bricks.app && bricks.app.get_session) {
+ this.userId = bricks.app.get_session();
+ }
+ } catch(e) {}
+ if (!this.userId) this.userId = 'anonymous';
+
+ var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ var url = protocol + '//' + window.location.host + '/harnessed_reasoning/reasoning_console.wss';
+ this.ws = new WebSocket(url);
+
+ this.ws.onopen = function() {
+ self.updateStatus('connected', '已连接');
+ self.ws.send(JSON.stringify({cmd: 'connect', user_id: self.userId}));
+ };
+
+ this.ws.onmessage = function(e) {
+ try { self.handleMessage(JSON.parse(e.data)); } catch(err) {}
+ };
+
+ this.ws.onerror = function() { self.updateStatus('disconnected', '连接错误'); };
+
+ this.ws.onclose = function() {
+ self.updateStatus('disconnected', '已断开');
+ setTimeout(function() { self.connect(); }, 3000);
+ };
+ },
+
+ updateStatus: function(state, text) {
+ var statusWidget = bricks.app.find_widget_by_id('status_text');
+ var badgeWidget = bricks.app.find_widget_by_id('ws_status');
+ if (statusWidget) statusWidget.set_text(text);
+ if (badgeWidget) {
+ var labels = {connected: '已连接', connecting: '连接中', disconnected: '未连接'};
+ badgeWidget.set_text(labels[state] || state);
+ }
+ },
+
+ handleMessage: function(msg) {
+ if (msg.type === 'connected' || msg.type === 'pong') return;
+ if (msg.type === 'error') {
+ this.addStep('error', msg.data ? msg.data.message : msg.message || '错误', msg.data || {});
+ var btn = bricks.app.find_widget_by_id('start_btn');
+ if (btn) btn.options.disabled = false;
+ return;
+ }
+ var event = msg.event || msg.type;
+ var data = msg.data || {};
+ var message = data.message || event;
+ this.addStep(event, message, data);
+ if (event === 'reasoning_complete' || event === 'execution_complete') {
+ var btn = bricks.app.find_widget_by_id('start_btn');
+ if (btn) btn.options.disabled = false;
+ }
+ },
+
+ addStep: function(event, message, data) {
+ var container = bricks.app.find_widget_by_id('steps_container');
+ if (!container) return;
+ this.stepCount++;
+
+ var typeClass = 'info', typeLabel = event;
+ if (event.indexOf('context') >= 0) { typeClass = 'context'; typeLabel = '上下文'; }
+ else if (event.indexOf('plan') >= 0 || event.indexOf('thinking') >= 0) { typeClass = 'plan'; typeLabel = '思考'; }
+ else if (event.indexOf('tool_call') >= 0) { typeClass = 'tool'; typeLabel = data.success === false ? '工具失败' : '工具调用'; }
+ else if (event.indexOf('step_') >= 0) { typeClass = 'execute'; typeLabel = '执行步骤'; }
+ else if (event.indexOf('complete') >= 0) { typeClass = 'result'; typeLabel = '完成'; }
+ else if (event.indexOf('error') >= 0) { typeClass = 'error'; typeLabel = '错误'; }
+ else if (event.indexOf('safety') >= 0) { typeClass = 'plan'; typeLabel = '安全检查'; }
+
+ var now = new Date();
+ var timeStr = now.getHours().toString().padStart(2,'0') + ':' + now.getMinutes().toString().padStart(2,'0') + ':' + now.getSeconds().toString().padStart(2,'0');
+
+ var detailsHtml = '';
+ if (data.request) detailsHtml += '
请求: ' + this.escapeHtml(data.request) + '
';
+ if (data.analysis) detailsHtml += '分析: ' + this.escapeHtml(data.analysis) + '
';
+ if (data.tool) detailsHtml += '工具: ' + data.tool + (data.parameters ? ' 参数: ' + JSON.stringify(data.parameters) : '') + '
';
+ if (data.result) detailsHtml += '结果: ' + this.escapeHtml(String(data.result).substring(0, 500)) + '
';
+
+ var dotColor = typeClass === 'result' ? '4caf50' : typeClass === 'error' ? 'f44336' : typeClass === 'tool' ? 'ce93d8' : typeClass === 'execute' ? 'ffcc80' : typeClass === 'plan' ? '90caf9' : '2196F3';
+ var dotBg = typeClass === 'result' ? '4caf50' : typeClass === 'error' ? 'f44336' : typeClass === 'tool' ? '7b1fa2' : typeClass === 'execute' ? 'e65100' : typeClass === 'plan' ? '0d47a1' : '1565c0';
+ var badgeBg = typeClass === 'context' ? '#1b5e20' : typeClass === 'plan' ? '#0d47a1' : typeClass === 'tool' ? '#4a148c' : typeClass === 'execute' ? '#e65100' : typeClass === 'result' ? '#1b5e20' : typeClass === 'error' ? '#b71c1c' : '#1565c0';
+ var badgeColor = typeClass === 'context' ? '#a5d6a7' : typeClass === 'plan' ? '#90caf9' : typeClass === 'tool' ? '#ce93d8' : typeClass === 'execute' ? '#ffcc80' : typeClass === 'result' ? '#a5d6a7' : typeClass === 'error' ? '#ef9a9a' : '#bbdefb';
+
+ var stepHtml = '' +
+ '
' +
+ '
' +
+ '
' +
+ '' + typeLabel + '' +
+ '' + timeStr + '
' +
+ '
' + this.escapeHtml(message) + '
' +
+ detailsHtml + '
';
+
+ container.el.insertAdjacentHTML('beforeend', stepHtml);
+ var currentWidget = bricks.app.find_widget_by_id('current_step');
+ if (currentWidget) currentWidget.set_text(message);
+ container.el.scrollTop = container.el.scrollHeight;
+ },
+
+ escapeHtml: function(text) {
+ var div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+ }
+ };
+
+ // Register functions for bricks RF
+ bricks.RF.register('startReasoning', async function(params) {
+ var ws = reasoningWS;
+ if (!ws.ws || ws.ws.readyState !== WebSocket.OPEN) {
+ // Try to connect first
+ ws.connect();
+ // Wait a bit for connection
+ await new Promise(function(resolve) { setTimeout(resolve, 1000); });
+ if (!ws.ws || ws.ws.readyState !== WebSocket.OPEN) {
+ bricks.show_error_message && bricks.show_error_message('WebSocket 未连接');
+ return;
+ }
+ }
+ var input = bricks.app.find_widget_by_id('reasoning_input');
+ if (!input || !input.options.text.trim()) {
+ bricks.show_error_message && bricks.show_error_message('请输入推理请求');
+ return;
+ }
+ var btn = bricks.app.find_widget_by_id('start_btn');
+ if (btn) btn.options.disabled = true;
+ ws.ws.send(JSON.stringify({cmd: 'start_reasoning', request: input.options.text.trim(), user_id: ws.userId}));
+ });
+
+ bricks.RF.register('clearSteps', async function(params) {
+ var container = bricks.app.find_widget_by_id('steps_container');
+ if (container) container.el.innerHTML = '';
+ reasoningWS.stepCount = 0;
+ var currentWidget = bricks.app.find_widget_by_id('current_step');
+ if (currentWidget) currentWidget.set_text('');
+ var btn = bricks.app.find_widget_by_id('start_btn');
+ if (btn) btn.options.disabled = false;
+ });
+
+ // Auto-connect when DOM is ready
+ function tryConnect() {
+ if (bricks.app) {
+ reasoningWS.connect();
+ } else {
+ setTimeout(tryConnect, 200);
+ }
+ }
+ tryConnect();
+
+ // Expose for legacy script actiontype compatibility
+ window.startReasoning = function() {
+ bricks.RF.call('startReasoning', {});
+ };
+ window.clearSteps = function() {
+ bricks.RF.call('clearSteps', {});
+ };
+ window.reasoningWS = reasoningWS;
+})();
diff --git a/wwwroot/reasoning_console.ui b/wwwroot/reasoning_console.ui
index cb2f5a3..eb319a1 100644
--- a/wwwroot/reasoning_console.ui
+++ b/wwwroot/reasoning_console.ui
@@ -41,19 +41,11 @@
},
"subwidgets": [
{
- "widgettype": "Form",
- "id": "input_form",
+ "widgettype": "KeyinText",
+ "id": "reasoning_input",
"options": {
- "layout": "vertical",
- "fields": [
- {
- "name": "user_input",
- "label": "输入请求",
- "uitype": "text",
- "height": "120px",
- "placeholder": "输入你的请求,AI 将自动推理、规划并执行..."
- }
- ]
+ "text": "",
+ "placeholder": "输入你的请求,AI 将自动推理、规划并执行..."
}
},
{
@@ -67,16 +59,15 @@
"widgettype": "Button",
"id": "start_btn",
"options": {
- "label": "开始推理",
- "variant": "primary",
- "bgcolor": "#2196F3"
+ "bgcolor": "#2196F3",
+ "label": "开始推理"
},
"binds": [
{
"wid": "self",
"event": "click",
- "actiontype": "script",
- "script": "startReasoning()"
+ "actiontype": "registerfunction",
+ "rfname": "startReasoning"
}
]
},
@@ -84,16 +75,15 @@
"widgettype": "Button",
"id": "clear_btn",
"options": {
- "label": "清空日志",
- "variant": "secondary",
- "bgcolor": "#607D8B"
+ "bgcolor": "#607D8B",
+ "label": "清空日志"
},
"binds": [
{
"wid": "self",
"event": "click",
- "actiontype": "script",
- "script": "clearSteps()"
+ "actiontype": "registerfunction",
+ "rfname": "clearSteps"
}
]
}
@@ -133,8 +123,8 @@
"widgettype": "VBox",
"options": {
"width": "100%",
- "height": "400px",
- "style": "overflow-y: auto;"
+ "height": "auto",
+ "style": "overflow-y: auto; max-height: 60vh;"
},
"subwidgets": [
{
@@ -154,8 +144,8 @@
"widgettype": "Html",
"id": "ws_logic",
"options": {
- "html": ""
+ "html": ""
}
}
]
-}
+}
\ No newline at end of file