botmux 2.12.2 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +29 -0
- package/README.md +29 -0
- package/dist/cli.js +104 -8
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/core/dashboard-events.d.ts +57 -0
- package/dist/core/dashboard-events.d.ts.map +1 -0
- package/dist/core/dashboard-events.js +23 -0
- package/dist/core/dashboard-events.js.map +1 -0
- package/dist/core/dashboard-ipc-server.d.ts +43 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -0
- package/dist/core/dashboard-ipc-server.js +232 -0
- package/dist/core/dashboard-ipc-server.js.map +1 -0
- package/dist/core/dashboard-locate.d.ts +20 -0
- package/dist/core/dashboard-locate.d.ts.map +1 -0
- package/dist/core/dashboard-locate.js +26 -0
- package/dist/core/dashboard-locate.js.map +1 -0
- package/dist/core/dashboard-rows.d.ts +30 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -0
- package/dist/core/dashboard-rows.js +48 -0
- package/dist/core/dashboard-rows.js.map +1 -0
- package/dist/core/scheduler.d.ts +20 -0
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +89 -2
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +19 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +146 -0
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +87 -4
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/aggregator.d.ts +41 -0
- package/dist/dashboard/aggregator.d.ts.map +1 -0
- package/dist/dashboard/aggregator.js +125 -0
- package/dist/dashboard/aggregator.js.map +1 -0
- package/dist/dashboard/auth.d.ts +23 -0
- package/dist/dashboard/auth.d.ts.map +1 -0
- package/dist/dashboard/auth.js +66 -0
- package/dist/dashboard/auth.js.map +1 -0
- package/dist/dashboard/registry.d.ts +28 -0
- package/dist/dashboard/registry.d.ts.map +1 -0
- package/dist/dashboard/registry.js +74 -0
- package/dist/dashboard/registry.js.map +1 -0
- package/dist/dashboard/web/app.d.ts +2 -0
- package/dist/dashboard/web/app.d.ts.map +1 -0
- package/dist/dashboard/web/app.js +42 -0
- package/dist/dashboard/web/app.js.map +1 -0
- package/dist/dashboard/web/groups.d.ts +2 -0
- package/dist/dashboard/web/groups.d.ts.map +1 -0
- package/dist/dashboard/web/groups.js +152 -0
- package/dist/dashboard/web/groups.js.map +1 -0
- package/dist/dashboard/web/schedules.d.ts +2 -0
- package/dist/dashboard/web/schedules.d.ts.map +1 -0
- package/dist/dashboard/web/schedules.js +105 -0
- package/dist/dashboard/web/schedules.js.map +1 -0
- package/dist/dashboard/web/sessions.d.ts +2 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -0
- package/dist/dashboard/web/sessions.js +184 -0
- package/dist/dashboard/web/sessions.js.map +1 -0
- package/dist/dashboard/web/store.d.ts +23 -0
- package/dist/dashboard/web/store.d.ts.map +1 -0
- package/dist/dashboard/web/store.js +82 -0
- package/dist/dashboard/web/store.js.map +1 -0
- package/dist/dashboard-web/app.js +129 -0
- package/dist/dashboard-web/index.html +22 -0
- package/dist/dashboard-web/style.css +50 -0
- package/dist/dashboard.d.ts +2 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +308 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/services/bridge-turn-queue.d.ts.map +1 -1
- package/dist/services/bridge-turn-queue.js +12 -7
- package/dist/services/bridge-turn-queue.js.map +1 -1
- package/dist/services/groups-store.d.ts +35 -0
- package/dist/services/groups-store.d.ts.map +1 -0
- package/dist/services/groups-store.js +104 -0
- package/dist/services/groups-store.js.map +1 -0
- package/dist/services/schedule-store.d.ts +1 -0
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +70 -1
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +1 -0
- package/dist/services/session-store.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +34 -0
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.js +47 -2
- package/dist/worker.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, watch } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const STALE_MS = 90_000;
|
|
4
|
+
/**
|
|
5
|
+
* Watches the dashboard-daemons descriptor directory and exposes the
|
|
6
|
+
* currently-online daemons (filtered by 90s heartbeat staleness).
|
|
7
|
+
*/
|
|
8
|
+
export class DaemonRegistry {
|
|
9
|
+
dir;
|
|
10
|
+
items = new Map();
|
|
11
|
+
listeners = new Set();
|
|
12
|
+
watcher;
|
|
13
|
+
constructor(dir) {
|
|
14
|
+
this.dir = dir;
|
|
15
|
+
}
|
|
16
|
+
async start() {
|
|
17
|
+
this.refresh();
|
|
18
|
+
try {
|
|
19
|
+
this.watcher = watch(this.dir, { persistent: true }, () => this.refresh());
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Directory may not exist yet — caller is expected to ensure it exists
|
|
23
|
+
// or the dashboard runs with an empty registry until the daemon writes.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
stop() {
|
|
27
|
+
this.watcher?.close();
|
|
28
|
+
this.watcher = undefined;
|
|
29
|
+
}
|
|
30
|
+
list() {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
return [...this.items.values()].filter(d => now - d.lastHeartbeat <= STALE_MS);
|
|
33
|
+
}
|
|
34
|
+
getByAppId(id) {
|
|
35
|
+
const d = this.items.get(id);
|
|
36
|
+
if (!d)
|
|
37
|
+
return undefined;
|
|
38
|
+
return Date.now() - d.lastHeartbeat > STALE_MS ? undefined : d;
|
|
39
|
+
}
|
|
40
|
+
on(fn) {
|
|
41
|
+
this.listeners.add(fn);
|
|
42
|
+
return () => this.listeners.delete(fn);
|
|
43
|
+
}
|
|
44
|
+
refresh() {
|
|
45
|
+
let names = [];
|
|
46
|
+
try {
|
|
47
|
+
names = readdirSync(this.dir);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const next = new Map();
|
|
53
|
+
for (const n of names) {
|
|
54
|
+
if (!n.endsWith('.json'))
|
|
55
|
+
continue;
|
|
56
|
+
try {
|
|
57
|
+
const d = JSON.parse(readFileSync(join(this.dir, n), 'utf8'));
|
|
58
|
+
next.set(d.larkAppId, d);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Skip malformed / partially-written files
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.items = next;
|
|
65
|
+
const online = this.list();
|
|
66
|
+
for (const fn of this.listeners) {
|
|
67
|
+
try {
|
|
68
|
+
fn(online);
|
|
69
|
+
}
|
|
70
|
+
catch { /* swallow */ }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/dashboard/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/dashboard/web/app.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Dashboard SPA entry: hash router + bootstrap + online indicator.
|
|
2
|
+
import { bootstrap, store } from './store.js';
|
|
3
|
+
import { renderSessionsPage } from './sessions.js';
|
|
4
|
+
import { renderSchedulesPage } from './schedules.js';
|
|
5
|
+
import { renderGroupsPage } from './groups.js';
|
|
6
|
+
const root = document.getElementById('root');
|
|
7
|
+
function route() {
|
|
8
|
+
const hash = location.hash || '#/';
|
|
9
|
+
if (hash.startsWith('#/groups'))
|
|
10
|
+
renderGroupsPage(root);
|
|
11
|
+
else if (hash.startsWith('#/schedules'))
|
|
12
|
+
renderSchedulesPage(root);
|
|
13
|
+
else
|
|
14
|
+
renderSessionsPage(root);
|
|
15
|
+
// active nav highlighting
|
|
16
|
+
for (const a of document.querySelectorAll('header nav a')) {
|
|
17
|
+
a.classList.toggle('active', a.getAttribute('href') === (hash || '#/') ||
|
|
18
|
+
(hash === '#/' && a.dataset.route === 'sessions'));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const statusEl = document.getElementById('status');
|
|
22
|
+
function paintStatus() {
|
|
23
|
+
if (!statusEl)
|
|
24
|
+
return;
|
|
25
|
+
statusEl.textContent = store.online ? '● live' : '● disconnected';
|
|
26
|
+
statusEl.className = 'status ' + (store.online ? 'online' : 'offline');
|
|
27
|
+
}
|
|
28
|
+
store.on(paintStatus);
|
|
29
|
+
paintStatus();
|
|
30
|
+
// esbuild's IIFE bundle does not support top-level await — use an async IIFE.
|
|
31
|
+
void (async () => {
|
|
32
|
+
try {
|
|
33
|
+
await bootstrap();
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error('botmux dashboard bootstrap failed', err);
|
|
37
|
+
store.setOnline(false);
|
|
38
|
+
}
|
|
39
|
+
window.addEventListener('hashchange', route);
|
|
40
|
+
route();
|
|
41
|
+
})();
|
|
42
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../../src/dashboard/web/app.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAE,CAAC;AAE9C,SAAS,KAAK;IACZ,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC;IACnC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;SACnD,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;;QAC9D,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAE9B,0BAA0B;IAC1B,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,gBAAgB,CAAoB,cAAc,CAAC,EAAE,CAAC;QAC7E,CAAC,CAAC,SAAS,CAAC,MAAM,CAChB,QAAQ,EACR,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC;YACvC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,CACpD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;AACnD,SAAS,WAAW;IAClB,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,QAAQ,CAAC,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAClE,QAAQ,CAAC,SAAS,GAAG,SAAS,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AACzE,CAAC;AACD,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;AACtB,WAAW,EAAE,CAAC;AAEd,8EAA8E;AAC9E,KAAK,CAAC,KAAK,IAAI,EAAE;IACf,IAAI,CAAC;QACH,MAAM,SAAS,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,GAAG,CAAC,CAAC;QACxD,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IACD,MAAM,CAAC,gBAAgB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAC7C,KAAK,EAAE,CAAC;AACV,CAAC,CAAC,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../../src/dashboard/web/groups.ts"],"names":[],"mappings":"AA6BA,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,WAAW,iBA4HvD"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Groups & Bots page: chat × bot membership matrix + add-bots modal.
|
|
2
|
+
// The aggregator at /api/groups fans out to all online daemons and merges chats
|
|
3
|
+
// by chatId; the dashboard displays this as a matrix where each cell shows
|
|
4
|
+
// whether a bot is a member of a given chat.
|
|
5
|
+
let cache = { chats: [], bots: [] };
|
|
6
|
+
const PAGE_HTML = `
|
|
7
|
+
<form id="g-filters" class="filters">
|
|
8
|
+
<input type="search" name="q" placeholder="search chat name / id / owner" />
|
|
9
|
+
<label><input type="checkbox" name="missing"> missing-bot only</label>
|
|
10
|
+
<button type="button" id="g-refresh">Refresh</button>
|
|
11
|
+
</form>
|
|
12
|
+
<table>
|
|
13
|
+
<thead id="g-head"></thead>
|
|
14
|
+
<tbody id="g-body"></tbody>
|
|
15
|
+
</table>
|
|
16
|
+
<dialog id="g-drawer"></dialog>
|
|
17
|
+
`;
|
|
18
|
+
function escapeHtml(s) {
|
|
19
|
+
return s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
20
|
+
}
|
|
21
|
+
async function loadGroups() {
|
|
22
|
+
const r = await fetch('/api/groups');
|
|
23
|
+
cache = await r.json();
|
|
24
|
+
}
|
|
25
|
+
export async function renderGroupsPage(root) {
|
|
26
|
+
root.innerHTML = PAGE_HTML;
|
|
27
|
+
const head = root.querySelector('#g-head');
|
|
28
|
+
const body = root.querySelector('#g-body');
|
|
29
|
+
const form = root.querySelector('#g-filters');
|
|
30
|
+
const refreshBtn = root.querySelector('#g-refresh');
|
|
31
|
+
const drawer = root.querySelector('#g-drawer');
|
|
32
|
+
refreshBtn.onclick = async () => {
|
|
33
|
+
refreshBtn.disabled = true;
|
|
34
|
+
try {
|
|
35
|
+
await loadGroups();
|
|
36
|
+
rerender();
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
refreshBtn.disabled = false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
await loadGroups();
|
|
43
|
+
function renderHead() {
|
|
44
|
+
head.innerHTML = `<tr>
|
|
45
|
+
<th>chat</th>
|
|
46
|
+
${cache.bots.map(b => `<th>${escapeHtml(b.botName ?? b.larkAppId)}</th>`).join('')}
|
|
47
|
+
<th>actions</th>
|
|
48
|
+
</tr>`;
|
|
49
|
+
}
|
|
50
|
+
function rerender() {
|
|
51
|
+
renderHead();
|
|
52
|
+
const f = new FormData(form);
|
|
53
|
+
const q = (f.get('q') ?? '').toLowerCase();
|
|
54
|
+
const onlyMissing = !!f.get('missing');
|
|
55
|
+
const filtered = cache.chats
|
|
56
|
+
.filter(c => !q ||
|
|
57
|
+
(c.name ?? '').toLowerCase().includes(q) ||
|
|
58
|
+
c.chatId.toLowerCase().includes(q) ||
|
|
59
|
+
(c.ownerId ?? '').toLowerCase().includes(q))
|
|
60
|
+
.filter(c => !onlyMissing || c.memberBots.some((m) => !m.inChat));
|
|
61
|
+
if (filtered.length === 0) {
|
|
62
|
+
body.innerHTML = `<tr><td colspan="${cache.bots.length + 2}" class="empty">No chats match the filter.</td></tr>`;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
body.innerHTML = filtered.map(c => `<tr data-chat="${escapeHtml(c.chatId)}">
|
|
66
|
+
<td>
|
|
67
|
+
<strong>${escapeHtml(c.name ?? c.chatId)}</strong><br>
|
|
68
|
+
<small><code>${escapeHtml(c.chatId)}</code></small>
|
|
69
|
+
</td>
|
|
70
|
+
${cache.bots.map(b => {
|
|
71
|
+
const m = c.memberBots.find((m) => m.larkAppId === b.larkAppId);
|
|
72
|
+
const cell = !m ? '?' : m.error ? '!' : m.inChat ? '✓' : '✗';
|
|
73
|
+
const cls = !m ? 'cell-unknown' : m.error ? 'cell-error' : m.inChat ? 'cell-in' : 'cell-out';
|
|
74
|
+
return `<td class="${cls}" title="${escapeHtml(m?.error ?? '')}">${cell}</td>`;
|
|
75
|
+
}).join('')}
|
|
76
|
+
<td><button class="add-bots" type="button">Add bots</button></td>
|
|
77
|
+
</tr>`).join('');
|
|
78
|
+
}
|
|
79
|
+
rerender();
|
|
80
|
+
body.addEventListener('click', async (e) => {
|
|
81
|
+
const btn = e.target.closest('button.add-bots');
|
|
82
|
+
if (!btn)
|
|
83
|
+
return;
|
|
84
|
+
const tr = btn.closest('tr[data-chat]');
|
|
85
|
+
const chatId = tr.dataset.chat;
|
|
86
|
+
const chat = cache.chats.find(c => c.chatId === chatId);
|
|
87
|
+
if (!chat)
|
|
88
|
+
return;
|
|
89
|
+
const missing = chat.memberBots.filter((m) => !m.inChat);
|
|
90
|
+
if (!missing.length) {
|
|
91
|
+
alert('All configured bots are already in this chat.');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
drawer.innerHTML = `
|
|
95
|
+
<article>
|
|
96
|
+
<header><h3>Add bots to ${escapeHtml(chat.name ?? chat.chatId)}</h3></header>
|
|
97
|
+
<p>Select bots to add. The dashboard will pick a bot that's already in the chat as the proxy.</p>
|
|
98
|
+
<form id="g-addform">
|
|
99
|
+
${missing.map((m) => `
|
|
100
|
+
<label class="checkbox-row">
|
|
101
|
+
<input type="checkbox" name="bot" value="${escapeHtml(m.larkAppId)}">
|
|
102
|
+
${escapeHtml(m.botName ?? m.larkAppId)} <small>(${escapeHtml(m.larkAppId)})</small>
|
|
103
|
+
</label>
|
|
104
|
+
`).join('')}
|
|
105
|
+
<div class="actions">
|
|
106
|
+
<button type="submit">Confirm add</button>
|
|
107
|
+
<button type="button" id="g-cancel">Cancel</button>
|
|
108
|
+
</div>
|
|
109
|
+
</form>
|
|
110
|
+
</article>`;
|
|
111
|
+
drawer.showModal();
|
|
112
|
+
drawer.querySelector('#g-cancel').onclick = () => drawer.close();
|
|
113
|
+
drawer.querySelector('#g-addform').onsubmit = async (ev) => {
|
|
114
|
+
ev.preventDefault();
|
|
115
|
+
const fd = new FormData(ev.target);
|
|
116
|
+
const ids = fd.getAll('bot');
|
|
117
|
+
if (ids.length === 0) {
|
|
118
|
+
alert('Pick at least one bot.');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const r = await fetch(`/api/groups/${encodeURIComponent(chatId)}/add-bots`, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: { 'content-type': 'application/json' },
|
|
125
|
+
body: JSON.stringify({ larkAppIds: ids }),
|
|
126
|
+
});
|
|
127
|
+
const respBody = await r.json();
|
|
128
|
+
if (respBody.error === 'no_proxy_bot') {
|
|
129
|
+
alert('No bot is currently in this chat — add one manually in Feishu first, then retry.');
|
|
130
|
+
}
|
|
131
|
+
else if (respBody.result) {
|
|
132
|
+
const lines = respBody.result.map((x) => `${x.id}: ${x.ok ? 'OK' : `failed (${x.error ?? 'unknown'})`}`).join('\n');
|
|
133
|
+
alert(lines);
|
|
134
|
+
// Refresh after change
|
|
135
|
+
await loadGroups();
|
|
136
|
+
rerender();
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
alert(`Unexpected response: ${JSON.stringify(respBody)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
alert('Network error: ' + e);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
drawer.close();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
form.addEventListener('input', rerender);
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=groups.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"groups.js","sourceRoot":"","sources":["../../../src/dashboard/web/groups.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedules.d.ts","sourceRoot":"","sources":["../../../src/dashboard/web/schedules.ts"],"names":[],"mappings":"AAkCA,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,QAoEpD"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
const PAGE_HTML = `
|
|
3
|
+
<form id="sched-filters" class="filters">
|
|
4
|
+
<input type="search" name="q" placeholder="search name / prompt / workingDir" />
|
|
5
|
+
<select name="kind">
|
|
6
|
+
<option value="">any kind</option>
|
|
7
|
+
<option>cron</option>
|
|
8
|
+
<option>interval</option>
|
|
9
|
+
<option>once</option>
|
|
10
|
+
</select>
|
|
11
|
+
<label><input type="checkbox" name="enabled"> enabled only</label>
|
|
12
|
+
</form>
|
|
13
|
+
<table>
|
|
14
|
+
<thead><tr>
|
|
15
|
+
<th>name</th><th>bot</th><th>schedule</th><th>next</th><th>last</th>
|
|
16
|
+
<th>repeat</th><th>enabled</th><th>actions</th>
|
|
17
|
+
</tr></thead>
|
|
18
|
+
<tbody id="schedules-tbody"></tbody>
|
|
19
|
+
</table>
|
|
20
|
+
`;
|
|
21
|
+
function escapeHtml(s) {
|
|
22
|
+
return s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
23
|
+
}
|
|
24
|
+
function fmtDate(s) {
|
|
25
|
+
if (!s)
|
|
26
|
+
return '—';
|
|
27
|
+
try {
|
|
28
|
+
const d = new Date(s);
|
|
29
|
+
return d.toLocaleString();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return s;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function renderSchedulesPage(root) {
|
|
36
|
+
root.innerHTML = PAGE_HTML;
|
|
37
|
+
const tbody = root.querySelector('#schedules-tbody');
|
|
38
|
+
const form = root.querySelector('#sched-filters');
|
|
39
|
+
function filtered() {
|
|
40
|
+
const f = new FormData(form);
|
|
41
|
+
const q = (f.get('q') ?? '').toLowerCase();
|
|
42
|
+
const kind = f.get('kind');
|
|
43
|
+
const enabledOnly = !!f.get('enabled');
|
|
44
|
+
return [...store.schedules.values()]
|
|
45
|
+
.filter(s => !kind || s.parsed?.kind === kind)
|
|
46
|
+
.filter(s => !enabledOnly || s.enabled)
|
|
47
|
+
.filter(s => !q || JSON.stringify(s).toLowerCase().includes(q))
|
|
48
|
+
.sort((a, b) => {
|
|
49
|
+
// enabled first, then earliest nextRunAt
|
|
50
|
+
if (a.enabled !== b.enabled)
|
|
51
|
+
return a.enabled ? -1 : 1;
|
|
52
|
+
const aN = a.nextRunAt ? Date.parse(a.nextRunAt) : Infinity;
|
|
53
|
+
const bN = b.nextRunAt ? Date.parse(b.nextRunAt) : Infinity;
|
|
54
|
+
return aN - bN;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function rerender() {
|
|
58
|
+
tbody.innerHTML = filtered().map(s => `<tr data-id="${escapeHtml(s.id)}">
|
|
59
|
+
<td>${escapeHtml(s.name ?? s.id)}</td>
|
|
60
|
+
<td>${escapeHtml(s.botName ?? s.larkAppId ?? '-')}</td>
|
|
61
|
+
<td><code>${escapeHtml(s.parsed?.display ?? '?')}</code></td>
|
|
62
|
+
<td>${fmtDate(s.nextRunAt)}</td>
|
|
63
|
+
<td>${fmtDate(s.lastRunAt)} ${s.lastStatus === 'error' ? '⚠️' : ''}</td>
|
|
64
|
+
<td>${s.repeat ? `${s.repeat.completed}/${s.repeat.times ?? '∞'}` : '—'}</td>
|
|
65
|
+
<td>${s.enabled ? '✓' : '✗'}</td>
|
|
66
|
+
<td class="actions-cell">
|
|
67
|
+
<button data-op="run" type="button">Run now</button>
|
|
68
|
+
${s.enabled
|
|
69
|
+
? `<button data-op="pause" type="button">Pause</button>`
|
|
70
|
+
: `<button data-op="resume" type="button">Resume</button>`}
|
|
71
|
+
</td>
|
|
72
|
+
</tr>`).join('') || '<tr><td colspan="8" class="empty">No schedules.</td></tr>';
|
|
73
|
+
}
|
|
74
|
+
tbody.addEventListener('click', async (e) => {
|
|
75
|
+
const btn = e.target.closest('button[data-op]');
|
|
76
|
+
if (!btn)
|
|
77
|
+
return;
|
|
78
|
+
const tr = btn.closest('tr[data-id]');
|
|
79
|
+
if (!tr)
|
|
80
|
+
return;
|
|
81
|
+
const id = tr.dataset.id;
|
|
82
|
+
const op = btn.dataset.op;
|
|
83
|
+
btn.disabled = true;
|
|
84
|
+
const original = btn.textContent;
|
|
85
|
+
btn.textContent = '...';
|
|
86
|
+
try {
|
|
87
|
+
const r = await fetch(`/api/schedules/${encodeURIComponent(id)}/${op}`, { method: 'POST' });
|
|
88
|
+
const body = await r.json().catch(() => ({}));
|
|
89
|
+
if (!r.ok || body.ok === false) {
|
|
90
|
+
alert(`Failed: ${r.status} ${body?.error ?? ''}`.trim());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
alert('Network error: ' + err);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
btn.disabled = false;
|
|
98
|
+
btn.textContent = original;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
form.addEventListener('input', rerender);
|
|
102
|
+
store.on(rerender);
|
|
103
|
+
rerender();
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=schedules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedules.js","sourceRoot":"","sources":["../../../src/dashboard/web/schedules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/dashboard/web/sessions.ts"],"names":[],"mappings":"AAsDA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,QAmInD"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Sessions page: filter bar, table, detail drawer with locate (30s cooldown) + close.
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
const PAGE_HTML = `
|
|
4
|
+
<form id="filters" class="filters">
|
|
5
|
+
<input type="search" name="q" placeholder="search workingDir / title / ids" />
|
|
6
|
+
<select name="cli" multiple size="4">
|
|
7
|
+
<option value="claude-code">claude-code</option>
|
|
8
|
+
<option value="codex">codex</option>
|
|
9
|
+
<option value="gemini">gemini</option>
|
|
10
|
+
<option value="opencode">opencode</option>
|
|
11
|
+
<option value="aiden">aiden</option>
|
|
12
|
+
<option value="coco">coco</option>
|
|
13
|
+
<option value="unknown">unknown</option>
|
|
14
|
+
</select>
|
|
15
|
+
<select name="status">
|
|
16
|
+
<option value="">any status</option>
|
|
17
|
+
<option>starting</option><option>working</option>
|
|
18
|
+
<option>idle</option><option>analyzing</option><option>closed</option>
|
|
19
|
+
</select>
|
|
20
|
+
<select name="adopt">
|
|
21
|
+
<option value="">adopt: any</option>
|
|
22
|
+
<option value="yes">adopt: yes</option>
|
|
23
|
+
<option value="no">adopt: no</option>
|
|
24
|
+
</select>
|
|
25
|
+
<label><input type="checkbox" name="active" checked> active only</label>
|
|
26
|
+
</form>
|
|
27
|
+
<table id="sessions-table">
|
|
28
|
+
<thead><tr>
|
|
29
|
+
<th>bot</th><th>cli</th><th>status</th><th>title</th><th>workingDir</th>
|
|
30
|
+
<th>spawned</th><th>last</th><th>adopt</th><th></th>
|
|
31
|
+
</tr></thead>
|
|
32
|
+
<tbody></tbody>
|
|
33
|
+
</table>
|
|
34
|
+
<dialog id="drawer"></dialog>
|
|
35
|
+
`;
|
|
36
|
+
function relTime(ms) {
|
|
37
|
+
if (!ms)
|
|
38
|
+
return '-';
|
|
39
|
+
const diff = Date.now() - ms;
|
|
40
|
+
if (diff < 60_000)
|
|
41
|
+
return 'now';
|
|
42
|
+
if (diff < 3_600_000)
|
|
43
|
+
return Math.floor(diff / 60_000) + 'm';
|
|
44
|
+
if (diff < 86_400_000)
|
|
45
|
+
return Math.floor(diff / 3_600_000) + 'h';
|
|
46
|
+
return Math.floor(diff / 86_400_000) + 'd';
|
|
47
|
+
}
|
|
48
|
+
function escapeHtml(s) {
|
|
49
|
+
return s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
50
|
+
}
|
|
51
|
+
const ICON_MIRROR = '\u{1FA9E}';
|
|
52
|
+
const ICON_PIN = '\u{1F4CD}';
|
|
53
|
+
const ICON_SCREEN = '\u{1F5A5}️';
|
|
54
|
+
export function renderSessionsPage(root) {
|
|
55
|
+
root.innerHTML = PAGE_HTML;
|
|
56
|
+
const tbody = root.querySelector('#sessions-table tbody');
|
|
57
|
+
const filtersForm = root.querySelector('#filters');
|
|
58
|
+
const drawer = root.querySelector('#drawer');
|
|
59
|
+
function rowHtml(s) {
|
|
60
|
+
return `<tr data-id="${escapeHtml(s.sessionId)}">
|
|
61
|
+
<td>${escapeHtml(s.botName ?? '')}</td>
|
|
62
|
+
<td><span class="badge cli-${escapeHtml(s.cliId ?? 'unknown')}">${escapeHtml(s.cliId ?? 'unknown')}</span></td>
|
|
63
|
+
<td><span class="status status-${escapeHtml(s.status)}">${escapeHtml(s.status)}</span></td>
|
|
64
|
+
<td>${escapeHtml((s.title ?? '').slice(0, 40))}</td>
|
|
65
|
+
<td title="${escapeHtml(s.workingDir ?? '')}">${escapeHtml((s.workingDir ?? '').slice(-30))}</td>
|
|
66
|
+
<td>${relTime(s.spawnedAt)}</td>
|
|
67
|
+
<td>${relTime(s.lastMessageAt)}</td>
|
|
68
|
+
<td>${s.adopt ? ICON_MIRROR : ''}</td>
|
|
69
|
+
<td><button class="open">⋯</button></td>
|
|
70
|
+
</tr>`;
|
|
71
|
+
}
|
|
72
|
+
function filtered() {
|
|
73
|
+
const f = new FormData(filtersForm);
|
|
74
|
+
const q = (f.get('q') ?? '').toLowerCase();
|
|
75
|
+
const cli = f.getAll('cli');
|
|
76
|
+
const status = f.get('status');
|
|
77
|
+
const adopt = f.get('adopt');
|
|
78
|
+
const active = !!f.get('active');
|
|
79
|
+
return [...store.sessions.values()]
|
|
80
|
+
.filter(s => !cli.length || cli.includes(s.cliId ?? 'unknown'))
|
|
81
|
+
.filter(s => !status || s.status === status)
|
|
82
|
+
.filter(s => !adopt || (adopt === 'yes') === !!s.adopt)
|
|
83
|
+
.filter(s => !active || s.status !== 'closed')
|
|
84
|
+
.filter(s => !q || JSON.stringify(s).toLowerCase().includes(q))
|
|
85
|
+
.sort((a, b) => (b.lastMessageAt ?? 0) - (a.lastMessageAt ?? 0));
|
|
86
|
+
}
|
|
87
|
+
function rerender() {
|
|
88
|
+
tbody.innerHTML = filtered().map(rowHtml).join('');
|
|
89
|
+
}
|
|
90
|
+
function openDrawer(s) {
|
|
91
|
+
const closed = s.status === 'closed';
|
|
92
|
+
drawer.innerHTML = `
|
|
93
|
+
<article>
|
|
94
|
+
<header>
|
|
95
|
+
<h3>${escapeHtml(s.title ?? s.sessionId)}</h3>
|
|
96
|
+
<code>${escapeHtml(s.sessionId)}</code> <button data-copy="${escapeHtml(s.sessionId)}">copy</button>
|
|
97
|
+
</header>
|
|
98
|
+
<p><b>bot:</b> ${escapeHtml(s.botName ?? '-')} · <b>cli:</b> ${escapeHtml(s.cliId ?? '?')} · <b>status:</b> ${escapeHtml(s.status)}</p>
|
|
99
|
+
<p><b>chatId:</b> <code>${escapeHtml(s.chatId)}</code> <button data-copy="${escapeHtml(s.chatId)}">copy</button></p>
|
|
100
|
+
<p><b>rootMessageId:</b> <code>${escapeHtml(s.rootMessageId ?? '')}</code> <button data-copy="${escapeHtml(s.rootMessageId ?? '')}">copy</button></p>
|
|
101
|
+
${s.threadId ? `<p><b>threadId:</b> <code>${escapeHtml(s.threadId)}</code></p>` : ''}
|
|
102
|
+
<p><b>workingDir:</b> ${escapeHtml(s.workingDir ?? '-')}</p>
|
|
103
|
+
<div class="actions">
|
|
104
|
+
<button id="locate-btn" type="button">${ICON_PIN} 定位到飞书话题</button>
|
|
105
|
+
${s.webPort ? `<a class="btn-link" href="http://${escapeHtml(location.hostname)}:${s.webPort}" target="_blank">${ICON_SCREEN} 打开 xterm</a>` : ''}
|
|
106
|
+
${!closed ? `<button id="close-btn" type="button" class="contrast">关闭会话</button>` : ''}
|
|
107
|
+
</div>
|
|
108
|
+
<form method="dialog"><button>关闭</button></form>
|
|
109
|
+
</article>`;
|
|
110
|
+
drawer.querySelectorAll('[data-copy]').forEach(b => {
|
|
111
|
+
b.onclick = () => {
|
|
112
|
+
navigator.clipboard.writeText(b.dataset.copy ?? '');
|
|
113
|
+
b.textContent = 'copied';
|
|
114
|
+
setTimeout(() => { b.textContent = 'copy'; }, 800);
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
const locateBtn = drawer.querySelector('#locate-btn');
|
|
118
|
+
if (locateBtn) {
|
|
119
|
+
locateBtn.onclick = async () => {
|
|
120
|
+
locateBtn.disabled = true;
|
|
121
|
+
locateBtn.textContent = `${ICON_PIN} 发送中...`;
|
|
122
|
+
try {
|
|
123
|
+
const r = await fetch(`/api/sessions/${encodeURIComponent(s.sessionId)}/locate`, { method: 'POST' });
|
|
124
|
+
const body = await r.json();
|
|
125
|
+
if (body.ok) {
|
|
126
|
+
window.open(s.feishuChatLink, '_blank');
|
|
127
|
+
let left = 30;
|
|
128
|
+
locateBtn.textContent = `${ICON_PIN} (冷却 ${left}s)`;
|
|
129
|
+
const tick = setInterval(() => {
|
|
130
|
+
left -= 1;
|
|
131
|
+
if (left <= 0) {
|
|
132
|
+
clearInterval(tick);
|
|
133
|
+
locateBtn.disabled = false;
|
|
134
|
+
locateBtn.textContent = `${ICON_PIN} 定位到飞书话题`;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
locateBtn.textContent = `${ICON_PIN} (冷却 ${left}s)`;
|
|
138
|
+
}
|
|
139
|
+
}, 1000);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const reason = body.error ?? r.status;
|
|
143
|
+
alert('Locate failed: ' + reason);
|
|
144
|
+
locateBtn.disabled = false;
|
|
145
|
+
locateBtn.textContent = `${ICON_PIN} 定位到飞书话题`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
alert('Locate error: ' + e);
|
|
150
|
+
locateBtn.disabled = false;
|
|
151
|
+
locateBtn.textContent = `${ICON_PIN} 定位到飞书话题`;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const closeBtn = drawer.querySelector('#close-btn');
|
|
156
|
+
if (closeBtn) {
|
|
157
|
+
closeBtn.onclick = async () => {
|
|
158
|
+
if (!confirm('关闭这个会话?'))
|
|
159
|
+
return;
|
|
160
|
+
closeBtn.disabled = true;
|
|
161
|
+
try {
|
|
162
|
+
await fetch(`/api/sessions/${encodeURIComponent(s.sessionId)}/close`, { method: 'POST' });
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
drawer.close();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
drawer.showModal();
|
|
170
|
+
}
|
|
171
|
+
tbody.addEventListener('click', e => {
|
|
172
|
+
const tr = e.target.closest('tr[data-id]');
|
|
173
|
+
if (!tr)
|
|
174
|
+
return;
|
|
175
|
+
const sid = tr.dataset.id;
|
|
176
|
+
const s = store.sessions.get(sid);
|
|
177
|
+
if (s)
|
|
178
|
+
openDrawer(s);
|
|
179
|
+
});
|
|
180
|
+
filtersForm.addEventListener('input', rerender);
|
|
181
|
+
store.on(rerender);
|
|
182
|
+
rerender();
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/dashboard/web/sessions.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCjB,CAAC;AAEF,SAAS,OAAO,CAAC,EAAU;IACzB,IAAI,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAC7B,IAAI,IAAI,GAAG,MAAM;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,IAAI,GAAG,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC;IAC7D,IAAI,IAAI,GAAG,UAAU;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC;IACjE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,UAAU,CAAC,GAAG,GAAG,CAAC;AAC7C,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAC,OAAO,EAAC,GAAG,EAAC,MAAM,EAAC,GAAG,EAAC,MAAM,EAAC,GAAG,EAAC,QAAQ,EAAC,GAAG,EAAC,OAAO,EAAE,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;AAC1G,CAAC;AAED,MAAM,WAAW,GAAG,WAAW,CAAC;AAChC,MAAM,QAAQ,GAAG,WAAW,CAAC;AAC7B,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,MAAM,UAAU,kBAAkB,CAAC,IAAiB;IAClD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAc,uBAAuB,CAAE,CAAC;IACxE,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAkB,UAAU,CAAE,CAAC;IACrE,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAoB,SAAS,CAAE,CAAC;IAEjE,SAAS,OAAO,CAAC,CAAM;QACrB,OAAO,gBAAgB,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;YACtC,UAAU,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;mCACJ,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC;uCACjE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;YACxE,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;mBACjC,UAAU,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;YACrF,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;YACpB,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;YACxB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE;;UAE5B,CAAC;IACT,CAAC;IAED,SAAS,QAAQ;QACf,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,CAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAY,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACvD,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAa,CAAC;QACxC,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAW,CAAC;QACzC,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAW,CAAC;QACvC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACjC,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;aAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;aAC9D,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC;aAC3C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;aACtD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC;aAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;aAC9D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,SAAS,QAAQ;QACf,KAAK,CAAC,SAAS,GAAG,QAAQ,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,SAAS,UAAU,CAAC,CAAM;QACxB,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC;QACrC,MAAM,CAAC,SAAS,GAAG;;;gBAGP,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS,CAAC;kBAChC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,8BAA8B,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;;yBAErE,UAAU,CAAC,CAAC,CAAC,OAAO,IAAI,GAAG,CAAC,kBAAkB,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,qBAAqB,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;kCACxG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,8BAA8B,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;yCAC/D,UAAU,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,8BAA8B,UAAU,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC;UAC/H,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,6BAA6B,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE;gCAC5D,UAAU,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC;;kDAEb,QAAQ;YAC9C,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,oCAAoC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,qBAAqB,WAAW,eAAe,CAAC,CAAC,CAAC,EAAE;YAC9I,CAAC,MAAM,CAAC,CAAC,CAAC,qEAAqE,CAAC,CAAC,CAAC,EAAE;;;iBAG/E,CAAC;QAEd,MAAM,CAAC,gBAAgB,CAAoB,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACpE,CAAC,CAAC,OAAO,GAAG,GAAG,EAAE;gBACf,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBACpD,CAAC,CAAC,WAAW,GAAG,QAAQ,CAAC;gBACzB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACrD,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,MAAM,CAAC,aAAa,CAAoB,aAAa,CAAC,CAAC;QACzE,IAAI,SAAS,EAAE,CAAC;YACd,SAAS,CAAC,OAAO,GAAG,KAAK,IAAI,EAAE;gBAC7B,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC;gBAC1B,SAAS,CAAC,WAAW,GAAG,GAAG,QAAQ,SAAS,CAAC;gBAC7C,IAAI,CAAC;oBACH,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,iBAAiB,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;oBACrG,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC5B,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;wBACZ,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;wBACxC,IAAI,IAAI,GAAG,EAAE,CAAC;wBACd,SAAS,CAAC,WAAW,GAAG,GAAG,QAAQ,QAAQ,IAAI,IAAI,CAAC;wBACpD,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;4BAC5B,IAAI,IAAI,CAAC,CAAC;4BACV,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;gCACd,aAAa,CAAC,IAAI,CAAC,CAAC;gCACpB,SAAS,CAAC,QAAQ,GAAG,KAAK,CAAC;gCAC3B,SAAS,CAAC,WAAW,GAAG,GAAG,QAAQ,UAAU,CAAC;4BAChD,CAAC;iCAAM,CAAC;gCACN,SAAS,CAAC,WAAW,GAAG,GAAG,QAAQ,QAAQ,IAAI,IAAI,CAAC;4BACtD,CAAC;wBACH,CAAC,EAAE,IAAI,CAAC,CAAC;oBACX,CAAC;yBAAM,CAAC;wBACN,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;wBACtC,KAAK,CAAC,iBAAiB,GAAG,MAAM,CAAC,CAAC;wBAClC,SAAS,CAAC,QAAQ,GAAG,KAAK,CAAC;wBAC3B,SAAS,CAAC,WAAW,GAAG,GAAG,QAAQ,UAAU,CAAC;oBAChD,CAAC;gBACH,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,KAAK,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC;oBAC5B,SAAS,CAAC,QAAQ,GAAG,KAAK,CAAC;oBAC3B,SAAS,CAAC,WAAW,GAAG,GAAG,QAAQ,UAAU,CAAC;gBAChD,CAAC;YACH,CAAC,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,aAAa,CAAoB,YAAY,CAAC,CAAC;QACvE,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,OAAO,GAAG,KAAK,IAAI,EAAE;gBAC5B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;oBAAE,OAAO;gBAChC,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACzB,IAAI,CAAC;oBACH,MAAM,KAAK,CAAC,iBAAiB,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC5F,CAAC;wBAAS,CAAC;oBACT,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,CAAC;YACH,CAAC,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,SAAS,EAAE,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE;QAClC,MAAM,EAAE,GAAI,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAsB,aAAa,CAAC,CAAC;QACjF,IAAI,CAAC,EAAE;YAAE,OAAO;QAChB,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,EAAG,CAAC;QAC3B,MAAM,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC;YAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IACH,WAAW,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAChD,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;IACnB,QAAQ,EAAE,CAAC;AACb,CAAC"}
|