viberoom 0.2.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/LICENSE +661 -0
- package/NOTICE +20 -0
- package/README.md +153 -0
- package/assets/icon-128.png +0 -0
- package/assets/icon-16.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon-32.png +0 -0
- package/assets/icon-48.png +0 -0
- package/assets/icon-512.png +0 -0
- package/assets/icon-64.png +0 -0
- package/assets/icon-vector.svg +30 -0
- package/assets/icon.icns +0 -0
- package/assets/icon.ico +0 -0
- package/assets/icon.svg +30 -0
- package/assets/vendors/claude.svg +3 -0
- package/assets/vendors/codex.svg +3 -0
- package/assets/vendors/copilot.svg +5 -0
- package/assets/vendors/cursor.svg +3 -0
- package/assets/vendors/gemini.svg +3 -0
- package/assets/vendors/opencode.svg +3 -0
- package/dist/acp-client.js +137 -0
- package/dist/acp-types.js +2 -0
- package/dist/edit.js +34 -0
- package/dist/hub.js +348 -0
- package/dist/icons.js +235 -0
- package/dist/jsonrpc.js +109 -0
- package/dist/launcher.js +161 -0
- package/dist/log.js +35 -0
- package/dist/main.js +389 -0
- package/dist/mcp-skills-server.js +177 -0
- package/dist/open.js +141 -0
- package/dist/persona.js +217 -0
- package/dist/recipes.js +261 -0
- package/dist/room.js +2124 -0
- package/dist/server.js +433 -0
- package/dist/shortcuts.js +176 -0
- package/dist/skills.js +344 -0
- package/dist/tui.js +109 -0
- package/package.json +61 -0
- package/scripts/install.mjs +34 -0
- package/scripts/render-icon.mjs +84 -0
- package/scripts/update.mjs +29 -0
- package/ui/app.css +346 -0
- package/ui/app.js +2834 -0
- package/ui/avatars.js +113 -0
- package/ui/fonts/OFL.txt +93 -0
- package/ui/fonts/nunito-cyrillic-ext.woff2 +0 -0
- package/ui/fonts/nunito-cyrillic.woff2 +0 -0
- package/ui/fonts/nunito-latin-ext.woff2 +0 -0
- package/ui/fonts/nunito-latin.woff2 +0 -0
- package/ui/fonts/nunito-vietnamese.woff2 +0 -0
- package/ui/fonts/nunito.css +6 -0
- package/ui/icons.js +76 -0
- package/ui/index.html +217 -0
- package/ui/manifest.json +14 -0
- package/ui/theme.css +425 -0
package/ui/avatars.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
|
+
function hash(text) {
|
|
6
|
+
let h = 2166136261;
|
|
7
|
+
for (const ch of String(text)) {
|
|
8
|
+
h ^= ch.codePointAt(0);
|
|
9
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
10
|
+
}
|
|
11
|
+
return h;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function shade(hex, amount) {
|
|
15
|
+
const n = parseInt(hex.replace("#", ""), 16);
|
|
16
|
+
const r = Math.min(255, Math.max(0, ((n >> 16) & 255) + amount));
|
|
17
|
+
const g = Math.min(255, Math.max(0, ((n >> 8) & 255) + amount));
|
|
18
|
+
const b = Math.min(255, Math.max(0, (n & 255) + amount));
|
|
19
|
+
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, "0")}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function initials(name) {
|
|
23
|
+
const parts = String(name).trim().split(/[\s_-]+/).filter(Boolean);
|
|
24
|
+
if (!parts.length) return "?";
|
|
25
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
26
|
+
return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function avatarSvg(name, color, emoji, size) {
|
|
30
|
+
const s = size || 40;
|
|
31
|
+
const c1 = shade(color, 30);
|
|
32
|
+
const c2 = shade(color, -25);
|
|
33
|
+
const id = `g${hash(name + color).toString(36)}`;
|
|
34
|
+
const label = emoji ? emoji : initials(name);
|
|
35
|
+
const fontSize = emoji ? s * 0.55 : s * 0.42;
|
|
36
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${s} ${s}" width="${s}" height="${s}" role="img" aria-label="${escapeAttr(name)}">
|
|
37
|
+
<defs><linearGradient id="${id}" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${c1}"/><stop offset="1" stop-color="${c2}"/></linearGradient></defs>
|
|
38
|
+
<circle cx="${s / 2}" cy="${s / 2}" r="${s / 2}" fill="url(#${id})"/>
|
|
39
|
+
<circle cx="${s / 2}" cy="${s * 0.36}" r="${s * 0.34}" fill="#ffffff" opacity="0.10"/>
|
|
40
|
+
<text x="50%" y="53%" dominant-baseline="middle" text-anchor="middle" font-family="system-ui, 'Segoe UI Emoji', sans-serif" font-size="${fontSize}" font-weight="700" fill="#ffffff">${escapeText(label)}</text>
|
|
41
|
+
</svg>`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function escapeText(value) {
|
|
45
|
+
return String(value).replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
46
|
+
}
|
|
47
|
+
function escapeAttr(value) {
|
|
48
|
+
return String(value).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function avatarHtml(participant, size, options) {
|
|
52
|
+
const opts = options || {};
|
|
53
|
+
const color = participant.color || "#5b5bf0";
|
|
54
|
+
const s = size || 40;
|
|
55
|
+
const emoji = participant.avatar;
|
|
56
|
+
const label = emoji ? emoji : initials(participant.name);
|
|
57
|
+
const fontSize = Math.round(emoji ? s * 0.56 : s * 0.38);
|
|
58
|
+
const radius = Math.round(s * 0.32);
|
|
59
|
+
const svg = `<span class="av-tile" role="img" aria-label="${escapeAttr(participant.name)}" style="background:color-mix(in srgb, ${escapeAttr(color)} 16%, #ffffff);color:${escapeAttr(color)};font-size:${fontSize}px;border-radius:${radius}px">${escapeText(label)}</span>`;
|
|
60
|
+
const status = opts.status ? `<span class="avatar-status status-${escapeAttr(participant.status || "idle")}"></span>` : "";
|
|
61
|
+
let badge = "";
|
|
62
|
+
if (opts.vendor && participant.kind === "agent") {
|
|
63
|
+
const recipe = opts.recipes ? opts.recipes.find((r) => r.id === participant.agentType) : null;
|
|
64
|
+
const letter = escapeText((participant.agentVendor || participant.agentType || "?").slice(0, 1).toUpperCase());
|
|
65
|
+
const title = escapeAttr(participant.agentLabel || participant.agentType || "");
|
|
66
|
+
badge = recipe && recipe.icon
|
|
67
|
+
? `<span class="avatar-badge" title="${title}"><img src="${escapeAttr(recipe.icon)}" alt="" onerror="this.replaceWith(document.createTextNode('${letter}'))"></span>`
|
|
68
|
+
: `<span class="avatar-badge" title="${title}">${letter}</span>`;
|
|
69
|
+
}
|
|
70
|
+
return `<span class="avatar" style="width:${size}px;height:${size}px">${svg}${badge}${status}</span>`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const GALLERY = [
|
|
74
|
+
"🦊", "🐼", "🦉", "🐯", "🐸", "🤖", "👩💻", "🧑🚀", "🧙", "🐙", "🦄", "🐺", "🧑🎨", "🕵️", "🧑🔬", "🐝", "🐧", "🦁", "🐨", "👾",
|
|
75
|
+
"🐱", "🐶", "🦝", "🐢", "🐬", "🦋", "🦩", "🐲", "🦖", "🐳", "🦭", "🐹", "🦔", "🐻", "🐮", "🐵",
|
|
76
|
+
"🧑🍳", "🧑🚒", "🧑⚕️", "🧑🏫", "🧑✈️", "🧛", "🧜", "🧞", "🦸", "🥷", "🤠", "👽", "👻", "🎃",
|
|
77
|
+
"🌙", "⭐", "🔥", "🍀", "🌵", "🌈", "🎧", "🎸", "🚀", "🛸", "🧠", "🎯", "🧩", "💎", "🍕", "☕",
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
function pickerElement(current, onPick) {
|
|
81
|
+
const wrap = document.createElement("div");
|
|
82
|
+
wrap.className = "avatar-picker";
|
|
83
|
+
const render = (value) => {
|
|
84
|
+
wrap.innerHTML = "";
|
|
85
|
+
const none = document.createElement("button");
|
|
86
|
+
none.type = "button";
|
|
87
|
+
none.className = "none" + (!value ? " selected" : "");
|
|
88
|
+
none.textContent = "Aa";
|
|
89
|
+
none.title = "Initials";
|
|
90
|
+
none.addEventListener("click", () => {
|
|
91
|
+
onPick("");
|
|
92
|
+
render("");
|
|
93
|
+
});
|
|
94
|
+
wrap.appendChild(none);
|
|
95
|
+
for (const e of GALLERY) {
|
|
96
|
+
const b = document.createElement("button");
|
|
97
|
+
b.type = "button";
|
|
98
|
+
b.textContent = e;
|
|
99
|
+
b.className = e === value ? "selected" : "";
|
|
100
|
+
b.addEventListener("click", () => {
|
|
101
|
+
onPick(e);
|
|
102
|
+
render(e);
|
|
103
|
+
});
|
|
104
|
+
wrap.appendChild(b);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
render(current || "");
|
|
108
|
+
wrap.setValue = render;
|
|
109
|
+
return wrap;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
window.Avatars = { avatarSvg, avatarHtml, initials, pickerElement, GALLERY };
|
|
113
|
+
})();
|
package/ui/fonts/OFL.txt
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2014 The Nunito Project Authors (https://github.com/googlefonts/nunito)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/* viberoom - Copyright (c) 2026 Todor Rusev - AGPL-3.0-or-later; see LICENSE */
|
|
2
|
+
@font-face { font-family: "Nunito"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/nunito-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
|
|
3
|
+
@font-face { font-family: "Nunito"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/nunito-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
|
|
4
|
+
@font-face { font-family: "Nunito"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/nunito-vietnamese.woff2") format("woff2"); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; }
|
|
5
|
+
@font-face { font-family: "Nunito"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/nunito-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
|
|
6
|
+
@font-face { font-family: "Nunito"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/nunito-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
|
package/ui/icons.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// viberoom - Copyright (c) 2026 Todor Rusev - AGPL-3.0-or-later; see LICENSE
|
|
2
|
+
(() => {
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const GEAR = `<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/>`;
|
|
6
|
+
const BELL_OFF = `<path d="M6 8a6 6 0 0 1 10.5-4M18 8v5l2 3H4l2-3V8"/><path d="M10 20a2 2 0 0 0 4 0M3 3l18 18"/>`;
|
|
7
|
+
|
|
8
|
+
const ICONS = {
|
|
9
|
+
rooms: `<rect x="4" y="4" width="7" height="7" rx="2"/><rect x="13" y="4" width="7" height="7" rx="2"/><rect x="4" y="13" width="7" height="7" rx="2"/><rect x="13" y="13" width="7" height="7" rx="2"/>`,
|
|
10
|
+
chat: `<path d="M20 12a8 8 0 0 1-8 8H5l-1.5 1.5V12a8 8 0 1 1 16 0z"/><path d="M8.5 12h.01M12 12h.01M15.5 12h.01" stroke-width="2.6"/>`,
|
|
11
|
+
skills: `<path d="M2 4.5h5.5a4 4 0 0 1 4 4V20a3 3 0 0 0-3-3H2z"/><path d="M22 4.5h-5.5a4 4 0 0 0-4 4V20a3 3 0 0 1 3-3H22z"/>`,
|
|
12
|
+
puzzle: `<path d="M10 4a2 2 0 1 1 4 0h3a1 1 0 0 1 1 1v3a2 2 0 1 1 0 4v3a1 1 0 0 1-1 1h-3a2 2 0 1 1-4 0H7a1 1 0 0 1-1-1v-3a2 2 0 1 1 0-4V5a1 1 0 0 1 1-1h3z"/>`,
|
|
13
|
+
settings: GEAR,
|
|
14
|
+
user: `<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>`,
|
|
15
|
+
plus: `<path d="M12 5v14M5 12h14"/>`,
|
|
16
|
+
search: `<circle cx="11" cy="11" r="6"/><path d="M20 20l-4.3-4.3"/>`,
|
|
17
|
+
filter: `<path d="M22 3H2l8 9.5V19l4 2v-8.5z"/>`,
|
|
18
|
+
back: `<path d="M15 18l-6-6 6-6"/>`,
|
|
19
|
+
forward: `<path d="M9 18l6-6-6-6"/>`,
|
|
20
|
+
collapse: `<path d="M11 7l-5 5 5 5M18 7l-5 5 5 5"/>`,
|
|
21
|
+
expand: `<path d="M6 7l5 5-5 5M13 7l5 5-5 5"/>`,
|
|
22
|
+
close: `<path d="M18 6L6 18M6 6l12 12"/>`,
|
|
23
|
+
down: `<path d="M6 9l6 6 6-6"/>`,
|
|
24
|
+
stop: `<rect x="6" y="6" width="12" height="12" rx="2"/>`,
|
|
25
|
+
refresh: `<path d="M20 12a8 8 0 1 1-2.3-5.7"/><path d="M20 4v5h-5"/>`,
|
|
26
|
+
bell: `<path d="M6 8a6 6 0 1 1 12 0v5l2 3H4l2-3V8z"/><path d="M10 20a2 2 0 0 0 4 0"/>`,
|
|
27
|
+
"bell-off": BELL_OFF,
|
|
28
|
+
hush: BELL_OFF,
|
|
29
|
+
at: `<circle cx="12" cy="12" r="4"/><path d="M16 12v1.5a2.5 2.5 0 0 0 5 0V12a9 9 0 1 0-3.5 7.1"/>`,
|
|
30
|
+
trash: `<path d="M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3"/>`,
|
|
31
|
+
pencil: `<path d="M17 3a2.8 2.8 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"/>`,
|
|
32
|
+
send: `<path d="M4 12l16-8-6 16-2.5-6.5L4 12z"/>`,
|
|
33
|
+
folder: `<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/>`,
|
|
34
|
+
spark: `<path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3z"/><path d="M19 15l.6 1.6 1.6.6-1.6.6L19 19.4l-.6-1.6-1.6-.6 1.6-.6z"/>`,
|
|
35
|
+
check: `<path d="M20 6L9 17l-5-5"/>`,
|
|
36
|
+
alert: `<path d="M10.3 3.9L1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/>`,
|
|
37
|
+
info: `<circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/>`,
|
|
38
|
+
more: `<path d="M5 12h.01M12 12h.01M19 12h.01" stroke-width="3"/>`,
|
|
39
|
+
logout: `<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/>`,
|
|
40
|
+
link: `<path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/>`,
|
|
41
|
+
wand: `<path d="M15 4V2M15 16v-2M8 9h2M20 9h2M17.8 11.8L19 13M17.8 6.2L19 5M3 21l9-9M12.2 6.2L11 5"/>`,
|
|
42
|
+
bolt: `<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>`,
|
|
43
|
+
save: `<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8M7 3v5h8"/>`,
|
|
44
|
+
eye: `<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>`,
|
|
45
|
+
lock: `<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>`,
|
|
46
|
+
sun: `<circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/>`,
|
|
47
|
+
geek: `<rect x="2.5" y="10" width="8" height="7" rx="2.5"/><rect x="13.5" y="10" width="8" height="7" rx="2.5"/><path d="M10.5 13h3M2.5 12l1.8-4.5M21.5 12l-1.8-4.5"/>`,
|
|
48
|
+
clock: `<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>`,
|
|
49
|
+
tool: `<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.8-3.8a6 6 0 0 1-7.9 7.9l-6.9 6.9a2.1 2.1 0 0 1-3-3l6.9-6.9a6 6 0 0 1 7.9-7.9l-3.8 3.8z"/>`,
|
|
50
|
+
smile: `<circle cx="12" cy="12" r="9"/><path d="M8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01"/>`,
|
|
51
|
+
"arrow-down": `<path d="M12 5v14M19 12l-7 7-7-7"/>`,
|
|
52
|
+
"arrow-up": `<path d="M12 19V5M5 12l7-7 7 7"/>`,
|
|
53
|
+
database: `<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.7-4 3-9 3s-9-1.3-9-3M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/>`,
|
|
54
|
+
hand: `<path d="M18 11V6a2 2 0 0 0-4 0v1M14 10V4a2 2 0 0 0-4 0v2M10 10.5V6a2 2 0 0 0-4 0v8"/><path d="M18 8a2 2 0 0 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.9-5.9-2.3L2.6 15.6a2 2 0 0 1 2.8-2.8L7 14.4"/>`,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function install() {
|
|
58
|
+
if (document.getElementById("viberoom-icons")) return;
|
|
59
|
+
const symbols = Object.entries(ICONS)
|
|
60
|
+
.map(([name, body]) => `<symbol id="i-${name}" viewBox="0 0 24 24">${body}</symbol>`)
|
|
61
|
+
.join("");
|
|
62
|
+
const holder = document.createElement("div");
|
|
63
|
+
holder.id = "viberoom-icons";
|
|
64
|
+
holder.setAttribute("aria-hidden", "true");
|
|
65
|
+
holder.style.cssText = "position:absolute;width:0;height:0;overflow:hidden";
|
|
66
|
+
holder.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg">${symbols}</svg>`;
|
|
67
|
+
document.body.prepend(holder);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function svg(name, cls) {
|
|
71
|
+
if (!ICONS[name]) name = "info";
|
|
72
|
+
return `<svg class="i${cls ? ` ${cls}` : ""}" aria-hidden="true"><use href="#i-${name}"/></svg>`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
window.Icons = { install, svg, names: Object.keys(ICONS) };
|
|
76
|
+
})();
|
package/ui/index.html
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<!-- viberoom - Copyright (c) 2026 Todor Rusev - AGPL-3.0-or-later; see LICENSE -->
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<title>viberoom</title>
|
|
8
|
+
<link rel="icon" href="/icon.svg?v=7" type="image/svg+xml">
|
|
9
|
+
<link rel="icon" href="/favicon.ico?v=7" sizes="any">
|
|
10
|
+
<link rel="apple-touch-icon" href="/icon-256.png?v=7">
|
|
11
|
+
<link rel="manifest" href="/manifest.json">
|
|
12
|
+
<meta name="theme-color" content="#6d5dfc">
|
|
13
|
+
<link rel="stylesheet" href="/theme.css">
|
|
14
|
+
<link rel="stylesheet" href="/app.css">
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div id="app" class="shell view-rooms">
|
|
18
|
+
<nav class="rail" id="rail" aria-label="Main menu">
|
|
19
|
+
<div class="rail-head">
|
|
20
|
+
<button class="rail-logo" id="rail-logo" title="viberoom"><img class="mark" src="/icon-256.png?v=7" alt=""><span class="word">viberoom</span></button>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="rail-items">
|
|
23
|
+
<button class="rail-item new" id="rail-new-room" title="Open a room"><span class="ico" data-icon="plus"></span><span class="label">Open a room</span></button>
|
|
24
|
+
<button class="rail-item" data-nav="rooms" title="Rooms"><span class="ico" data-icon="rooms"></span><span class="label">Rooms</span></button>
|
|
25
|
+
<button class="rail-item" data-nav="room" title="Current room" id="rail-room" hidden><span class="ico" data-icon="chat"></span><span class="label" id="rail-room-label">Room</span><span class="rail-count" id="rail-unread" hidden></span></button>
|
|
26
|
+
<button class="rail-item" data-nav="skills" title="Skills"><span class="ico" data-icon="skills"></span><span class="label">Skills</span></button>
|
|
27
|
+
<button class="rail-item" data-nav="settings" title="Settings"><span class="ico" data-icon="settings"></span><span class="label">Settings</span></button>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="rail-foot">
|
|
30
|
+
<button class="rail-item me" data-nav="me" title="Your vibe" id="rail-me"><span class="ico" id="rail-me-avatar"></span><span class="label" id="rail-me-label">You</span></button>
|
|
31
|
+
<div class="rail-status"><span class="conn" id="conn" title="Connection to the hub"></span><span class="label conn-label" id="conn-label">connected</span></div>
|
|
32
|
+
<button class="rail-toggle" id="rail-toggle" title="Expand the menu"><span data-icon="expand"></span></button>
|
|
33
|
+
</div>
|
|
34
|
+
</nav>
|
|
35
|
+
<aside class="side" id="side">
|
|
36
|
+
<div class="side-rooms" id="side-rooms">
|
|
37
|
+
<div class="side-head">
|
|
38
|
+
<h2 id="side-rooms-title">Rooms</h2>
|
|
39
|
+
</div>
|
|
40
|
+
<label class="search"><span class="search-icon" data-icon="search"></span><input id="room-search" type="search" placeholder="Find a room…" autocomplete="off"></label>
|
|
41
|
+
<ul class="side-list" id="room-list"></ul>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="side-room" id="side-room" hidden>
|
|
44
|
+
<div class="side-head room">
|
|
45
|
+
<button class="icon-btn ghost sm" id="back-to-rooms" title="All rooms"><span data-icon="back"></span></button>
|
|
46
|
+
<div class="side-room-title"><h2><span id="side-room-name">Room</span> <span id="side-room-emoji"></span></h2><div class="hint" id="side-room-sub"></div></div>
|
|
47
|
+
<button class="icon-btn ghost sm" id="room-settings-btn" title="Room settings"><span data-icon="settings"></span></button>
|
|
48
|
+
<button class="icon-btn ghost sm" id="side-toggle" title="Fold this column"><span data-icon="collapse"></span></button>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="side-actions">
|
|
51
|
+
<button id="invite-btn" class="btn primary cta full"><span class="cta-ico" data-icon="spark"></span><span class="label">Summon a Vibemate</span></button>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="side-label">In this room</div>
|
|
54
|
+
<ul id="participants" class="side-list participants"></ul>
|
|
55
|
+
<div class="side-foot">
|
|
56
|
+
<button id="reconnect-all-btn" class="btn wake full" hidden title="Bring offline vibemates back"><span data-icon="refresh"></span><span class="label">Wake everyone up</span></button>
|
|
57
|
+
<button id="focus-btn" class="btn hush full" title="Stop every running reply; vibemates stay quiet until you speak again"><span class="hush-face">🤫</span><span class="label">Hush the room</span></button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</aside>
|
|
61
|
+
<main class="main" id="main">
|
|
62
|
+
<section class="home" id="home-view" hidden></section>
|
|
63
|
+
<section class="rooms-view" id="rooms-view">
|
|
64
|
+
<header class="main-head">
|
|
65
|
+
<div><h1>Your rooms</h1><div class="hint" id="rooms-sub">Pick a room or open a new one.</div></div>
|
|
66
|
+
</header>
|
|
67
|
+
<div class="rooms-grid" id="rooms-grid"></div>
|
|
68
|
+
</section>
|
|
69
|
+
<section class="chat" id="chat-view" hidden>
|
|
70
|
+
<header class="main-head chat-head">
|
|
71
|
+
<div class="chat-title">
|
|
72
|
+
<h1 id="chat-room-name">…</h1>
|
|
73
|
+
<div class="chat-sub" id="chat-room-sub"></div>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="chat-actions">
|
|
76
|
+
<label class="search"><span class="search-icon" data-icon="search"></span><input id="search" type="search" placeholder="Search in this room…" autocomplete="off"></label>
|
|
77
|
+
<button id="chat-info-btn" class="icon-btn" title="Room settings"><span data-icon="settings"></span></button>
|
|
78
|
+
</div>
|
|
79
|
+
</header>
|
|
80
|
+
<div id="messages" class="messages"></div>
|
|
81
|
+
<div id="mention-menu" class="mention-menu" hidden></div>
|
|
82
|
+
<div id="emoji-menu" class="mention-menu emoji-menu" hidden></div>
|
|
83
|
+
<form id="composer" class="composer">
|
|
84
|
+
<button type="button" class="smile-btn" id="emoji-btn" title="Add an emoji"><span data-icon="smile"></span></button>
|
|
85
|
+
<div class="composer-box">
|
|
86
|
+
<textarea id="input" rows="1" placeholder="Message the room… @Name or /skill" title="Enter sends, Shift+Enter is a new line"></textarea>
|
|
87
|
+
</div>
|
|
88
|
+
<button type="submit" class="send-btn" title="Send"><span data-icon="send"></span></button>
|
|
89
|
+
</form>
|
|
90
|
+
</section>
|
|
91
|
+
<section class="page" id="page-view" hidden>
|
|
92
|
+
<div class="page-inner" id="page-inner"></div>
|
|
93
|
+
</section>
|
|
94
|
+
</main>
|
|
95
|
+
<aside class="details" id="details" hidden>
|
|
96
|
+
<div class="resizer" id="details-resizer" title="Drag to resize; double-click to fit"></div>
|
|
97
|
+
<div class="details-inner" id="details-inner"></div>
|
|
98
|
+
</aside>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="toasts" id="toasts"></div>
|
|
101
|
+
<dialog id="profile-dialog" class="dialog">
|
|
102
|
+
<form id="profile-form" method="dialog">
|
|
103
|
+
<h3><span class="h-ico" data-icon="spark"></span>Welcome to viberoom</h3>
|
|
104
|
+
<p class="lead">Every room needs a human with a vibe. Set yours: it is how the vibemates will know you. You can change it any time under Your vibe.</p>
|
|
105
|
+
<label>Vibename <span class="hint">(how you appear in every room)</span><input id="pf-name" type="text" maxlength="24" required autocomplete="off" placeholder="letters, digits, _ or -"></label>
|
|
106
|
+
<label>Vibeface <span class="hint">(pick one, or type any emoji)</span>
|
|
107
|
+
<div id="pf-avatar-picker"></div>
|
|
108
|
+
<input id="pf-avatar" type="text" maxlength="8" placeholder="custom emoji (optional)"></label>
|
|
109
|
+
<label>Your vibe line <span class="hint">(a sentence or two about you)</span><button type="button" class="geek-tip" title="For geeks"><span data-icon="geek"></span>for geeks</button><span class="geek-text" hidden>The vibemates get this line in every room's brief, so they know who they are talking to. A room can add its own line or replace it (Your vibe, in the room).</span>
|
|
110
|
+
<textarea id="pf-desc" rows="3" maxlength="200" placeholder="e.g. software engineer, curious about agent protocols; likes short answers"></textarea></label>
|
|
111
|
+
<p class="error" id="pf-error" hidden></p>
|
|
112
|
+
<div class="actions"><button type="submit" class="btn primary lg">Start vibing</button></div>
|
|
113
|
+
</form>
|
|
114
|
+
</dialog>
|
|
115
|
+
<dialog id="confirm-dialog" class="dialog narrow">
|
|
116
|
+
<form method="dialog">
|
|
117
|
+
<h3 id="cf-title">Are you sure?</h3>
|
|
118
|
+
<p class="lead" id="cf-text"></p>
|
|
119
|
+
<div class="actions"><button class="btn ghost" value="cancel" type="submit">Cancel</button><button class="btn primary" id="cf-ok" value="ok" type="submit">OK</button></div>
|
|
120
|
+
</form>
|
|
121
|
+
</dialog>
|
|
122
|
+
<dialog id="room-dialog" class="dialog">
|
|
123
|
+
<form id="room-form" method="dialog">
|
|
124
|
+
<h3><span class="h-ico" data-icon="rooms"></span>Open a room</h3>
|
|
125
|
+
<p class="lead">A room is a shared space for you and the vibemates you summon. Give it a name.</p>
|
|
126
|
+
<label>Room name<input id="room-name" type="text" maxlength="60" required placeholder="e.g. Architecture review"></label>
|
|
127
|
+
<label>Working directory <span class="hint">(optional)</span><button type="button" class="geek-tip" title="For geeks"><span data-icon="geek"></span>for geeks</button><span class="geek-text" hidden>The folder the vibemates work in (their cwd). Leave it empty and the hub keeps a folder per room. Instruction files in it (CLAUDE.md, AGENTS.md, GEMINI.md, .cursor/rules) are read by the vibemates by their own conventions; the hub tells you if it finds any.</span>
|
|
128
|
+
<input id="room-dir" type="text" placeholder="C:\projects\my-app"></label>
|
|
129
|
+
<p class="error" id="room-error" hidden></p>
|
|
130
|
+
<div class="actions"><button type="button" class="btn ghost" data-close>Cancel</button><button type="submit" class="btn primary">Open the room</button></div>
|
|
131
|
+
</form>
|
|
132
|
+
</dialog>
|
|
133
|
+
<dialog id="invite-dialog" class="dialog wide">
|
|
134
|
+
<form id="invite-form" method="dialog">
|
|
135
|
+
<h3><span class="h-ico" data-icon="spark"></span>Summon a Vibemate</h3>
|
|
136
|
+
<p class="lead">Pick a vibemate, give it a vibename and a character, and it joins the room.</p>
|
|
137
|
+
<div class="agent-grid" id="inv-agents"></div>
|
|
138
|
+
<p class="hint" id="inv-none" hidden></p>
|
|
139
|
+
<select id="inv-type" hidden></select>
|
|
140
|
+
<div id="inv-options" hidden>
|
|
141
|
+
<div class="choice-row"><span class="label">Model</span><div class="chips" data-for="inv-model"></div><select id="inv-model" hidden></select><input id="inv-model-custom" type="text" placeholder="or type a model name" hidden></div>
|
|
142
|
+
<div class="choice-row"><span class="label">Effort</span><div class="chips" data-for="inv-effort"></div><select id="inv-effort" hidden></select></div>
|
|
143
|
+
<div class="choice-row"><span class="label">Mode</span><div class="chips" data-for="inv-mode"></div><select id="inv-mode" hidden></select></div>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="two-col">
|
|
146
|
+
<label>Vibename <span class="hint">(required)</span><input id="inv-name" type="text" maxlength="24" required autocomplete="off" placeholder="letters, digits, _ or -"></label>
|
|
147
|
+
<label>Vibersona <span class="hint">(optional; a few words under the vibename)</span><input id="inv-tagline" type="text" maxlength="80" placeholder="e.g. curious analyst"></label>
|
|
148
|
+
</div>
|
|
149
|
+
<label>Vibeface <span class="hint">(pick one, or type any emoji)</span>
|
|
150
|
+
<div id="inv-avatar-picker"></div>
|
|
151
|
+
<input id="inv-avatar" type="text" maxlength="8" placeholder="custom emoji (optional)"></label>
|
|
152
|
+
<label>Vibio <span class="hint">(only this vibemate reads it)</span><button type="button" class="geek-tip" title="For geeks"><span data-icon="geek"></span>for geeks</button><span class="geek-text" hidden>Who it is, how it speaks, what it cares about. It reaches the vibemate as instructions in its brief; the other participants never see it. You can change it later in the vibemate's panel.</span>
|
|
153
|
+
<textarea id="inv-role" rows="4" maxlength="4000" placeholder="e.g. A calm team lead who asks one question at a time."></textarea></label>
|
|
154
|
+
<details class="geek" id="inv-geek">
|
|
155
|
+
<summary><span data-icon="geek"></span>for geeks<span class="g-hint">reply delay, skills, where the vibemate lives</span><span class="chev" data-icon="down"></span></summary>
|
|
156
|
+
<div class="geek-body">
|
|
157
|
+
<label>Reply delay override, seconds<button type="button" class="geek-tip" title="For geeks"><span data-icon="geek"></span>for geeks</button><span class="geek-text" hidden>Empty: the room's delay. A value here overrides it for this vibemate only. Before each turn the vibemate waits a random 0–N seconds, so replies cross less often; messages that arrive meanwhile land in its backlog.</span><input id="inv-delay" type="number" min="0" max="120" step="0.5" placeholder="the room's"></label>
|
|
158
|
+
<label>Skills <span class="hint">(from the library)</span><div id="inv-skills" class="check-list"></div></label>
|
|
159
|
+
<p class="hint" id="inv-where"></p>
|
|
160
|
+
<p class="hint" id="inv-note"></p>
|
|
161
|
+
<p class="hint accent" id="inv-status"></p>
|
|
162
|
+
<div class="row-btns start"><button type="button" id="inv-refresh" class="btn ghost sm" title="Ask the vibemate again what it offers"><span data-icon="refresh"></span>Ask the vibemate again what it offers</button></div>
|
|
163
|
+
</div>
|
|
164
|
+
</details>
|
|
165
|
+
<p class="error" id="inv-error" hidden></p>
|
|
166
|
+
<div class="actions">
|
|
167
|
+
<button type="button" class="btn ghost" data-close>Cancel</button>
|
|
168
|
+
<button type="submit" id="inv-submit" class="btn primary">Summon</button>
|
|
169
|
+
</div>
|
|
170
|
+
</form>
|
|
171
|
+
</dialog>
|
|
172
|
+
<dialog id="edit-dialog" class="dialog">
|
|
173
|
+
<form id="edit-form" method="dialog">
|
|
174
|
+
<h3><span class="h-ico" data-icon="pencil"></span>Edit your message</h3>
|
|
175
|
+
<p class="lead" id="ed-summary"></p>
|
|
176
|
+
<div class="choice-list">
|
|
177
|
+
<div class="choice"><span><strong>Notify vibemates</strong> — the message changes in place; the vibemates that read the old version are told.<button type="button" class="geek-tip" title="For geeks"><span data-icon="geek"></span>for geeks</button><span class="geek-text" hidden>The vibemates that already read the old version get a room event with the change and reply only if it matters to them. Cheap; their memory still holds the old wording.</span></span></div>
|
|
178
|
+
<div class="choice"><span><strong>Rewrite from here</strong> — <span id="ed-rewrite-detail"></span> The vibemates that read the old version start over from this message.<button type="button" class="geek-tip" title="For geeks"><span data-icon="geek"></span>for geeks</button><span class="geek-text" hidden>Those vibemates restart with a fresh session and the conversation up to this message; they lose their working memory (tool results, reasoning). The removed messages are kept for you in <code>history.deleted.jsonl</code>.</span></span></div>
|
|
179
|
+
</div>
|
|
180
|
+
<p class="error" id="ed-error" hidden></p>
|
|
181
|
+
<div class="actions">
|
|
182
|
+
<button type="button" class="btn ghost" data-close>Cancel</button>
|
|
183
|
+
<button type="button" id="ed-rewrite" class="btn danger">Rewrite from here</button>
|
|
184
|
+
<button type="button" id="ed-notify" class="btn primary" autofocus>Notify vibemates</button>
|
|
185
|
+
</div>
|
|
186
|
+
</form>
|
|
187
|
+
</dialog>
|
|
188
|
+
<dialog id="reconnect-dialog" class="dialog">
|
|
189
|
+
<form id="reconnect-form" method="dialog">
|
|
190
|
+
<h3><span class="h-ico" data-icon="refresh"></span>Welcome back</h3>
|
|
191
|
+
<p class="lead" id="rc-intro"></p>
|
|
192
|
+
<div class="choice-list">
|
|
193
|
+
<label class="choice"><input type="radio" name="rc-mode" value="replay" checked> <span><strong>Replay the last</strong> <input id="rc-replay" type="number" min="0" max="500" value="10" class="rc-num"> <strong>chat messages</strong> — a fresh start with the recent history.<button type="button" class="geek-tip" title="For geeks"><span data-icon="geek"></span>for geeks</button><span class="geek-text" hidden>A new vibemate session that reads the recent room history (room events in between come along), like a person coming back from a break. Cheap and works for every vibemate.</span></span></label>
|
|
194
|
+
<label class="choice"><input type="radio" name="rc-mode" value="load"> <span><strong>Restore the full session</strong> — the vibemate comes back with everything it knew.<button type="button" class="geek-tip" title="For geeks"><span data-icon="geek"></span>for geeks</button><span class="geek-text" hidden>ACP <code>session/load</code>: the vibemate reloads its own stored conversation, with tool results and reasoning. Its next turn pays for the whole history again. Falls back to replay if the vibemate cannot load it.</span></span></label>
|
|
195
|
+
</div>
|
|
196
|
+
<table class="rc-table" id="rc-table"></table>
|
|
197
|
+
<p class="error" id="rc-error" hidden></p>
|
|
198
|
+
<div class="actions">
|
|
199
|
+
<button type="button" class="btn ghost" data-close>Later</button>
|
|
200
|
+
<button type="submit" id="rc-submit" class="btn primary">Reconnect</button>
|
|
201
|
+
</div>
|
|
202
|
+
</form>
|
|
203
|
+
</dialog>
|
|
204
|
+
<dialog id="erase-dialog" class="dialog narrow">
|
|
205
|
+
<form id="erase-form" method="dialog">
|
|
206
|
+
<h3><span class="h-ico danger" data-icon="alert"></span>Erase my vibe</h3>
|
|
207
|
+
<p class="lead">This wipes <strong>everything</strong> in this viberoom: your vibe, all rooms and their history, every vibemate session, your skills (the built-in ones come back). The next start asks you to set a vibe again.</p>
|
|
208
|
+
<label>Type <code>erase</code> to confirm<input id="erase-word" type="text" autocomplete="off" placeholder="erase"></label>
|
|
209
|
+
<p class="error" id="erase-error" hidden></p>
|
|
210
|
+
<div class="actions"><button type="button" class="btn ghost" data-close>Keep my vibe</button><button type="submit" id="erase-submit" class="btn danger solid" disabled>Erase everything</button></div>
|
|
211
|
+
</form>
|
|
212
|
+
</dialog>
|
|
213
|
+
<script src="/icons.js"></script>
|
|
214
|
+
<script src="/avatars.js"></script>
|
|
215
|
+
<script src="/app.js"></script>
|
|
216
|
+
</body>
|
|
217
|
+
</html>
|
package/ui/manifest.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "viberoom",
|
|
3
|
+
"short_name": "viberoom",
|
|
4
|
+
"description": "Group chat rooms for a human and several coding agents over the Agent Client Protocol",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"background_color": "#f3f2fb",
|
|
8
|
+
"theme_color": "#6d5dfc",
|
|
9
|
+
"icons": [
|
|
10
|
+
{ "src": "/icon.svg?v=6", "sizes": "any", "type": "image/svg+xml", "purpose": "any" },
|
|
11
|
+
{ "src": "/icon-256.png?v=6", "sizes": "256x256", "type": "image/png" },
|
|
12
|
+
{ "src": "/icon-512.png?v=6", "sizes": "512x512", "type": "image/png" }
|
|
13
|
+
]
|
|
14
|
+
}
|