viberoom 0.5.8 → 0.6.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.md +20 -0
- package/dist/agent-health.js +52 -0
- package/dist/context.js +6 -0
- package/dist/duration.js +12 -0
- package/dist/hub.js +13 -2
- package/dist/launcher.js +19 -2
- package/dist/main.js +77 -34
- package/dist/mcp-skills-server.js +143 -0
- package/dist/persona.js +167 -46
- package/dist/quotes.js +41 -0
- package/dist/recipes.js +28 -1
- package/dist/room-design.js +205 -0
- package/dist/room.js +388 -150
- package/dist/rows.js +19 -0
- package/dist/server.js +304 -27
- package/dist/skills.js +28 -2
- package/dist/templates.js +17 -0
- package/dist/update.js +32 -2
- package/dist/viewer.js +69 -2
- package/dist/ws.js +175 -0
- package/package.json +2 -1
- package/ui/app.css +348 -246
- package/ui/app.js +1422 -345
- package/ui/avatars.js +131 -30
- package/ui/components.css +140 -0
- package/ui/components.js +342 -0
- package/ui/icons.js +11 -0
- package/ui/index.html +58 -36
- package/ui/theme.css +457 -180
- package/ui/tokens.js +620 -0
- package/ui/ui.js +113 -0
package/ui/ui.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// viberoom - Copyright (c) 2026 Todor Rusev - AGPL-3.0-or-later; see LICENSE
|
|
2
|
+
(() => {
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const ESC = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
6
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (c) => ESC[c]);
|
|
7
|
+
const VOID = new Set(["br", "hr", "img", "input", "meta", "link", "wbr"]);
|
|
8
|
+
|
|
9
|
+
class Raw {
|
|
10
|
+
constructor(html) {
|
|
11
|
+
this.html = String(html ?? "");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const raw = (html) => new Raw(html);
|
|
15
|
+
|
|
16
|
+
class UiNode {
|
|
17
|
+
constructor(tag, attrs, children) {
|
|
18
|
+
this.tag = tag;
|
|
19
|
+
this.attrs = attrs;
|
|
20
|
+
this.children = children;
|
|
21
|
+
}
|
|
22
|
+
toString() {
|
|
23
|
+
const attrs = Object.entries(this.attrs)
|
|
24
|
+
.filter(([, value]) => value !== null && value !== undefined && value !== false)
|
|
25
|
+
.map(([key, value]) => (value === true ? ` ${key}` : ` ${key}="${esc(value)}"`))
|
|
26
|
+
.join("");
|
|
27
|
+
if (VOID.has(this.tag)) return `<${this.tag}${attrs}>`;
|
|
28
|
+
return `<${this.tag}${attrs}>${this.children.map(renderChild).join("")}</${this.tag}>`;
|
|
29
|
+
}
|
|
30
|
+
toElement() {
|
|
31
|
+
const template = document.createElement("template");
|
|
32
|
+
template.innerHTML = String(this);
|
|
33
|
+
return template.content.firstElementChild;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function renderChild(child) {
|
|
37
|
+
if (child === null || child === undefined || child === false) return "";
|
|
38
|
+
if (child instanceof UiNode) return String(child);
|
|
39
|
+
if (child instanceof Raw) return child.html;
|
|
40
|
+
return esc(child);
|
|
41
|
+
}
|
|
42
|
+
const h = (tag, attrs, ...children) => new UiNode(tag, attrs || {}, children.flat(Infinity));
|
|
43
|
+
|
|
44
|
+
const registry = new Map();
|
|
45
|
+
|
|
46
|
+
function define(name, spec) {
|
|
47
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`a UI type is named in lower-case words joined by dashes, not "${name}"`);
|
|
48
|
+
if (registry.has(name)) throw new Error(`the UI type "${name}" is defined twice`);
|
|
49
|
+
if (typeof spec.build !== "function") throw new Error(`the UI type "${name}" has no build function`);
|
|
50
|
+
registry.set(name, {
|
|
51
|
+
name,
|
|
52
|
+
describe: spec.describe || "",
|
|
53
|
+
props: spec.props || {},
|
|
54
|
+
build: spec.build,
|
|
55
|
+
states: spec.states && spec.states.length ? spec.states : ["rest"],
|
|
56
|
+
samples: spec.samples || [],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function checked(spec, props) {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const [key, rule] of Object.entries(spec.props)) {
|
|
63
|
+
let value = props[key];
|
|
64
|
+
if (value === undefined) {
|
|
65
|
+
if (rule.required) throw new Error(`UI "${spec.name}": the prop "${key}" is required`);
|
|
66
|
+
value = rule.default;
|
|
67
|
+
}
|
|
68
|
+
if (value !== undefined && rule.values && !rule.values.includes(value)) throw new Error(`UI "${spec.name}": "${key}" takes ${rule.values.join(" | ")}, not "${value}"`);
|
|
69
|
+
out[key] = value;
|
|
70
|
+
}
|
|
71
|
+
for (const key of Object.keys(props)) if (!(key in spec.props)) throw new Error(`UI "${spec.name}" knows no prop "${key}"`);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function build(name, props = {}) {
|
|
76
|
+
const spec = registry.get(name);
|
|
77
|
+
if (!spec) throw new Error(`no UI type "${name}"`);
|
|
78
|
+
const node = spec.build(checked(spec, props), api);
|
|
79
|
+
if (!(node instanceof UiNode)) throw new Error(`UI "${name}" must build a node (use ui.h)`);
|
|
80
|
+
node.attrs = { "data-ui": name, ...node.attrs };
|
|
81
|
+
return node;
|
|
82
|
+
}
|
|
83
|
+
const html = (name, props) => String(build(name, props));
|
|
84
|
+
const el = (name, props) => build(name, props).toElement();
|
|
85
|
+
|
|
86
|
+
function on(container, name, handler, type = "click") {
|
|
87
|
+
container.addEventListener(type, (event) => {
|
|
88
|
+
const target = event.target.closest(`[data-ui="${name}"]`);
|
|
89
|
+
if (!target || !container.contains(target)) return;
|
|
90
|
+
handler(target.dataset.act, target, event);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const icon = (name, cls) => raw(globalThis.Icons.svg(name, cls));
|
|
95
|
+
|
|
96
|
+
function setState(element, state) {
|
|
97
|
+
if (!element) return;
|
|
98
|
+
if (state) element.dataset.state = state;
|
|
99
|
+
else delete element.dataset.state;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function dataAttrs(data) {
|
|
103
|
+
const out = {};
|
|
104
|
+
for (const [key, value] of Object.entries(data || {})) {
|
|
105
|
+
if (value === undefined || value === null || value === false) continue;
|
|
106
|
+
out[`data-${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`] = value === true ? true : String(value);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const api = { define, build, html, el, on, h, raw, esc, icon, setState, dataAttrs, types: () => [...registry.values()], type: (name) => registry.get(name) };
|
|
112
|
+
globalThis.UI = api;
|
|
113
|
+
})();
|