viberoom 0.5.7 โ 0.5.9
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 +19 -1
- package/dist/context.js +6 -0
- package/dist/hub.js +4 -2
- package/dist/main.js +3 -2
- package/dist/mcp-skills-server.js +119 -0
- package/dist/persona.js +98 -32
- package/dist/room-design.js +205 -0
- package/dist/room.js +253 -118
- package/dist/server.js +54 -1
- package/dist/shortcuts.js +10 -10
- package/dist/skills.js +28 -2
- package/dist/templates.js +17 -0
- package/dist/update.js +3 -3
- package/package.json +1 -1
- package/scripts/install.mjs +6 -1
- package/scripts/update.mjs +6 -1
- package/templates/maken-and-elaine/template.json +30 -0
- package/ui/app.css +13 -1
- package/ui/app.js +164 -92
- package/ui/avatars.js +125 -26
- package/ui/icons.js +2 -0
- package/ui/theme.css +12 -2
- package/templates/wren-and-quinn/template.json +0 -30
package/ui/app.js
CHANGED
|
@@ -162,38 +162,18 @@
|
|
|
162
162
|
return room.messages.some((m) => m.streaming && m.from === p.id) ? "writing" : "thinking";
|
|
163
163
|
}
|
|
164
164
|
const CHAT_EMOJI = ["๐", "๐", "๐", "๐", "๐", "๐", "๐ค", "๐", "๐ฅณ", "๐
", "๐ข", "๐ก", "๐", "๐", "๐", "๐", "๐", "๐ช", "๐ฅ", "โจ", "๐", "โค๏ธ", "๐", "โ
", "โ", "โ ๏ธ", "๐ก", "๐", "๐", "๐ค", "๐คซ", "โ"];
|
|
165
|
-
const ROOM_EMOJI = [
|
|
165
|
+
const ROOM_EMOJI = [
|
|
166
|
+
"๐ญ", "๐", "๐งช", "๐ ๏ธ", "๐จ", "๐", "๐ง ", "๐ฌ", "๐ฌ", "๐ฏ", "๐", "โ", "๐", "๐๏ธ", "๐ฎ", "๐ฅ", "๐งฉ", "๐", "๐บ๏ธ", "๐ง", "๐ฑ", "๐ ", "๐ธ", "๐งญ",
|
|
167
|
+
"๐งโ๐ป", "โ๏ธ", "๐จ", "๐ง", "๐ฉ", "โ๏ธ", "๐งฐ", "๐ช", "๐งฑ", "๐ญ", "๐", "๐ฅ๏ธ", "๐ป", "โจ๏ธ", "๐ค", "๐", "๐", "โก", "๐",
|
|
168
|
+
"๐", "๐", "๐งฎ", "๐", "๐ญ", "๐งฌ", "โ๏ธ", "๐งฒ", "๐ก", "๐ฐ๏ธ", "๐๏ธ", "๐พ", "๐๏ธ", "๐", "๐", "โ๏ธ",
|
|
169
|
+
"โ๏ธ", "๐", "๐", "๐ฐ", "๐", "๐", "๐", "๐๏ธ", "๐ท๏ธ", "โ๏ธ", "๐ฃ", "๐ฃ๏ธ", "๐ค", "๐ฅ",
|
|
170
|
+
"๐๏ธ", "๐ผ๏ธ", "๐ท", "๐ฌ", "๐ฅ", "๐ต", "๐น", "๐ธ", "๐ค", "๐ฒ", "โ๏ธ", "๐งธ", "๐ช", "๐",
|
|
171
|
+
"๐
", "โฐ", "โณ", "๐ณ๏ธ", "โ๏ธ", "๐งพ", "๐ฐ", "๐ฆ", "๐", "๐", "๐ฆ", "๐ข", "๐", "๐ซ", "๐", "๐", "๐",
|
|
172
|
+
"๐", "๐", "๐ก๏ธ", "๐จ", "๐ฆ", "๐งฏ", "๐ฉบ", "๐งน", "โป๏ธ", "๐งโ๐ณ", "๐งโ๐ฌ", "๐งโ๐จ", "๐งโ๐ซ", "๐งโ๐", "๐ต๏ธ", "๐ง",
|
|
173
|
+
"๐ฆ", "๐ฆ", "๐ผ", "๐", "๐ข", "๐ฌ", "๐ฆ", "๐ฒ", "๐", "๐", "โญ", "๐", "๐๏ธ", "๐๏ธ", "๐ฒ", "๐", "๐ธ", "๐", "๐", "โจ", "๐ก", "๐ฎ", "๐ช", "โค๏ธ",
|
|
174
|
+
];
|
|
166
175
|
function emojiGrid(list, current, onPick) {
|
|
167
|
-
|
|
168
|
-
wrap.className = "avatar-picker";
|
|
169
|
-
const render = (value) => {
|
|
170
|
-
wrap.innerHTML = "";
|
|
171
|
-
if (current !== null && current !== undefined) {
|
|
172
|
-
const none = document.createElement("button");
|
|
173
|
-
none.type = "button";
|
|
174
|
-
none.className = "none" + (!value ? " selected" : "");
|
|
175
|
-
none.textContent = "โ";
|
|
176
|
-
none.title = "No emoji";
|
|
177
|
-
none.addEventListener("click", () => {
|
|
178
|
-
onPick("");
|
|
179
|
-
render("");
|
|
180
|
-
});
|
|
181
|
-
wrap.appendChild(none);
|
|
182
|
-
}
|
|
183
|
-
for (const e of list) {
|
|
184
|
-
const b = document.createElement("button");
|
|
185
|
-
b.type = "button";
|
|
186
|
-
b.textContent = e;
|
|
187
|
-
b.className = e === value ? "selected" : "";
|
|
188
|
-
b.addEventListener("click", () => {
|
|
189
|
-
onPick(e);
|
|
190
|
-
render(e);
|
|
191
|
-
});
|
|
192
|
-
wrap.appendChild(b);
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
render(current || "");
|
|
196
|
-
return wrap;
|
|
176
|
+
return window.Avatars.searchableGrid(list, current, onPick, current !== null && current !== undefined ? { label: "โ", title: "No emoji" } : null);
|
|
197
177
|
}
|
|
198
178
|
const CLAMP_CHARS = 700;
|
|
199
179
|
|
|
@@ -627,23 +607,30 @@
|
|
|
627
607
|
function sectionTitle(iconName, text) {
|
|
628
608
|
return `<h4>${ic(iconName)}${text}</h4>`;
|
|
629
609
|
}
|
|
630
|
-
function saveRow(id) {
|
|
631
|
-
return `<div class="save-row"><button class="btn sm primary save" id="${id}" disabled>Save</button></div>`;
|
|
632
|
-
}
|
|
633
610
|
function geek(id, bodyHtml, hint) {
|
|
634
611
|
return `<details class="geek" id="${id}"><summary>${ic("geek")}for geeks${hint ? `<span class="g-hint">${hint}</span>` : ""}<span class="chev">${ic("down")}</span></summary><div class="geek-body">${bodyHtml}</div></details>`;
|
|
635
612
|
}
|
|
613
|
+
const SAVED_MARK_MS = 2000;
|
|
636
614
|
const recentlySaved = new Map();
|
|
637
|
-
function
|
|
638
|
-
|
|
615
|
+
function markSaved(fieldId, ms) {
|
|
616
|
+
const el = fieldId && document.getElementById(fieldId);
|
|
617
|
+
const host = el && el.closest(".field, .switch");
|
|
618
|
+
const label = host && host.querySelector(":scope > .label");
|
|
619
|
+
if (!label) return;
|
|
620
|
+
const old = label.querySelector(".fsaved");
|
|
621
|
+
if (old) old.remove();
|
|
622
|
+
label.insertAdjacentHTML("beforeend", `<span class="fsaved">${ic("check")}Saved</span>`);
|
|
623
|
+
const mark = label.lastElementChild;
|
|
624
|
+
setTimeout(() => mark.remove(), ms == null ? SAVED_MARK_MS : ms);
|
|
625
|
+
}
|
|
626
|
+
function bindSave(container, onSave) {
|
|
627
|
+
if (!container) return;
|
|
639
628
|
let dirty = false;
|
|
640
629
|
let saving = false;
|
|
641
630
|
let again = false;
|
|
631
|
+
let lastField = null;
|
|
642
632
|
const arm = () => {
|
|
643
633
|
dirty = true;
|
|
644
|
-
button.disabled = false;
|
|
645
|
-
button.classList.remove("saved");
|
|
646
|
-
button.textContent = "Save";
|
|
647
634
|
};
|
|
648
635
|
const save = async () => {
|
|
649
636
|
if (!dirty) return;
|
|
@@ -653,18 +640,16 @@
|
|
|
653
640
|
}
|
|
654
641
|
saving = true;
|
|
655
642
|
dirty = false;
|
|
656
|
-
|
|
657
|
-
|
|
643
|
+
const field = lastField;
|
|
644
|
+
lastField = null;
|
|
658
645
|
try {
|
|
659
646
|
await onSave();
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
647
|
+
if (field) {
|
|
648
|
+
recentlySaved.set(field, Date.now());
|
|
649
|
+
markSaved(field);
|
|
650
|
+
}
|
|
664
651
|
} catch (e) {
|
|
665
652
|
dirty = true;
|
|
666
|
-
button.classList.remove("loading");
|
|
667
|
-
button.disabled = false;
|
|
668
653
|
showError(e);
|
|
669
654
|
}
|
|
670
655
|
saving = false;
|
|
@@ -673,14 +658,14 @@
|
|
|
673
658
|
save();
|
|
674
659
|
}
|
|
675
660
|
};
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
button.innerHTML = `${ic("check")} Saved`;
|
|
661
|
+
for (const [id, at] of recentlySaved) {
|
|
662
|
+
const left = SAVED_MARK_MS - (Date.now() - at);
|
|
663
|
+
if (left > 0 && container.querySelector(`#${CSS.escape(id)}`)) markSaved(id, left);
|
|
680
664
|
}
|
|
681
665
|
container.addEventListener("input", arm);
|
|
682
|
-
container.addEventListener("change", () => {
|
|
666
|
+
container.addEventListener("change", (e) => {
|
|
683
667
|
arm();
|
|
668
|
+
if (e.target.id) lastField = e.target.id;
|
|
684
669
|
save();
|
|
685
670
|
});
|
|
686
671
|
container.addEventListener("keydown", (e) => {
|
|
@@ -692,11 +677,10 @@
|
|
|
692
677
|
}
|
|
693
678
|
});
|
|
694
679
|
container.addEventListener("focusout", (e) => {
|
|
695
|
-
if (e.target.isContentEditable)
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
save();
|
|
680
|
+
if (e.target.isContentEditable) {
|
|
681
|
+
if (e.target.id) lastField = e.target.id;
|
|
682
|
+
save();
|
|
683
|
+
}
|
|
700
684
|
});
|
|
701
685
|
}
|
|
702
686
|
function roomHue(room) {
|
|
@@ -1144,7 +1128,7 @@
|
|
|
1144
1128
|
const rows = new Map([...els.participants.children].map((li) => [li.dataset.id, li]));
|
|
1145
1129
|
for (const p of ordered) {
|
|
1146
1130
|
let li = rows.get(p.id);
|
|
1147
|
-
const selected = (state.selection.kind === "participant" && state.selection.id === p.id) || (p.kind === "human" && state.selection.kind === "me"
|
|
1131
|
+
const selected = state.detailsOpen && ((state.selection.kind === "participant" && state.selection.id === p.id) || (p.kind === "human" && state.selection.kind === "me"));
|
|
1148
1132
|
const asleep = p.kind === "agent" && (p.status === "offline" || p.status === "left");
|
|
1149
1133
|
const unstaffed = p.kind === "agent" && p.status === "unstaffed";
|
|
1150
1134
|
const shown = shownStatus(room, p);
|
|
@@ -1156,7 +1140,7 @@
|
|
|
1156
1140
|
: asleep
|
|
1157
1141
|
? `<span class="zzz" title="${esc(STATUS_LABEL[p.status] || p.status)}">zzz</span>`
|
|
1158
1142
|
: p.kind === "agent" && p.status !== "idle" ? `<span class="badge status-${shown}">${p.status === "thinking" ? '<span class="dot"></span>' : ""}${STATUS_LABEL[shown] || shown}</span>` : "";
|
|
1159
|
-
const avatarHtml = avatar(p.kind === "human" ? meAvatarData() : p, 44, { vendor: true });
|
|
1143
|
+
const avatarHtml = avatar(p.kind === "human" ? meAvatarData() : p, 44, { vendor: true, muted: p.muted });
|
|
1160
1144
|
const statusClass = p.kind === "agent" ? `avatar-status status-${esc(shown || "idle")}` : "";
|
|
1161
1145
|
const bodyHtml = `<div class="p-body">
|
|
1162
1146
|
<div class="p-name"><span>${esc(p.name)}</span>${p.muted ? '<span class="badge muted">muted</span>' : ""}${status}</div>
|
|
@@ -1630,6 +1614,7 @@
|
|
|
1630
1614
|
if (!placed.has(seq)) placeInList(dividerElement(agents));
|
|
1631
1615
|
}
|
|
1632
1616
|
for (const perm of room.permissions) renderPermission(room, perm);
|
|
1617
|
+
for (const prop of room.proposals || []) renderProposal(room, prop);
|
|
1633
1618
|
refreshSeen(room);
|
|
1634
1619
|
scrollToBottom();
|
|
1635
1620
|
renderTimeline();
|
|
@@ -1638,6 +1623,10 @@
|
|
|
1638
1623
|
function upsertMessage(roomId, m) {
|
|
1639
1624
|
const room = state.rooms.get(roomId);
|
|
1640
1625
|
if (!room) return;
|
|
1626
|
+
if (m.from === "human" && !m.pending && !room.messages.some((x) => x.id === m.id)) {
|
|
1627
|
+
const local = room.messages.find((x) => x.pending && x.from === "human" && x.text === m.text);
|
|
1628
|
+
if (local) adoptLocalMessage(roomId, local.id, m.id);
|
|
1629
|
+
}
|
|
1641
1630
|
const idx = room.messages.findIndex((x) => x.id === m.id);
|
|
1642
1631
|
const wasFinal = idx >= 0 && !room.messages[idx].streaming;
|
|
1643
1632
|
if (idx >= 0) {
|
|
@@ -1785,6 +1774,16 @@
|
|
|
1785
1774
|
editEls.rewrite.addEventListener("click", () => editRequest && submitEdit(editRequest.room, editRequest.m, editRequest.next, "rewrite"));
|
|
1786
1775
|
|
|
1787
1776
|
|
|
1777
|
+
function placeCard(card, room, ts) {
|
|
1778
|
+
let anchor = null;
|
|
1779
|
+
for (const m of room.messages) {
|
|
1780
|
+
if (m.ts > ts) break;
|
|
1781
|
+
const el = els.messages.querySelector(`.msg[data-id="${CSS.escape(m.id)}"]`);
|
|
1782
|
+
if (el) anchor = el;
|
|
1783
|
+
}
|
|
1784
|
+
if (anchor) anchor.insertAdjacentElement("afterend", card);
|
|
1785
|
+
else els.messages.appendChild(card);
|
|
1786
|
+
}
|
|
1788
1787
|
function renderPermission(room, perm) {
|
|
1789
1788
|
const p = findById(room, perm.participantId);
|
|
1790
1789
|
const card = document.createElement("div");
|
|
@@ -1811,8 +1810,9 @@
|
|
|
1811
1810
|
actions.appendChild(cancel);
|
|
1812
1811
|
const draft = [...room.messages].reverse().find((m) => m.streaming && m.from === perm.participantId);
|
|
1813
1812
|
const host = draft ? els.messages.querySelector(`.msg[data-id="${draft.id}"] .perms`) : null;
|
|
1814
|
-
(host
|
|
1815
|
-
|
|
1813
|
+
if (host) host.appendChild(card);
|
|
1814
|
+
else placeCard(card, room, perm.ts || Date.now());
|
|
1815
|
+
if (stuck) scrollToBottom();
|
|
1816
1816
|
}
|
|
1817
1817
|
function resolvePermissionCard(key, optionId) {
|
|
1818
1818
|
const card = document.querySelector(`.perm[data-key="${key}"]`);
|
|
@@ -1821,6 +1821,64 @@
|
|
|
1821
1821
|
card.querySelector(".perm-actions").innerHTML = `<span class="perm-result">${optionId ? `chosen: ${esc(optionId)}` : "dismissed"}</span>`;
|
|
1822
1822
|
}
|
|
1823
1823
|
|
|
1824
|
+
function proposalValue(v) {
|
|
1825
|
+
if (v === null || v === undefined || v === "") return "(empty)";
|
|
1826
|
+
if (typeof v === "object") return v.mode === "fixed" ? v.language : v.mode || JSON.stringify(v);
|
|
1827
|
+
return String(v);
|
|
1828
|
+
}
|
|
1829
|
+
function proposalDiffHtml(p) {
|
|
1830
|
+
const rows = [];
|
|
1831
|
+
for (const c of p.settings || []) {
|
|
1832
|
+
if (c.key === "customRules") {
|
|
1833
|
+
const before = String(c.from || "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
1834
|
+
const after = String(c.to || "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
1835
|
+
const lines = [...before.filter((l) => !after.includes(l)).map((l) => `<div class="prop-line del">โ ${esc(l)}</div>`), ...after.filter((l) => !before.includes(l)).map((l) => `<div class="prop-line add">+ ${esc(l)}</div>`)];
|
|
1836
|
+
rows.push(`<div class="prop-row"><b>Room rules</b>${lines.join("")}</div>`);
|
|
1837
|
+
} else rows.push(`<div class="prop-row"><b>${esc(c.key)}</b> <span class="prop-from">${esc(proposalValue(c.from))}</span> โ <span class="prop-to">${esc(proposalValue(c.to))}</span></div>`);
|
|
1838
|
+
}
|
|
1839
|
+
for (const v of p.vibemates || []) {
|
|
1840
|
+
if (v.op === "update") rows.push(`<div class="prop-row"><b>${esc(v.name)}</b>${(v.fields || []).map((f) => `<div class="prop-line">${esc(f.field)}: <span class="prop-from">${esc(f.from || "(empty)")}</span> โ <span class="prop-to">${esc(f.to || "(empty)")}</span></div>`).join("")}</div>`);
|
|
1841
|
+
else rows.push(`<div class="prop-row"><b>${v.op === "add" ? "New vibemate" : "Remove"}</b> ${esc(v.name)}${v.op === "add" ? ' <span class="hint">(you pick its coding agent)</span>' : ""}</div>`);
|
|
1842
|
+
}
|
|
1843
|
+
return rows.join("");
|
|
1844
|
+
}
|
|
1845
|
+
function renderProposal(room, p) {
|
|
1846
|
+
const existing = els.messages.querySelector(`.proposal[data-key="${p.key}"]`);
|
|
1847
|
+
const card = existing || document.createElement("div");
|
|
1848
|
+
card.className = `perm proposal${p.status !== "pending" ? " resolved" : ""}`;
|
|
1849
|
+
card.dataset.key = p.key;
|
|
1850
|
+
card.innerHTML = `
|
|
1851
|
+
<div class="perm-title">${ic("pencil")} ${esc(p.participantName)} proposes changes to the room</div>
|
|
1852
|
+
${p.why ? `<div class="prop-why">${esc(p.why)}</div>` : ""}
|
|
1853
|
+
<div class="prop-diff">${proposalDiffHtml(p)}</div>
|
|
1854
|
+
${p.warnings && p.warnings.length ? `<ul class="prop-warn">${p.warnings.map((w) => `<li>${esc(w)}</li>`).join("")}</ul>` : ""}
|
|
1855
|
+
${p.touchesOwn ? `<div class="prop-own">${ic("info")} This changes the rules or ${esc(p.participantName)}'s own persona: it decides how ${esc(p.participantName)} itself will behave.</div>` : ""}
|
|
1856
|
+
<div class="perm-actions"></div>`;
|
|
1857
|
+
const actions = card.querySelector(".perm-actions");
|
|
1858
|
+
if (p.status === "pending") {
|
|
1859
|
+
const apply = document.createElement("button");
|
|
1860
|
+
apply.className = "perm-btn kind-allow_once";
|
|
1861
|
+
apply.textContent = "Apply";
|
|
1862
|
+
apply.addEventListener("click", () => post(roomApi(`/proposals/${encodeURIComponent(p.key)}`), { accept: true }).catch(showError));
|
|
1863
|
+
const reject = document.createElement("button");
|
|
1864
|
+
reject.className = "perm-btn kind-reject_once";
|
|
1865
|
+
reject.textContent = "Reject";
|
|
1866
|
+
reject.addEventListener("click", () => post(roomApi(`/proposals/${encodeURIComponent(p.key)}`), { accept: false }).catch(showError));
|
|
1867
|
+
actions.append(apply, reject);
|
|
1868
|
+
} else actions.innerHTML = `<span class="perm-result">${p.status === "applied" ? `applied${p.skipped && p.skipped.length ? ` ยท not applied: ${esc(p.skipped.join("; "))}` : ""}` : "rejected"}</span>`;
|
|
1869
|
+
if (!existing) {
|
|
1870
|
+
placeCard(card, room, p.ts || Date.now());
|
|
1871
|
+
if (stuck) scrollToBottom();
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
function resolveProposalCard(room, key, status) {
|
|
1875
|
+
const p = (room.proposals || []).find((x) => x.key === key);
|
|
1876
|
+
if (p) {
|
|
1877
|
+
p.status = status;
|
|
1878
|
+
renderProposal(room, p);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1824
1882
|
|
|
1825
1883
|
const DETAILS_MIN = 320;
|
|
1826
1884
|
const DETAILS_MAX = 760;
|
|
@@ -1879,7 +1937,7 @@
|
|
|
1879
1937
|
renderRoomPanel(room);
|
|
1880
1938
|
}
|
|
1881
1939
|
function profileHeader(p) {
|
|
1882
|
-
return `${avatar(p, 76, { vendor: true, status: true })}
|
|
1940
|
+
return `${avatar(p, 76, { vendor: true, status: true, muted: p.muted })}
|
|
1883
1941
|
<h3>${esc(p.name)}</h3>
|
|
1884
1942
|
<div class="tagline">${esc(p.tagline || "no vibersona")}</div>
|
|
1885
1943
|
<div class="badges"><span class="badge">${esc(p.agentVendor || p.agentType || "vibemate")}</span><span class="badge status-${p.status}">${STATUS_LABEL[p.status] || p.status}</span>${p.muted ? '<span class="badge muted">muted</span>' : ""}</div>`;
|
|
@@ -1906,7 +1964,7 @@
|
|
|
1906
1964
|
</div>
|
|
1907
1965
|
<div class="action-row">
|
|
1908
1966
|
<button class="action" data-act="mention"><span class="ico">${ic("at")}</span>Mention</button>
|
|
1909
|
-
<button class="action" data-act="${p.muted ? "unmute" : "mute"}"><span class="ico">${ic(p.muted ? "
|
|
1967
|
+
<button class="action" data-act="${p.muted ? "unmute" : "mute"}"><span class="ico">${ic(p.muted ? "mute" : "unmute")}</span>${p.muted ? "Unmute" : "Mute"}</button>
|
|
1910
1968
|
${offline ? `<button class="action" data-act="reconnect"><span class="ico">${ic("refresh")}</span>Reconnect</button>` : `<button class="action" data-act="cancel" ${p.status !== "thinking" ? "disabled" : ""}><span class="ico">${ic("stop")}</span>Stop</button>`}
|
|
1911
1969
|
<button class="action danger" data-act="remove"><span class="ico">${ic("trash")}</span>Remove</button>
|
|
1912
1970
|
</div>
|
|
@@ -1916,25 +1974,23 @@
|
|
|
1916
1974
|
${field("Vibersona", `<input type="text" id="pp-tagline" maxlength="80" value="${esc(p.tagline || "")}" placeholder="a few words under the vibename">`, "Shown under the vibename.", "Everyone in the room sees it: you, and the other vibemates in their roster.")}
|
|
1917
1975
|
${field("Vibeface", `<div id="pp-avatar-picker"></div><input type="text" id="pp-avatar" maxlength="8" value="${esc(p.avatar || "")}" placeholder="custom emoji (optional)">`)}
|
|
1918
1976
|
${field("Vibio", `<textarea id="pp-role" rows="5" maxlength="4000" placeholder="who it is, how it speaks, what it cares about">${esc(p.role || "")}</textarea>`, "Only this vibemate reads it.", "Reaches the vibemate as refreshed instructions in its brief on its next turn; its memory is kept. The other participants never see it.")}
|
|
1919
|
-
${saveRow("pp-save")}
|
|
1920
1977
|
</div>
|
|
1921
1978
|
${p.statusDetail && (p.status === "offline" || p.status === "error" || p.failedTurns) ? `<p class="hint" style="color:var(--danger);margin:0 4px 10px">${esc(p.statusDetail)}</p>` : ""}
|
|
1979
|
+
<div class="section" id="pp-engine">
|
|
1980
|
+
${sectionTitle("spark", "Coding agent")}
|
|
1981
|
+
<p class="hint">What runs ${esc(p.name)}${rec ? `: ${esc(rec.vendor)}` : ""}. Its model, how hard it thinks, what it may do without asking.${geekTip("These options come from the coding agent itself: the hub lists the ones it offers and sets your pick on its running session, so a change takes effect from the next turn, without restarting it or losing what it remembers.")}</p>
|
|
1982
|
+
<div id="pp-config"></div>
|
|
1983
|
+
</div>
|
|
1922
1984
|
${geek(
|
|
1923
1985
|
"pp-geek",
|
|
1924
1986
|
`<div class="section" id="pp-skills-section">
|
|
1925
1987
|
${sectionTitle("skills", "Skills")}
|
|
1926
1988
|
<div class="check-list" id="pp-skills"></div>
|
|
1927
1989
|
<p class="hint" style="margin-top:8px">What this vibemate can load on request.${geekTip(`Listed in this vibemate's brief by name and description; the text arrives when you write /name or when the vibemate loads it. ${esc(skillChannelText(p))}`)}</p>
|
|
1928
|
-
${saveRow("pp-skills-save")}
|
|
1929
1990
|
</div>
|
|
1930
1991
|
<div class="section" id="pp-timing">
|
|
1931
1992
|
${sectionTitle("bolt", "Timing")}
|
|
1932
1993
|
${field("Reply delay override, seconds", `<input type="number" id="pp-delay" min="0" max="120" step="0.5" value="${p.replyDelay ?? ""}" placeholder="the room's: ${room.settings.replyDelay ?? 4} s">`, `Overrides the room's delay (${room.settings.replyDelay ?? 4} s, used only when two or more vibemates are in) for this vibemate only, even when it is alone. Empty: it follows the room.`, "Before each turn the vibemate waits a random 0โN seconds, so replies cross less often. Messages that arrive during the wait land in its backlog, so it can react to them or stay silent.")}
|
|
1933
|
-
${saveRow("pp-delay-save")}
|
|
1934
|
-
</div>
|
|
1935
|
-
<div class="section">
|
|
1936
|
-
${sectionTitle("link", "Session")}
|
|
1937
|
-
<div id="pp-config"></div>
|
|
1938
1994
|
</div>
|
|
1939
1995
|
<div class="section danger">
|
|
1940
1996
|
${sectionTitle("bolt", "Respawn")}
|
|
@@ -1956,7 +2012,7 @@
|
|
|
1956
2012
|
<span>Adapter</span><span>${esc(rec ? rec.label : p.agentLabel || "")}${p.agentInfo && p.agentInfo.version ? ` ${esc(p.agentInfo.version)}` : ""}</span>
|
|
1957
2013
|
</div>
|
|
1958
2014
|
</div>`,
|
|
1959
|
-
"skills, timing,
|
|
2015
|
+
"skills, timing, stats",
|
|
1960
2016
|
)}`;
|
|
1961
2017
|
wireDetailsClose();
|
|
1962
2018
|
const respawnBtn = els.detailsInner.querySelector('button[data-act="respawn"]');
|
|
@@ -1987,9 +2043,9 @@
|
|
|
1987
2043
|
}),
|
|
1988
2044
|
);
|
|
1989
2045
|
renderSkillChecks($("#pp-skills"), p.skills || []);
|
|
1990
|
-
bindSave($("#pp-skills-section"),
|
|
1991
|
-
bindSave($("#pp-timing"),
|
|
1992
|
-
bindSave($("#pp-persona"),
|
|
2046
|
+
bindSave($("#pp-skills-section"), () => post(roomApi(`/participants/${encodeURIComponent(p.id)}/persona`), { skills: checkedSkills($("#pp-skills")) }));
|
|
2047
|
+
bindSave($("#pp-timing"), () => post(roomApi(`/participants/${encodeURIComponent(p.id)}/persona`), { replyDelay: $("#pp-delay").value === "" ? null : Number($("#pp-delay").value) }));
|
|
2048
|
+
bindSave($("#pp-persona"), () => post(roomApi(`/participants/${encodeURIComponent(p.id)}/persona`), { name: $("#pp-name").value, tagline: $("#pp-tagline").value, role: $("#pp-role").value, avatar: $("#pp-avatar").value }));
|
|
1993
2049
|
renderConfig($("#pp-config"), p, offline);
|
|
1994
2050
|
}
|
|
1995
2051
|
|
|
@@ -2051,7 +2107,6 @@
|
|
|
2051
2107
|
${field("Vibename", `<input type="text" id="me-name" maxlength="24" value="${esc(s.humanName || "")}">`, "How you appear in every room.")}
|
|
2052
2108
|
${field("Vibeface", `<div id="me-avatar-picker"></div><input type="text" id="me-avatar" maxlength="8" value="${esc(s.humanAvatar || "")}" placeholder="custom emoji (optional)">`)}
|
|
2053
2109
|
${field("Your vibe line", `<textarea id="me-desc" rows="3" maxlength="200" placeholder="e.g. software engineer, curious about agent protocols; likes short answers">${esc(s.humanDescription || "")}</textarea>`, "A sentence or two about you.", "The vibemates get it in every room's brief, unless a room adds its own line or replaces it (below, when you are in a room).")}
|
|
2054
|
-
${saveRow("me-save")}
|
|
2055
2110
|
</div>
|
|
2056
2111
|
${
|
|
2057
2112
|
rs
|
|
@@ -2059,7 +2114,6 @@
|
|
|
2059
2114
|
${sectionTitle("chat", "In this room")}
|
|
2060
2115
|
${field("What vibemates get about you here", `<select id="hp-mode"><option value="inherit"${rs.humanDescriptionMode === "inherit" ? " selected" : ""}>Your vibe line</option><option value="append"${rs.humanDescriptionMode === "append" ? " selected" : ""}>Your vibe line + this room's</option><option value="override"${rs.humanDescriptionMode === "override" ? " selected" : ""}>Only this room's line</option><option value="none"${rs.humanDescriptionMode === "none" ? " selected" : ""}>Nothing about me in this room</option></select>`)}
|
|
2061
2116
|
${field("This room's line about you", `<textarea id="hp-desc" rows="3" maxlength="200" placeholder="e.g. host of this session, product owner">${esc(rs.humanDescription || "")}</textarea>`)}
|
|
2062
|
-
${saveRow("hp-save")}
|
|
2063
2117
|
</div>`
|
|
2064
2118
|
: ""
|
|
2065
2119
|
}
|
|
@@ -2075,8 +2129,8 @@
|
|
|
2075
2129
|
$("#me-avatar").dispatchEvent(new Event("change", { bubbles: true }));
|
|
2076
2130
|
}),
|
|
2077
2131
|
);
|
|
2078
|
-
bindSave($("#me-vibe"),
|
|
2079
|
-
bindSave($("#me-room"),
|
|
2132
|
+
bindSave($("#me-vibe"), () => post("/api/settings", { humanName: $("#me-name").value, humanAvatar: $("#me-avatar").value, humanDescription: $("#me-desc").value }));
|
|
2133
|
+
bindSave($("#me-room"), () => post(roomApi("/settings"), { humanDescriptionMode: $("#hp-mode").value, humanDescription: $("#hp-desc").value }));
|
|
2080
2134
|
$("#me-erase").addEventListener("click", openEraseDialog);
|
|
2081
2135
|
}
|
|
2082
2136
|
|
|
@@ -2138,7 +2192,7 @@
|
|
|
2138
2192
|
</div>`,
|
|
2139
2193
|
"tools, hops, referee, briefs",
|
|
2140
2194
|
)}
|
|
2141
|
-
<div class="save-row" style="margin-top:10px"><span class="hint">The vibemates get the changes on their next turn.</span
|
|
2195
|
+
<div class="save-row" style="margin-top:10px"><span class="hint">The vibemates get the changes on their next turn.</span></div>
|
|
2142
2196
|
</div>
|
|
2143
2197
|
<div class="section danger" style="margin-top:12px">
|
|
2144
2198
|
${sectionTitle("alert", "Danger zone")}
|
|
@@ -2151,7 +2205,7 @@
|
|
|
2151
2205
|
$("#rp-emoji-picker").appendChild(
|
|
2152
2206
|
emojiGrid(ROOM_EMOJI, rs.emoji || "", (emoji) => {
|
|
2153
2207
|
$("#rp-emoji").value = emoji;
|
|
2154
|
-
$("#rp-emoji").dispatchEvent(new Event("
|
|
2208
|
+
$("#rp-emoji").dispatchEvent(new Event("change", { bubbles: true }));
|
|
2155
2209
|
}),
|
|
2156
2210
|
);
|
|
2157
2211
|
$("#rp-dir-browse").addEventListener("click", () => openFolderPicker($("#rp-dir").value, (dir) => {
|
|
@@ -2159,7 +2213,7 @@
|
|
|
2159
2213
|
$("#rp-dir").dispatchEvent(new Event("change", { bubbles: true }));
|
|
2160
2214
|
}));
|
|
2161
2215
|
$("#rp-template").addEventListener("click", () => openSaveTemplateDialog(room));
|
|
2162
|
-
bindSave($("#rp-form"),
|
|
2216
|
+
bindSave($("#rp-form"), async () => {
|
|
2163
2217
|
const name = $("#rp-name").value;
|
|
2164
2218
|
if (name.trim() !== room.name) await post(roomApi("/rename"), { name });
|
|
2165
2219
|
const dir = $("#rp-dir").value.trim();
|
|
@@ -2220,7 +2274,7 @@
|
|
|
2220
2274
|
installed.map((r) => `<div class="vendor-row">${logo(r)}<span class="vc-name">${esc(r.vendor)}<span class="hint" title="${esc(r.installedAt || "")}">${esc(r.installedAt || "bundled")}</span></span><span class="badge status-idle"><span class="dot"></span>installed</span></div>`).join("") +
|
|
2221
2275
|
missing.map((r) => `<div class="vendor-row" style="opacity:.75">${logo(r)}<span class="vc-name">${esc(r.vendor)}<span class="hint">${esc(r.installHint || r.unavailableReason || "")}</span></span><span class="badge status-offline">not installed</span></div>`).join("");
|
|
2222
2276
|
els.pageInner.innerHTML = `
|
|
2223
|
-
<div class="page-head"><div><h1>Settings</h1><div class="hint">${state.version ? `${esc(state.version.name)} ${esc(state.version.version)} ยท hub built ${esc(new Date(state.version.build).toLocaleString())}` : "hub build unknown (older hub process; run viberoom again to replace it)"}</div></div
|
|
2277
|
+
<div class="page-head"><div><h1>Settings</h1><div class="hint">${state.version ? `${esc(state.version.name)} ${esc(state.version.version)} ยท hub built ${esc(new Date(state.version.build).toLocaleString())}` : "hub build unknown (older hub process; run viberoom again to replace it)"}</div></div></div>
|
|
2224
2278
|
<div id="sp-form">
|
|
2225
2279
|
<div class="page-cols">
|
|
2226
2280
|
<div>
|
|
@@ -2314,7 +2368,7 @@
|
|
|
2314
2368
|
editorSection.querySelectorAll(".editor-modes .chip-btn").forEach((x) => x.classList.toggle("on", x === b));
|
|
2315
2369
|
const input = $("#sp-editor-mode");
|
|
2316
2370
|
input.value = b.dataset.mode;
|
|
2317
|
-
input.dispatchEvent(new Event("
|
|
2371
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2318
2372
|
showEditorCmd();
|
|
2319
2373
|
}),
|
|
2320
2374
|
);
|
|
@@ -2337,7 +2391,7 @@
|
|
|
2337
2391
|
diagramSection.querySelectorAll(".diagram-presets .chip-btn").forEach((x) => x.classList.toggle("on", x === b));
|
|
2338
2392
|
const input = $("#sp-diagram-preset");
|
|
2339
2393
|
input.value = b.dataset.preset;
|
|
2340
|
-
input.dispatchEvent(new Event("
|
|
2394
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2341
2395
|
redrawPreview();
|
|
2342
2396
|
}),
|
|
2343
2397
|
);
|
|
@@ -2368,7 +2422,7 @@
|
|
|
2368
2422
|
b.disabled = false;
|
|
2369
2423
|
b.classList.remove("loading");
|
|
2370
2424
|
});
|
|
2371
|
-
bindSave($("#sp-form"),
|
|
2425
|
+
bindSave($("#sp-form"), async () => {
|
|
2372
2426
|
const vendorPresets = {};
|
|
2373
2427
|
els.pageInner.querySelectorAll("input[data-vendor]").forEach((inp) => {
|
|
2374
2428
|
vendorPresets[inp.dataset.vendor] = vendorPresets[inp.dataset.vendor] || { model: null, effort: null, mode: null };
|
|
@@ -3358,6 +3412,18 @@
|
|
|
3358
3412
|
room.permissions = room.permissions.filter((p) => p.key !== event.key);
|
|
3359
3413
|
if (showing) resolvePermissionCard(event.key, event.optionId);
|
|
3360
3414
|
return;
|
|
3415
|
+
case "proposal":
|
|
3416
|
+
room.proposals = [...(room.proposals || []), event.proposal];
|
|
3417
|
+
if (showing) renderProposal(room, event.proposal);
|
|
3418
|
+
else toast(`${esc(event.proposal.participantName)} proposes changes to "${room.name}".`, "warn");
|
|
3419
|
+
return;
|
|
3420
|
+
case "proposal.resolved": {
|
|
3421
|
+
const p = (room.proposals || []).find((x) => x.key === event.key);
|
|
3422
|
+
if (p) p.skipped = event.skipped;
|
|
3423
|
+
if (showing) resolveProposalCard(room, event.key, event.status);
|
|
3424
|
+
else if (p) p.status = event.status;
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3361
3427
|
case "room":
|
|
3362
3428
|
room.hopLimit = event.hopLimit;
|
|
3363
3429
|
room.hops = event.hops;
|
|
@@ -3700,13 +3766,14 @@
|
|
|
3700
3766
|
autosize();
|
|
3701
3767
|
const local = { id: `local-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, seq: 0, from: "human", fromName: (state.settings || {}).humanName || "You", to: [], toNames: [], text, ts: Date.now(), kind: "chat", pending: true };
|
|
3702
3768
|
if (shots.length) local.images = shots.map((shot) => ({ file: "", name: shot.name, mimeType: shot.mimeType, bytes: 0, n: shot.n, url: shot.data }));
|
|
3769
|
+
const localId = local.id;
|
|
3703
3770
|
upsertMessage(room.id, local);
|
|
3704
3771
|
try {
|
|
3705
3772
|
const r = await post(roomApi("/send"), { text, images: shots });
|
|
3706
|
-
if (r.command) removeMessage(room.id,
|
|
3707
|
-
else adoptLocalMessage(room.id,
|
|
3773
|
+
if (r.command) removeMessage(room.id, localId);
|
|
3774
|
+
else adoptLocalMessage(room.id, localId, r.id);
|
|
3708
3775
|
} catch (error) {
|
|
3709
|
-
removeMessage(room.id,
|
|
3776
|
+
removeMessage(room.id, localId);
|
|
3710
3777
|
showError(error);
|
|
3711
3778
|
els.input.value = text;
|
|
3712
3779
|
pendingShots = shots;
|
|
@@ -3715,7 +3782,7 @@
|
|
|
3715
3782
|
});
|
|
3716
3783
|
function adoptLocalMessage(roomId, localId, realId) {
|
|
3717
3784
|
const room = state.rooms.get(roomId);
|
|
3718
|
-
if (!room || !realId) return;
|
|
3785
|
+
if (!room || !realId || localId === realId) return;
|
|
3719
3786
|
if (room.messages.some((m) => m.id === realId)) return removeMessage(roomId, localId);
|
|
3720
3787
|
const m = room.messages.find((x) => x.id === localId);
|
|
3721
3788
|
if (!m) return;
|
|
@@ -3723,6 +3790,11 @@
|
|
|
3723
3790
|
delete m.pending;
|
|
3724
3791
|
const el = els.messages.querySelector(`.msg[data-id="${localId}"]`);
|
|
3725
3792
|
if (el) el.dataset.id = realId;
|
|
3793
|
+
const note = doneNotes.find((n) => n.id === localId);
|
|
3794
|
+
if (note) {
|
|
3795
|
+
note.id = realId;
|
|
3796
|
+
renderDoneNotes();
|
|
3797
|
+
}
|
|
3726
3798
|
}
|
|
3727
3799
|
|
|
3728
3800
|
|
|
@@ -4347,10 +4419,10 @@
|
|
|
4347
4419
|
let shown = 0;
|
|
4348
4420
|
for (const b of buttons) {
|
|
4349
4421
|
const draft = room.messages.find((x) => x.from === b.dataset.id && x.streaming);
|
|
4350
|
-
const
|
|
4422
|
+
const el = draft && els.messages.querySelector(`.msg[data-id="${draft.id}"]`);
|
|
4351
4423
|
let show = false;
|
|
4352
|
-
if (
|
|
4353
|
-
const r =
|
|
4424
|
+
if (el) {
|
|
4425
|
+
const r = el.getBoundingClientRect();
|
|
4354
4426
|
show = !(r.bottom > box.top && r.top < box.bottom);
|
|
4355
4427
|
}
|
|
4356
4428
|
b.hidden = !show;
|