shadok-ai 0.15.50 → 0.15.52
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/package.json +1 -1
- package/public/echo-author.js +24 -0
- package/public/index.html +74 -4
package/package.json
CHANGED
package/public/echo-author.js
CHANGED
|
@@ -24,3 +24,27 @@ export function echoAuthor(msg) {
|
|
|
24
24
|
// from another of your tabs; a single cockpit has one human.
|
|
25
25
|
return place || "you";
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Is this prompt the viewer's OWN? Decides the side of the bubble: yours on the
|
|
30
|
+
* right, anyone else's — a teammate on the web, a Telegram sender, a cron, the
|
|
31
|
+
* auto-retry — on the left, still tinted as a human message (only the agent is
|
|
32
|
+
* white).
|
|
33
|
+
*
|
|
34
|
+
* Only a web prompt can be yours: another platform is another person, or nobody.
|
|
35
|
+
* Without a name it is a prompt from this browser or a cockpit with no accounts
|
|
36
|
+
* — yours. While `me` is not known yet (`/me` still in flight), a named web
|
|
37
|
+
* prompt is taken as yours too: guessing "someone else" would flash your own
|
|
38
|
+
* history to the left on every reload.
|
|
39
|
+
*
|
|
40
|
+
* @param {{from?: string, origin?: string, auto?: boolean}} msg
|
|
41
|
+
* @param {string | null | undefined} me the signed-in account's name, if known
|
|
42
|
+
*/
|
|
43
|
+
export function isOwnPrompt(msg, me) {
|
|
44
|
+
if (msg?.auto) return false;
|
|
45
|
+
const origin = (msg?.origin ?? "").trim();
|
|
46
|
+
if (origin && origin !== "web") return false;
|
|
47
|
+
const from = (msg?.from ?? "").trim();
|
|
48
|
+
if (!from || !me) return true;
|
|
49
|
+
return from === me;
|
|
50
|
+
}
|
package/public/index.html
CHANGED
|
@@ -187,7 +187,36 @@
|
|
|
187
187
|
background: var(--amber); color: var(--on-accent);
|
|
188
188
|
display: grid; place-items: center; margin-right: 4px;
|
|
189
189
|
}
|
|
190
|
-
header .brand-logo svg { width:
|
|
190
|
+
header .brand-logo svg { width: 22px; height: 22px; overflow: visible; }
|
|
191
|
+
/* The mark is the Shadok itself (same anatomy as the favicon's `shadokInner`),
|
|
192
|
+
alive: it bobs, its antennae sway, it blinks and glances around, and waves
|
|
193
|
+
now and then. Pure CSS on static markup, so it costs no JS timer and needs
|
|
194
|
+
no bridge (invariant 10). Hover makes it hop. Every part rotates/scales
|
|
195
|
+
about its OWN box (`transform-box: fill-box`), never the SVG's origin. */
|
|
196
|
+
header .brand-logo svg * { transform-box: fill-box; }
|
|
197
|
+
header .brand-logo .sk-pupils { fill: var(--amber); animation: sk-glance 7s ease-in-out infinite; }
|
|
198
|
+
header .brand-logo .sk-bob { animation: sk-bob 2.4s ease-in-out infinite; }
|
|
199
|
+
header .brand-logo .sk-ant { animation: sk-sway 1.6s ease-in-out infinite alternate; }
|
|
200
|
+
header .brand-logo .sk-ant-l { transform-origin: 100% 100%; }
|
|
201
|
+
header .brand-logo .sk-ant-r { transform-origin: 0% 100%; animation-delay: -0.8s; }
|
|
202
|
+
header .brand-logo .sk-eyes { transform-origin: 50% 50%; animation: sk-blink 4.6s infinite; }
|
|
203
|
+
header .brand-logo .sk-arm { transform-origin: 0% 0%; animation: sk-wave 9s ease-in-out infinite; }
|
|
204
|
+
header .brand:hover .brand-logo svg { animation: sk-hop 0.6s ease-out; }
|
|
205
|
+
@keyframes sk-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(0.9px); } }
|
|
206
|
+
@keyframes sk-sway { from { transform: rotate(-14deg); } to { transform: rotate(12deg); } }
|
|
207
|
+
@keyframes sk-blink { 0%, 90%, 96%, 100% { transform: scaleY(1); } 93% { transform: scaleY(0.12); } }
|
|
208
|
+
@keyframes sk-glance {
|
|
209
|
+
0%, 20% { transform: translate(0, 0); } 28%, 45% { transform: translate(-0.8px, 0.2px); }
|
|
210
|
+
53%, 70% { transform: translate(0.6px, -0.3px); } 78%, 100% { transform: translate(0, 0); }
|
|
211
|
+
}
|
|
212
|
+
@keyframes sk-wave {
|
|
213
|
+
0%, 70%, 100% { transform: rotate(0); } 74% { transform: rotate(-70deg); }
|
|
214
|
+
78% { transform: rotate(-35deg); } 82% { transform: rotate(-70deg); } 86% { transform: rotate(0); }
|
|
215
|
+
}
|
|
216
|
+
@keyframes sk-hop { 0%, 100% { transform: translateY(0); } 40% { transform: translateY(-4px); } 70% { transform: translateY(0.5px); } }
|
|
217
|
+
@media (prefers-reduced-motion: reduce) {
|
|
218
|
+
header .brand-logo svg, header .brand-logo svg * { animation: none !important; }
|
|
219
|
+
}
|
|
191
220
|
/* The brand name doubles as the cockpit's editable title: click to rename,
|
|
192
221
|
Enter/blur commits, Escape cancels. A dashed outline + pencil on hover make
|
|
193
222
|
the affordance obvious; the outline turns solid amber while editing. */
|
|
@@ -1128,6 +1157,11 @@
|
|
|
1128
1157
|
between this bubble and the one before, so it keeps its own label — a
|
|
1129
1158
|
wordless boundary the reader would otherwise never see. */
|
|
1130
1159
|
.turn.user { align-self: flex-end; align-items: flex-end; }
|
|
1160
|
+
/* Someone else's prompt (a teammate, a Telegram sender, a cron): on the
|
|
1161
|
+
agent's side, still a human bubble — same tint as yours, only the agent is
|
|
1162
|
+
white. The side and the name say who; a colour per person read as noise. */
|
|
1163
|
+
.turn.user.other { align-self: flex-start; align-items: flex-start; }
|
|
1164
|
+
.turn.user + .turn.user.new-speaker > .label { display: block; }
|
|
1131
1165
|
.turn.claude { align-self: flex-start; }
|
|
1132
1166
|
.bubble {
|
|
1133
1167
|
padding: 9px 14px;
|
|
@@ -2283,7 +2317,7 @@
|
|
|
2283
2317
|
<body>
|
|
2284
2318
|
|
|
2285
2319
|
<header>
|
|
2286
|
-
<span class="brand"><span class="brand-logo" aria-hidden="true"><svg viewBox="0 0
|
|
2320
|
+
<span class="brand"><span class="brand-logo" aria-hidden="true"><svg viewBox="0 0 32 32"><g fill="currentColor" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"><g class="sk-leg sk-leg-l"><line x1="13" y1="23" x2="12.6" y2="30.6"/><line x1="11" y1="30.7" x2="14.1" y2="30.7"/></g><g class="sk-leg sk-leg-r"><line x1="18.6" y1="23" x2="19" y2="30.6"/><line x1="17.6" y1="30.7" x2="20.7" y2="30.7"/></g><g class="sk-bob"><line class="sk-arm" x1="21.6" y1="17.2" x2="26" y2="20.4"/><g class="sk-ant sk-ant-l" stroke-width="0.9"><line x1="13.8" y1="5.4" x2="11.8" y2="2.8"/><circle cx="11.8" cy="2.8" r="1" stroke="none"/></g><g class="sk-ant sk-ant-r" stroke-width="0.9"><line x1="18.2" y1="5.4" x2="20.2" y2="2.8"/><circle cx="20.2" cy="2.8" r="1" stroke="none"/></g><circle cx="16" cy="16.2" r="8.4" stroke="none"/><path d="M14.4 18.2 L17.6 18.2 L16 21.6 Z" stroke="none"/><g class="sk-eyes" stroke="none"><circle cx="13.1" cy="7.8" r="2.6" fill="#fff"/><circle cx="18.9" cy="7.8" r="2.6" fill="#fff"/><g class="sk-pupils"><circle cx="13.4" cy="8.1" r="1.3"/><circle cx="19.2" cy="8.1" r="1.3"/></g></g></g></g></svg></span><span class="brand-name" id="brandName" title="Rename this cockpit">shadok-ai</span><span class="brand-edit" id="brandEdit" title="Rename this cockpit" aria-hidden="true">✎</span> <span class="ver" id="verView" title="Running version"></span><span class="ver-chan" id="verChan" aria-hidden="true"></span></span>
|
|
2287
2321
|
<span class="spacer"></span>
|
|
2288
2322
|
<div class="gauge quota" id="quota5h" data-window="5h" title="5h rolling limit">
|
|
2289
2323
|
<svg class="dial" viewBox="0 0 56 44" role="img" aria-label="5h rolling limit">
|
|
@@ -3047,7 +3081,7 @@
|
|
|
3047
3081
|
hasReadonlyPreset,
|
|
3048
3082
|
applyReadonlyPreset, isManagedProfile, profileSaveBody, secretUsers } from "/profile-card.js?v=__ASSET_V__";
|
|
3049
3083
|
import { notifyState, BLINK_MS } from "/notify.js?v=__ASSET_V__";
|
|
3050
|
-
import { echoAuthor } from "/echo-author.js?v=__ASSET_V__";
|
|
3084
|
+
import { echoAuthor, isOwnPrompt } from "/echo-author.js?v=__ASSET_V__";
|
|
3051
3085
|
import { dialPos, dialColor, arcSegments, dialTitle, SWEEP_DEG } from "/gauge-dial.js?v=__ASSET_V__";
|
|
3052
3086
|
import { TOUR_STEPS, bubblePlacement, unionRect, visibleSteps } from "/tour-steps.js?v=__ASSET_V__";
|
|
3053
3087
|
import { pickChannelSource, dirKey, serverSwapped } from "/channel-store.js?v=__ASSET_V__";
|
|
@@ -3105,6 +3139,7 @@
|
|
|
3105
3139
|
window.hasReadonlyPreset = hasReadonlyPreset;
|
|
3106
3140
|
window.applyReadonlyPreset = applyReadonlyPreset;
|
|
3107
3141
|
window.echoAuthor = echoAuthor;
|
|
3142
|
+
window.isOwnPrompt = isOwnPrompt;
|
|
3108
3143
|
window.notifyState = notifyState;
|
|
3109
3144
|
window.BLINK_MS = BLINK_MS;
|
|
3110
3145
|
window.dialPos = dialPos;
|
|
@@ -3154,6 +3189,7 @@
|
|
|
3154
3189
|
extractLiveText: () => "",
|
|
3155
3190
|
livePreviewDecision: () => null,
|
|
3156
3191
|
echoAuthor: () => "",
|
|
3192
|
+
isOwnPrompt: () => true,
|
|
3157
3193
|
notifyState: () => ({ color: null, badge: "", blink: false }),
|
|
3158
3194
|
dialPos: () => 0,
|
|
3159
3195
|
dialColor: () => "",
|
|
@@ -5920,6 +5956,32 @@
|
|
|
5920
5956
|
return window.echoAuthor ? window.echoAuthor(msg) : "you";
|
|
5921
5957
|
}
|
|
5922
5958
|
|
|
5959
|
+
/** Which side a human prompt sits on: yours on the right, anyone else's on
|
|
5960
|
+
* the left — still a tinted human bubble, only the agent is white. The author
|
|
5961
|
+
* is kept on the turn so the side can be re-decided once `/me` answers
|
|
5962
|
+
* (`resideAllPrompts`). */
|
|
5963
|
+
function markPromptSide(bubble, msg) {
|
|
5964
|
+
const turn = bubble.parentElement;
|
|
5965
|
+
turn.dataset.from = msg?.from ?? "";
|
|
5966
|
+
turn.dataset.origin = msg?.origin ?? "";
|
|
5967
|
+
if (msg?.auto) turn.dataset.auto = "1";
|
|
5968
|
+
// A run of prompts shows one label — but only while it is ONE speaker.
|
|
5969
|
+
const prev = turn.previousElementSibling;
|
|
5970
|
+
const who = turn.querySelector(":scope > .label")?.firstChild?.textContent ?? "";
|
|
5971
|
+
const prevWho = prev?.classList.contains("user") ? prev.querySelector(":scope > .label")?.firstChild?.textContent ?? "" : who;
|
|
5972
|
+
turn.classList.toggle("new-speaker", who !== prevWho);
|
|
5973
|
+
applyPromptSide(turn);
|
|
5974
|
+
}
|
|
5975
|
+
function applyPromptSide(turn) {
|
|
5976
|
+
const msg = { from: turn.dataset.from, origin: turn.dataset.origin, auto: turn.dataset.auto === "1" };
|
|
5977
|
+
const own = window.isOwnPrompt ? window.isOwnPrompt(msg, me?.name) : true;
|
|
5978
|
+
turn.classList.toggle("other", !own);
|
|
5979
|
+
}
|
|
5980
|
+
function resideAllPrompts() {
|
|
5981
|
+
for (const turn of document.querySelectorAll(".turn.user[data-origin]")) applyPromptSide(turn);
|
|
5982
|
+
}
|
|
5983
|
+
window.addEventListener("shadok-bridge-ready", resideAllPrompts);
|
|
5984
|
+
|
|
5923
5985
|
function addTurn(tab, role, who, text, extraClass, at) {
|
|
5924
5986
|
// Time of the last message in the channel list, relative ("3m ago"). Every
|
|
5925
5987
|
// message (replayed history or live) carries `at`; the last one wins.
|
|
@@ -7423,6 +7485,7 @@
|
|
|
7423
7485
|
// SAME echoAuthor the live echo uses: "Ada · telegram"
|
|
7424
7486
|
// for a Telegram message, "you" for your own web prompt.
|
|
7425
7487
|
const b = addTurn(t, "user", echoAuthorLabel({ from: h.from, origin: h.origin }), h.text, "hist", h.at);
|
|
7488
|
+
markPromptSide(b, { from: h.from, origin: h.origin });
|
|
7426
7489
|
stampMsgIds(t, b.parentElement, h.ids);
|
|
7427
7490
|
} else {
|
|
7428
7491
|
const b = addTurn(t, "claude", "claude", h.text, "hist", h.at);
|
|
@@ -7537,6 +7600,7 @@
|
|
|
7537
7600
|
closeActivity(t);
|
|
7538
7601
|
{
|
|
7539
7602
|
const b = addTurn(t, "user", echoAuthorLabel(msg), msg.text, null, Date.now());
|
|
7603
|
+
markPromptSide(b, msg);
|
|
7540
7604
|
awaitPromptId(b, msg.text);
|
|
7541
7605
|
markUnreadBoundary(t, b.parentElement);
|
|
7542
7606
|
}
|
|
@@ -7993,7 +8057,11 @@
|
|
|
7993
8057
|
// Sending = going back to the bottom, whatever happens (even after scrolling
|
|
7994
8058
|
// up): you want to see your message and the answer that follows.
|
|
7995
8059
|
t.transcriptEl._stickBottom = true;
|
|
7996
|
-
|
|
8060
|
+
{
|
|
8061
|
+
const b = addTurn(t, "user", "you", text, null, Date.now());
|
|
8062
|
+
markPromptSide(b, {});
|
|
8063
|
+
awaitPromptId(b, text);
|
|
8064
|
+
}
|
|
7997
8065
|
startTurnTimer(t);
|
|
7998
8066
|
setTabMood(t, "working");
|
|
7999
8067
|
// Remember this channel's last sent prompt, so Escape (an interruption) can
|
|
@@ -9532,6 +9600,8 @@
|
|
|
9532
9600
|
$("signOut").hidden = !named;
|
|
9533
9601
|
$("whoSep").hidden = !named;
|
|
9534
9602
|
if (named) $("whoAmI").textContent = "Signed in as " + me.name + " · " + me.role;
|
|
9603
|
+
// Prompts drawn before we knew who you are get their side now.
|
|
9604
|
+
resideAllPrompts();
|
|
9535
9605
|
// Security: for a signed-in person (or the lone admin of a no-password
|
|
9536
9606
|
// instance, who is everybody). The server answers 403 otherwise.
|
|
9537
9607
|
$("securityBtn").hidden = !(me?.name || me?.role === "admin");
|