v2.1: add Property Inspector with poll interval setting

This commit is contained in:
2026-06-18 15:10:55 +08:00
parent 77c4db36d8
commit 8535838fc4
11 changed files with 1849 additions and 3 deletions
@@ -21,6 +21,7 @@
"UserTitleEnabled": true,
"SupportedInMultiActions": true,
"Tooltip": "Codex Desktop 工作流状态监控"
"PropertyInspectorPath": "propertyInspector/monitor/index.html"
},
{
"UUID": "com.streamdock.ai-monitor.claude",
@@ -43,6 +44,7 @@
"UserTitleEnabled": true,
"SupportedInMultiActions": true,
"Tooltip": "Claude Desktop 工作流状态监控"
"PropertyInspectorPath": "propertyInspector/monitor/index.html"
},
{
"UUID": "com.streamdock.ai-monitor.opencode",
@@ -65,6 +67,7 @@
"UserTitleEnabled": true,
"SupportedInMultiActions": true,
"Tooltip": "opencode 工作流状态监控"
"PropertyInspectorPath": "propertyInspector/monitor/index.html"
}
],
"SDKVersion": 1,
@@ -185,6 +185,14 @@ function makeActionFn(agentKey) {
if (states[ctx] && states[ctx].timer) { clearInterval(states[ctx].timer); }
delete states[ctx];
},
_didReceiveSettings: function(data) {
var ctx = data.context;
var ss = states[ctx]; if (!ss) return;
if (ss.timer) clearInterval(ss.timer);
var settings = plugin[agentKey].data[ctx];
var interval = (settings && settings.pollInterval) || POLL_INTERVAL;
ss.timer = setInterval(function() { poll(ctx); }, interval);
},
keyUp: function(data) { poll(data.context); },
};
}
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Monitor Settings</title>
<link rel="stylesheet" href="../utils/css/sdpi.css">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; padding: 16px; margin: 0; }
label { display: block; font-size: 12px; color: #888; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
.row { display: flex; align-items: center; gap: 8px; }
input[type="range"] { flex: 1; accent-color: #f59e0b; }
.row span { font-size: 12px; color: #f59e0b; min-width: 50px; text-align: right; }
</style>
</head>
<body>
<label>Poll Interval (ms)</label>
<div class="row">
<input type="range" id="pollInterval" min="500" max="5000" step="100" value="1000">
<span id="pollVal">1000ms</span>
</div>
<script src="../utils/action.js"></script>
<script>
(function () {
var slider = document.getElementById('pollInterval');
var val = document.getElementById('pollVal');
slider.addEventListener('input', function () { val.textContent = this.value + 'ms'; $settings.pollInterval = parseInt(this.value, 10); });
if ($settings.pollInterval) slider.value = $settings.pollInterval;
else $settings.pollInterval = 1000;
})();
</script>
</body>
</html>
@@ -0,0 +1,157 @@
let $websocket, $uuid, $action, $context, $settings, $lang, $FileID = '';
WebSocket.prototype.setGlobalSettings = function(payload) {
this.send(JSON.stringify({
event: "setGlobalSettings",
context: $uuid, payload
}));
}
WebSocket.prototype.getGlobalSettings = function() {
this.send(JSON.stringify({
event: "getGlobalSettings",
context: $uuid,
}));
}
// 与插件通信
WebSocket.prototype.sendToPlugin = function (payload) {
this.send(JSON.stringify({
event: "sendToPlugin",
action: $action,
context: $uuid,
payload
}));
};
//设置标题
WebSocket.prototype.setTitle = function (str, row = 0, num = 6) {
console.log(str);
let newStr = '';
if (row) {
let nowRow = 1, strArr = str.split('');
strArr.forEach((item, index) => {
if (nowRow < row && index >= nowRow * num) { nowRow++; newStr += '\n'; }
if (nowRow <= row && index < nowRow * num) { newStr += item; }
});
if (strArr.length > row * num) { newStr = newStr.substring(0, newStr.length - 1); newStr += '..'; }
}
this.send(JSON.stringify({
event: "setTitle",
context: $context,
payload: {
target: 0,
title: newStr || str
}
}));
}
// 设置状态
WebSocket.prototype.setState = function (state) {
this.send(JSON.stringify({
event: "setState",
context: $context,
payload: { state }
}));
};
// 设置背景
WebSocket.prototype.setImage = function (url) {
let image = new Image();
image.src = url;
image.onload = () => {
let canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
this.send(JSON.stringify({
event: "setImage",
context: $context,
payload: {
target: 0,
image: canvas.toDataURL("image/png")
}
}));
};
};
// 打开网页
WebSocket.prototype.openUrl = function (url) {
this.send(JSON.stringify({
event: "openUrl",
payload: { url }
}));
};
// 保存持久化数据
WebSocket.prototype.saveData = $.debounce(function (payload) {
this.send(JSON.stringify({
event: "setSettings",
context: $uuid,
payload
}));
});
// StreamDock 软件入口函数
const connectSocket = connectElgatoStreamDeckSocket;
async function connectElgatoStreamDeckSocket(port, uuid, event, app, info) {
info = JSON.parse(info);
$uuid = uuid; $action = info.action;
$context = info.context;
$websocket = new WebSocket('ws://127.0.0.1:' + port);
$websocket.onopen = () => $websocket.send(JSON.stringify({ event, uuid }));
// 持久数据代理
$websocket.onmessage = e => {
let data = JSON.parse(e.data);
if (data.event === 'didReceiveSettings') {
$settings = new Proxy(data.payload.settings, {
get(target, property) {
return target[property];
},
set(target, property, value) {
target[property] = value;
$websocket.saveData(data.payload.settings);
}
});
if (!$back) $dom.main.style.display = 'block';
}
$propEvent[data.event]?.(data.payload);
};
// 自动翻译页面
if (!$local) return;
$lang = await new Promise(resolve => {
const req = new XMLHttpRequest();
req.open('GET', `../../${JSON.parse(app).application.language}.json`);
req.send();
req.onreadystatechange = () => {
if (req.readyState === 4) {
resolve(JSON.parse(req.responseText).Localization);
}
};
});
// 遍历文本节点并翻译所有文本节点
const walker = document.createTreeWalker($dom.main, NodeFilter.SHOW_TEXT, (e) => {
return e.data.trim() && NodeFilter.FILTER_ACCEPT;
});
while (walker.nextNode()) {
console.log(walker.currentNode.data);
walker.currentNode.data = $lang[walker.currentNode.data];
}
// placeholder 特殊处理
const translate = item => {
if (item.placeholder?.trim()) {
console.log(item.placeholder);
item.placeholder = $lang[item.placeholder];
}
};
$('input', true).forEach(translate);
$('textarea', true).forEach(translate);
}
// StreamDock 文件路径回调
Array.from($('input[type="file"]', true)).forEach(item => item.addEventListener('click', () => $FileID = item.id));
const onFilePickerReturn = (url) => $emit.send(`File-${$FileID}`, JSON.parse(url));
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,81 @@
// 自定义事件类
class EventPlus {
constructor() {
this.event = new EventTarget();
}
on(name, callback) {
this.event.addEventListener(name, e => callback(e.detail));
}
send(name, data) {
this.event.dispatchEvent(new CustomEvent(name, {
detail: data,
bubbles: false,
cancelable: false
}));
}
}
// 补零
String.prototype.fill = function () {
return this >= 10 ? this : '0' + this;
};
// unicode编码转换字符串
String.prototype.uTs = function () {
return eval('"' + Array.from(this).join('') + '"');
};
// 字符串转换unicode编码
String.prototype.sTu = function (str = '') {
Array.from(this).forEach(item => str += `\\u${item.charCodeAt(0).toString(16)}`);
return str;
};
// 全局变量/方法
const $emit = new EventPlus(), $ = (selector, isAll = false) => {
const element = document.querySelector(selector), methods = {
on: function (event, callback) {
this.addEventListener(event, callback);
},
attr: function (name, value = '') {
value && this.setAttribute(name, value);
return this;
}
};
if (!isAll && element) {
return Object.assign(element, methods);
} else if (!isAll && !element) {
throw `HTML没有 ${selector} 元素! 请检查是否拼写错误`;
}
return Array.from(document.querySelectorAll(selector)).map(item => Object.assign(item, methods));
};
// 节流函数
$.throttle = (fn, delay) => {
let Timer = null;
return function () {
if (Timer) return;
Timer = setTimeout(() => {
fn.apply(this, arguments);
Timer = null;
}, delay);
};
};
// 防抖函数
$.debounce = (fn, delay) => {
let Timer = null;
return function () {
clearTimeout(Timer);
Timer = setTimeout(() => fn.apply(this, arguments), delay);
};
};
// 绑定限制数字方法
Array.from($('input[type="num"]', true)).forEach(item => {
item.addEventListener('input', function limitNum() {
if (!item.value || /^\d+$/.test(item.value)) return;
item.value = item.value.slice(0, -1);
limitNum(item);
});
});
@@ -1 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Codex</title><path d="M19.503 0H4.496A4.496 4.496 0 000 4.496v15.007A4.496 4.496 0 004.496 24h15.007A4.496 4.496 0 0024 19.503V4.496A4.496 4.496 0 0019.503 0z" fill="#fff"></path><path d="M9.064 3.344a4.578 4.578 0 012.285-.312c1 .115 1.891.54 2.673 1.275.01.01.024.017.037.021a.09.09 0 00.043 0 4.55 4.55 0 013.046.275l.047.022.116.057a4.581 4.581 0 012.188 2.399c.209.51.313 1.041.315 1.595a4.24 4.24 0 01-.134 1.223.123.123 0 00.03.115c.594.607.988 1.33 1.183 2.17.289 1.425-.007 2.71-.887 3.854l-.136.166a4.548 4.548 0 01-2.201 1.388.123.123 0 00-.081.076c-.191.551-.383 1.023-.74 1.494-.9 1.187-2.222 1.846-3.711 1.838-1.187-.006-2.239-.44-3.157-1.302a.107.107 0 00-.105-.024c-.388.125-.78.143-1.204.138a4.441 4.441 0 01-1.945-.466 4.544 4.544 0 01-1.61-1.335c-.152-.202-.303-.392-.414-.617a5.81 5.81 0 01-.37-.961 4.582 4.582 0 01-.014-2.298.124.124 0 00.006-.056.085.085 0 00-.027-.048 4.467 4.467 0 01-1.034-1.651 3.896 3.896 0 01-.251-1.192 5.189 5.189 0 01.141-1.6c.337-1.112.982-1.985 1.933-2.618.212-.141.413-.251.601-.33.215-.089.43-.164.646-.227a.098.098 0 00.065-.066 4.51 4.51 0 01.829-1.615 4.535 4.535 0 011.837-1.388zm3.482 10.565a.637.637 0 000 1.272h3.636a.637.637 0 100-1.272h-3.636zM8.462 9.23a.637.637 0 00-1.106.631l1.272 2.224-1.266 2.136a.636.636 0 101.095.649l1.454-2.455a.636.636 0 00.005-.64L8.462 9.23z" fill="url(#lobe-icons-codex-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-codex-_R_0_" x1="12" x2="12" y1="3" y2="21"><stop stop-color="#B1A7FF"></stop><stop offset=".5" stop-color="#7A9DFF"></stop><stop offset="1" stop-color="#3941FF"></stop></linearGradient></defs></svg>
<svg fill="none" height="2500" viewBox="0 0 24 24" width="2500" xmlns="http://www.w3.org/2000/svg"><title>Codex</title><path d="M19.503 0H4.496A4.496 4.496 0 000 4.496v15.007A4.496 4.496 0 004.496 24h15.007A4.496 4.496 0 0024 19.503V4.496A4.496 4.496 0 0019.503 0z" fill="#fff"></path><path d="M9.064 3.344a4.578 4.578 0 012.285-.312c1 .115 1.891.54 2.673 1.275.01.01.024.017.037.021a.09.09 0 00.043 0 4.55 4.55 0 013.046.275l.047.022.116.057a4.581 4.581 0 012.188 2.399c.209.51.313 1.041.315 1.595a4.24 4.24 0 01-.134 1.223.123.123 0 00.03.115c.594.607.988 1.33 1.183 2.17.289 1.425-.007 2.71-.887 3.854l-.136.166a4.548 4.548 0 01-2.201 1.388.123.123 0 00-.081.076c-.191.551-.383 1.023-.74 1.494-.9 1.187-2.222 1.846-3.711 1.838-1.187-.006-2.239-.44-3.157-1.302a.107.107 0 00-.105-.024c-.388.125-.78.143-1.204.138a4.441 4.441 0 01-1.945-.466 4.544 4.544 0 01-1.61-1.335c-.152-.202-.303-.392-.414-.617a5.81 5.81 0 01-.37-.961 4.582 4.582 0 01-.014-2.298.124.124 0 00.006-.056.085.085 0 00-.027-.048 4.467 4.467 0 01-1.034-1.651 3.896 3.896 0 01-.251-1.192 5.189 5.189 0 01.141-1.6c.337-1.112.982-1.985 1.933-2.618.212-.141.413-.251.601-.33.215-.089.43-.164.646-.227a.098.098 0 00.065-.066 4.51 4.51 0 01.829-1.615 4.535 4.535 0 011.837-1.388zm3.482 10.565a.637.637 0 000 1.272h3.636a.637.637 0 100-1.272h-3.636zM8.462 9.23a.637.637 0 00-1.106.631l1.272 2.224-1.266 2.136a.636.636 0 101.095.649l1.454-2.455a.636.636 0 00.005-.64L8.462 9.23z" fill="url(#lobe-icons-codex-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-codex-_R_0_" x1="12" x2="12" y1="3" y2="21"><stop stop-color="#B1A7FF"></stop><stop offset=".5" stop-color="#7A9DFF"></stop><stop offset="1" stop-color="#3941FF"></stop></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB