@athenaintel/react 0.60.0 → 0.61.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 +225 -2
- package/dist/agent-runtime/src/client-tool-bridge.cjs +20 -2
- package/dist/agent-runtime/src/client-tool-bridge.cjs.map +1 -1
- package/dist/agent-runtime/src/client-tool-bridge.d.ts +9 -4
- package/dist/agent-runtime/src/client-tool-bridge.js +20 -2
- package/dist/agent-runtime/src/client-tool-bridge.js.map +1 -1
- package/dist/agent-runtime/src/client-tools.cjs.map +1 -1
- package/dist/agent-runtime/src/client-tools.d.ts +9 -0
- package/dist/agent-runtime/src/client-tools.js.map +1 -1
- package/dist/agent-ui/AgentTarget.cjs +11 -0
- package/dist/agent-ui/AgentTarget.cjs.map +1 -0
- package/dist/agent-ui/AgentTarget.d.ts +17 -0
- package/dist/agent-ui/AgentTarget.js +11 -0
- package/dist/agent-ui/AgentTarget.js.map +1 -0
- package/dist/agent-ui/AgentUiLayer.cjs +387 -0
- package/dist/agent-ui/AgentUiLayer.cjs.map +1 -0
- package/dist/agent-ui/AgentUiLayer.d.ts +40 -0
- package/dist/agent-ui/AgentUiLayer.js +387 -0
- package/dist/agent-ui/AgentUiLayer.js.map +1 -0
- package/dist/agent-ui/contract.cjs +104 -0
- package/dist/agent-ui/contract.cjs.map +1 -0
- package/dist/agent-ui/contract.d.ts +113 -0
- package/dist/agent-ui/contract.js +104 -0
- package/dist/agent-ui/contract.js.map +1 -0
- package/dist/agent-ui/driver.cjs +335 -0
- package/dist/agent-ui/driver.cjs.map +1 -0
- package/dist/agent-ui/driver.d.ts +66 -0
- package/dist/agent-ui/driver.js +335 -0
- package/dist/agent-ui/driver.js.map +1 -0
- package/dist/agent-ui/index.cjs +33 -0
- package/dist/agent-ui/index.cjs.map +1 -0
- package/dist/agent-ui/index.d.ts +19 -0
- package/dist/agent-ui/index.js +33 -0
- package/dist/agent-ui/index.js.map +1 -0
- package/dist/agent-ui/pace.cjs +121 -0
- package/dist/agent-ui/pace.cjs.map +1 -0
- package/dist/agent-ui/pace.d.ts +66 -0
- package/dist/agent-ui/pace.js +121 -0
- package/dist/agent-ui/pace.js.map +1 -0
- package/dist/agent-ui/screen.cjs +471 -0
- package/dist/agent-ui/screen.cjs.map +1 -0
- package/dist/agent-ui/screen.d.ts +97 -0
- package/dist/agent-ui/screen.js +471 -0
- package/dist/agent-ui/screen.js.map +1 -0
- package/dist/agent-ui/secret-fields.cjs +50 -0
- package/dist/agent-ui/secret-fields.cjs.map +1 -0
- package/dist/agent-ui/secret-fields.d.ts +15 -0
- package/dist/agent-ui/secret-fields.js +50 -0
- package/dist/agent-ui/secret-fields.js.map +1 -0
- package/dist/agent-ui/stage.cjs +196 -0
- package/dist/agent-ui/stage.cjs.map +1 -0
- package/dist/agent-ui/stage.d.ts +110 -0
- package/dist/agent-ui/stage.js +196 -0
- package/dist/agent-ui/stage.js.map +1 -0
- package/dist/agent-ui/tools.cjs +448 -0
- package/dist/agent-ui/tools.cjs.map +1 -0
- package/dist/agent-ui/tools.d.ts +103 -0
- package/dist/agent-ui/tools.js +448 -0
- package/dist/agent-ui/tools.js.map +1 -0
- package/dist/agent-ui/use-agent-target.cjs +63 -0
- package/dist/agent-ui/use-agent-target.cjs.map +1 -0
- package/dist/agent-ui/use-agent-target.d.ts +27 -0
- package/dist/agent-ui/use-agent-target.js +63 -0
- package/dist/agent-ui/use-agent-target.js.map +1 -0
- package/dist/agent-ui/use-agent-ui.cjs +15 -0
- package/dist/agent-ui/use-agent-ui.cjs.map +1 -0
- package/dist/agent-ui/use-agent-ui.d.ts +24 -0
- package/dist/agent-ui/use-agent-ui.js +15 -0
- package/dist/agent-ui/use-agent-ui.js.map +1 -0
- package/dist/chat/AthenaChat.cjs +19 -4
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +13 -0
- package/dist/chat/AthenaChat.js +19 -4
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireClientToolBridge.cjs +74 -1
- package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
- package/dist/chat/StatewireClientToolBridge.d.ts +16 -1
- package/dist/chat/StatewireClientToolBridge.js +75 -2
- package/dist/chat/StatewireClientToolBridge.js.map +1 -1
- package/dist/chat/StatewireClientToolConsentCard.cjs +66 -0
- package/dist/chat/StatewireClientToolConsentCard.cjs.map +1 -0
- package/dist/chat/StatewireClientToolConsentCard.d.ts +25 -0
- package/dist/chat/StatewireClientToolConsentCard.js +66 -0
- package/dist/chat/StatewireClientToolConsentCard.js.map +1 -0
- package/dist/chat/chat-voice-controls.cjs +5 -2
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +6 -3
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat/client-tool-write-consent.cjs +122 -0
- package/dist/chat/client-tool-write-consent.cjs.map +1 -0
- package/dist/chat/client-tool-write-consent.d.ts +79 -0
- package/dist/chat/client-tool-write-consent.js +122 -0
- package/dist/chat/client-tool-write-consent.js.map +1 -0
- package/dist/chat/statewire-client-tools.cjs +30 -5
- package/dist/chat/statewire-client-tools.cjs.map +1 -1
- package/dist/chat/statewire-client-tools.d.ts +34 -2
- package/dist/chat/statewire-client-tools.js +30 -5
- package/dist/chat/statewire-client-tools.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs +13 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.d.ts +12 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js +13 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs +2 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.js +3 -2
- package/dist/chat-ui/mentions/suggestions/data/sources/index.js.map +1 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs +2 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs.map +1 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.d.ts +2 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.js +2 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.js.map +1 -1
- package/dist/chat-ui/messages/message-components.cjs +1 -1
- package/dist/chat-ui/messages/message-components.cjs.map +1 -1
- package/dist/chat-ui/messages/message-components.d.ts +2 -2
- package/dist/chat-ui/messages/message-components.js +1 -1
- package/dist/chat-ui/messages/message-components.js.map +1 -1
- package/dist/chat-ui/statewire.cjs +4 -0
- package/dist/chat-ui/statewire.cjs.map +1 -1
- package/dist/chat-ui/statewire.d.ts +2 -0
- package/dist/chat-ui/statewire.js +4 -0
- package/dist/chat-ui/statewire.js.map +1 -1
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +59 -11
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +25 -1
- package/dist/provider/AthenaProvider.js +55 -7
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const SECRET_AUTOCOMPLETE_TOKENS = [
|
|
2
|
+
"current-password",
|
|
3
|
+
"new-password",
|
|
4
|
+
"one-time-code",
|
|
5
|
+
"cc-number",
|
|
6
|
+
"cc-csc"
|
|
7
|
+
];
|
|
8
|
+
const SECRET_LABEL_PATTERN_SOURCE = [
|
|
9
|
+
"\\b(?:password|passwd|passcode|passphrase|pwd|pin|otp|totp|cvv|cvc|csc|secret|token)\\d*\\b",
|
|
10
|
+
"\\bone time (?:code|password|passcode|pin)\\b",
|
|
11
|
+
"\\b(?:verification|security|authentication|authenticator|auth|2fa|mfa|sms) code\\b",
|
|
12
|
+
"\\b(?:api|access|secret|private) key\\b"
|
|
13
|
+
].join("|");
|
|
14
|
+
const SECRET_LABEL_PATTERN = new RegExp(SECRET_LABEL_PATTERN_SOURCE, "i");
|
|
15
|
+
function normalizedFieldText(text) {
|
|
16
|
+
return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[^A-Za-z0-9]+/g, " ").toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
function isSecretFieldLabel(text) {
|
|
19
|
+
return SECRET_LABEL_PATTERN.test(normalizedFieldText(text));
|
|
20
|
+
}
|
|
21
|
+
function labelText(element) {
|
|
22
|
+
const own = element.labels;
|
|
23
|
+
const labels = own ? Array.from(own, (node) => node.textContent ?? "").join(" ") : "";
|
|
24
|
+
return [
|
|
25
|
+
element.getAttribute("aria-label"),
|
|
26
|
+
element.getAttribute("placeholder"),
|
|
27
|
+
element.getAttribute("name"),
|
|
28
|
+
element.id,
|
|
29
|
+
labels
|
|
30
|
+
].join(" ");
|
|
31
|
+
}
|
|
32
|
+
function isSecretField(element) {
|
|
33
|
+
if (element.tagName.toUpperCase() === "INPUT" && (element.getAttribute("type") ?? "").toLowerCase() === "password") {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const hint = (element.getAttribute("autocomplete") ?? "").toLowerCase().split(/\s+/);
|
|
37
|
+
if (hint.some((token) => SECRET_AUTOCOMPLETE_TOKENS.includes(token))) return true;
|
|
38
|
+
const view = element.ownerDocument.defaultView;
|
|
39
|
+
const masking = view == null ? void 0 : view.getComputedStyle(element).getPropertyValue("-webkit-text-security");
|
|
40
|
+
if (masking && masking !== "none") return true;
|
|
41
|
+
return isSecretFieldLabel(labelText(element));
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
SECRET_AUTOCOMPLETE_TOKENS,
|
|
45
|
+
SECRET_LABEL_PATTERN_SOURCE,
|
|
46
|
+
isSecretField,
|
|
47
|
+
isSecretFieldLabel,
|
|
48
|
+
normalizedFieldText
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=secret-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-fields.js","sources":["../../src/agent-ui/secret-fields.ts"],"sourcesContent":["/*\n * Fields whose value the agent never reads and never types: a password, a\n * one-time code, a card number or its security code.\n *\n * The rules are the ones Athena's browser tools apply to any web page\n * (`packages/browser-cdp/src/secret-fields.ts`, which this published package\n * cannot import); `secret-fields.test.ts` holds the two together.\n */\n\n/** `autocomplete` tokens that mark a field as holding a secret. */\nexport const SECRET_AUTOCOMPLETE_TOKENS: readonly string[] = [\n 'current-password',\n 'new-password',\n 'one-time-code',\n 'cc-number',\n 'cc-csc',\n];\n\n/** What a secret field's label, name, id or placeholder says. */\nexport const SECRET_LABEL_PATTERN_SOURCE = [\n '\\\\b(?:password|passwd|passcode|passphrase|pwd|pin|otp|totp|cvv|cvc|csc|secret|token)\\\\d*\\\\b',\n '\\\\bone time (?:code|password|passcode|pin)\\\\b',\n '\\\\b(?:verification|security|authentication|authenticator|auth|2fa|mfa|sms) code\\\\b',\n '\\\\b(?:api|access|secret|private) key\\\\b',\n].join('|');\n\nconst SECRET_LABEL_PATTERN = new RegExp(SECRET_LABEL_PATTERN_SOURCE, 'i');\n\n/** `text` as lower-case words: camelCase, runs of capitals and punctuation split. */\nexport function normalizedFieldText(text: string): string {\n return text\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/[^A-Za-z0-9]+/g, ' ')\n .toLowerCase();\n}\n\n/** Whether a field's label, name, id or placeholder says it holds a secret. */\nexport function isSecretFieldLabel(text: string): boolean {\n return SECRET_LABEL_PATTERN.test(normalizedFieldText(text));\n}\n\nfunction labelText(element: Element): string {\n // Read structurally, never with `instanceof`: an element of another\n // document (a popout, an iframe) is not an instance of this one's classes.\n const own = (element as { labels?: ArrayLike<Element> | null }).labels;\n const labels = own ? Array.from(own, (node) => node.textContent ?? '').join(' ') : '';\n return [\n element.getAttribute('aria-label'),\n element.getAttribute('placeholder'),\n element.getAttribute('name'),\n element.id,\n labels,\n ].join(' ');\n}\n\n/**\n * Whether a form control holds a secret: a password input, an `autocomplete`\n * that names a password, a one-time code or a card number or code, a field\n * the page masks, or one whose label, name, id or placeholder says so (which\n * keeps a revealed password hidden, since a reveal toggle keeps the label).\n */\nexport function isSecretField(element: Element): boolean {\n if (\n element.tagName.toUpperCase() === 'INPUT' &&\n (element.getAttribute('type') ?? '').toLowerCase() === 'password'\n ) {\n return true;\n }\n const hint = (element.getAttribute('autocomplete') ?? '').toLowerCase().split(/\\s+/);\n if (hint.some((token) => SECRET_AUTOCOMPLETE_TOKENS.includes(token))) return true;\n const view = element.ownerDocument.defaultView;\n const masking = view?.getComputedStyle(element).getPropertyValue('-webkit-text-security');\n if (masking && masking !== 'none') return true;\n return isSecretFieldLabel(labelText(element));\n}\n"],"names":[],"mappings":"AAUO,MAAM,6BAAgD;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,MAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAEV,MAAM,uBAAuB,IAAI,OAAO,6BAA6B,GAAG;AAGjE,SAAS,oBAAoB,MAAsB;AACxD,SAAO,KACJ,QAAQ,mBAAmB,OAAO,EAClC,QAAQ,yBAAyB,OAAO,EACxC,QAAQ,kBAAkB,GAAG,EAC7B,YAAA;AACL;AAGO,SAAS,mBAAmB,MAAuB;AACxD,SAAO,qBAAqB,KAAK,oBAAoB,IAAI,CAAC;AAC5D;AAEA,SAAS,UAAU,SAA0B;AAG3C,QAAM,MAAO,QAAmD;AAChE,QAAM,SAAS,MAAM,MAAM,KAAK,KAAK,CAAC,SAAS,KAAK,eAAe,EAAE,EAAE,KAAK,GAAG,IAAI;AACnF,SAAO;AAAA,IACL,QAAQ,aAAa,YAAY;AAAA,IACjC,QAAQ,aAAa,aAAa;AAAA,IAClC,QAAQ,aAAa,MAAM;AAAA,IAC3B,QAAQ;AAAA,IACR;AAAA,EAAA,EACA,KAAK,GAAG;AACZ;AAQO,SAAS,cAAc,SAA2B;AACvD,MACE,QAAQ,QAAQ,YAAA,MAAkB,YACjC,QAAQ,aAAa,MAAM,KAAK,IAAI,YAAA,MAAkB,YACvD;AACA,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,QAAQ,aAAa,cAAc,KAAK,IAAI,YAAA,EAAc,MAAM,KAAK;AACnF,MAAI,KAAK,KAAK,CAAC,UAAU,2BAA2B,SAAS,KAAK,CAAC,EAAG,QAAO;AAC7E,QAAM,OAAO,QAAQ,cAAc;AACnC,QAAM,UAAU,6BAAM,iBAAiB,SAAS,iBAAiB;AACjE,MAAI,WAAW,YAAY,OAAQ,QAAO;AAC1C,SAAO,mBAAmB,UAAU,OAAO,CAAC;AAC9C;"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const pace = require("./pace.cjs");
|
|
4
|
+
const screen = require("./screen.cjs");
|
|
5
|
+
const EMPTY = { mark: null, cursor: null, tour: null, question: null };
|
|
6
|
+
function tourStep({
|
|
7
|
+
target,
|
|
8
|
+
text,
|
|
9
|
+
seconds
|
|
10
|
+
}) {
|
|
11
|
+
const trimmed = text.trim();
|
|
12
|
+
return { target: target.trim(), text: trimmed, durationMs: pace.stepDurationMs({ text: trimmed, seconds }) };
|
|
13
|
+
}
|
|
14
|
+
function createAgentUiStage({
|
|
15
|
+
pace: pace$1,
|
|
16
|
+
findStepElementNow,
|
|
17
|
+
findStepElement,
|
|
18
|
+
reveal
|
|
19
|
+
}) {
|
|
20
|
+
let state = EMPTY;
|
|
21
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
22
|
+
let markTimer;
|
|
23
|
+
let fadeTimer;
|
|
24
|
+
let tourTimer;
|
|
25
|
+
let tourSearch = null;
|
|
26
|
+
let resolveQuestion = null;
|
|
27
|
+
const set = (next) => {
|
|
28
|
+
state = { ...state, ...next };
|
|
29
|
+
for (const listener of Array.from(listeners)) listener();
|
|
30
|
+
};
|
|
31
|
+
const stopMarkTimers = () => {
|
|
32
|
+
if (markTimer !== void 0) clearTimeout(markTimer);
|
|
33
|
+
if (fadeTimer !== void 0) clearTimeout(fadeTimer);
|
|
34
|
+
markTimer = void 0;
|
|
35
|
+
fadeTimer = void 0;
|
|
36
|
+
};
|
|
37
|
+
const clearMark = () => {
|
|
38
|
+
stopMarkTimers();
|
|
39
|
+
const mark = state.mark;
|
|
40
|
+
if (!mark) return;
|
|
41
|
+
const fadeMs = pace$1().fadeMs;
|
|
42
|
+
if (fadeMs <= 0) {
|
|
43
|
+
set({ mark: null });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
set({ mark: { ...mark, leaving: true } });
|
|
47
|
+
fadeTimer = setTimeout(() => {
|
|
48
|
+
var _a;
|
|
49
|
+
fadeTimer = void 0;
|
|
50
|
+
if ((_a = state.mark) == null ? void 0 : _a.leaving) set({ mark: null });
|
|
51
|
+
}, fadeMs);
|
|
52
|
+
};
|
|
53
|
+
const stopTourTimers = () => {
|
|
54
|
+
if (tourTimer !== void 0) clearTimeout(tourTimer);
|
|
55
|
+
tourTimer = void 0;
|
|
56
|
+
tourSearch == null ? void 0 : tourSearch.abort();
|
|
57
|
+
tourSearch = null;
|
|
58
|
+
};
|
|
59
|
+
const endTour = () => {
|
|
60
|
+
stopTourTimers();
|
|
61
|
+
if (state.tour) set({ tour: null });
|
|
62
|
+
};
|
|
63
|
+
const scheduleAdvance = () => {
|
|
64
|
+
if (tourTimer !== void 0) clearTimeout(tourTimer);
|
|
65
|
+
tourTimer = void 0;
|
|
66
|
+
const tour = state.tour;
|
|
67
|
+
if (!(tour == null ? void 0 : tour.playing) || tour.searching) return;
|
|
68
|
+
const step = tour.steps[tour.index];
|
|
69
|
+
if (!step) return;
|
|
70
|
+
tourTimer = setTimeout(() => {
|
|
71
|
+
tourTimer = void 0;
|
|
72
|
+
const current = state.tour;
|
|
73
|
+
if (!(current == null ? void 0 : current.playing)) return;
|
|
74
|
+
if (current.index >= current.steps.length - 1) endTour();
|
|
75
|
+
else void showStep(current.index + 1);
|
|
76
|
+
}, step.durationMs);
|
|
77
|
+
};
|
|
78
|
+
const showStep = async (index) => {
|
|
79
|
+
const tour = state.tour;
|
|
80
|
+
const step = tour == null ? void 0 : tour.steps[index];
|
|
81
|
+
if (!tour || !step) return;
|
|
82
|
+
stopTourTimers();
|
|
83
|
+
const present = findStepElementNow(step.target);
|
|
84
|
+
if (present && screen.isInViewport(present)) {
|
|
85
|
+
set({ tour: { ...tour, index, element: present, searching: false } });
|
|
86
|
+
scheduleAdvance();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const search = new AbortController();
|
|
90
|
+
tourSearch = search;
|
|
91
|
+
set({ tour: { ...tour, index, element: present, searching: true } });
|
|
92
|
+
let element = present;
|
|
93
|
+
try {
|
|
94
|
+
element ?? (element = await findStepElement({ target: step.target, signal: search.signal }));
|
|
95
|
+
if (element && !search.signal.aborted && !screen.isInViewport(element)) {
|
|
96
|
+
await reveal({ element, signal: search.signal });
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
element = null;
|
|
100
|
+
}
|
|
101
|
+
if (search.signal.aborted || tourSearch !== search) return;
|
|
102
|
+
tourSearch = null;
|
|
103
|
+
const current = state.tour;
|
|
104
|
+
if (!current) return;
|
|
105
|
+
set({ tour: { ...current, index, element, searching: false } });
|
|
106
|
+
scheduleAdvance();
|
|
107
|
+
};
|
|
108
|
+
const answer = (allow) => {
|
|
109
|
+
const resolve = resolveQuestion;
|
|
110
|
+
resolveQuestion = null;
|
|
111
|
+
if (state.question) set({ question: null });
|
|
112
|
+
resolve == null ? void 0 : resolve(allow);
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
getState: () => state,
|
|
116
|
+
subscribe(listener) {
|
|
117
|
+
listeners.add(listener);
|
|
118
|
+
return () => {
|
|
119
|
+
listeners.delete(listener);
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
mark({ element, tone, caption = null, spotlight = false, durationMs }) {
|
|
123
|
+
stopMarkTimers();
|
|
124
|
+
set({ mark: { element, tone, caption, spotlight, leaving: false } });
|
|
125
|
+
if (durationMs !== void 0) {
|
|
126
|
+
markTimer = setTimeout(() => {
|
|
127
|
+
markTimer = void 0;
|
|
128
|
+
clearMark();
|
|
129
|
+
}, durationMs);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
clearMark,
|
|
133
|
+
async cursorTo({ element, signal }) {
|
|
134
|
+
var _a;
|
|
135
|
+
set({ cursor: { element, presses: ((_a = state.cursor) == null ? void 0 : _a.presses) ?? 0 } });
|
|
136
|
+
await pace.pause(pace$1().cursorGlideMs, signal);
|
|
137
|
+
},
|
|
138
|
+
press() {
|
|
139
|
+
if (state.cursor) set({ cursor: { ...state.cursor, presses: state.cursor.presses + 1 } });
|
|
140
|
+
},
|
|
141
|
+
hideCursor() {
|
|
142
|
+
if (state.cursor) set({ cursor: null });
|
|
143
|
+
},
|
|
144
|
+
ask(question) {
|
|
145
|
+
answer(false);
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
resolveQuestion = resolve;
|
|
148
|
+
set({ question });
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
answer,
|
|
152
|
+
startTour({ title, steps }) {
|
|
153
|
+
stopMarkTimers();
|
|
154
|
+
stopTourTimers();
|
|
155
|
+
state = {
|
|
156
|
+
...state,
|
|
157
|
+
mark: null,
|
|
158
|
+
cursor: null,
|
|
159
|
+
tour: { title, steps, index: 0, playing: true, element: null, searching: true }
|
|
160
|
+
};
|
|
161
|
+
void showStep(0);
|
|
162
|
+
},
|
|
163
|
+
tourNext() {
|
|
164
|
+
const tour = state.tour;
|
|
165
|
+
if (!tour) return;
|
|
166
|
+
if (tour.index >= tour.steps.length - 1) endTour();
|
|
167
|
+
else void showStep(tour.index + 1);
|
|
168
|
+
},
|
|
169
|
+
tourBack() {
|
|
170
|
+
const tour = state.tour;
|
|
171
|
+
if (tour && tour.index > 0) void showStep(tour.index - 1);
|
|
172
|
+
},
|
|
173
|
+
tourTogglePlaying() {
|
|
174
|
+
const tour = state.tour;
|
|
175
|
+
if (!tour) return;
|
|
176
|
+
set({ tour: { ...tour, playing: !tour.playing } });
|
|
177
|
+
if (tour.playing) {
|
|
178
|
+
if (tourTimer !== void 0) clearTimeout(tourTimer);
|
|
179
|
+
tourTimer = void 0;
|
|
180
|
+
} else {
|
|
181
|
+
scheduleAdvance();
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
endTour,
|
|
185
|
+
clear() {
|
|
186
|
+
stopMarkTimers();
|
|
187
|
+
stopTourTimers();
|
|
188
|
+
answer(false);
|
|
189
|
+
state = EMPTY;
|
|
190
|
+
for (const listener of Array.from(listeners)) listener();
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
exports.createAgentUiStage = createAgentUiStage;
|
|
195
|
+
exports.tourStep = tourStep;
|
|
196
|
+
//# sourceMappingURL=stage.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage.cjs","sources":["../../src/agent-ui/stage.ts"],"sourcesContent":["/*\n * The stage: what the overlay is showing right now. A mark around an element\n * with its caption, the agent's cursor, a walkthrough, a question to the\n * person. The tools write it, `AgentUiLayer` draws it, and nothing here\n * touches the page: the stage only holds which element is marked and for how\n * long.\n *\n * Framework-free, so a host without React can draw it too.\n */\n\nimport { type AgentUiPace, pause, stepDurationMs } from './pace';\nimport { isInViewport } from './screen';\n\n/** Why the mark is on an element: the agent is showing it, operating it, or asking about it. */\nexport type AgentUiMarkTone = 'point' | 'act' | 'ask';\n\nexport interface AgentUiMark {\n element: Element;\n tone: AgentUiMarkTone;\n /** Dim the rest of the page. */\n spotlight: boolean;\n /** The words shown beside the element. */\n caption: string | null;\n /** Fading out. */\n leaving: boolean;\n}\n\nexport interface AgentUiCursor {\n /** The element the cursor rests on. */\n element: Element;\n /** Counts presses, so the layer plays one ripple per click. */\n presses: number;\n}\n\nexport interface AgentUiTourStep {\n /** The target the step is about, as the agent named it. */\n target: string;\n text: string;\n /** How long the step stays before the next one plays. */\n durationMs: number;\n}\n\nexport interface AgentUiTour {\n title: string | null;\n steps: readonly AgentUiTourStep[];\n index: number;\n /** Advancing by itself; false while the person paused it. */\n playing: boolean;\n /** The current step's element, or `null` when its target is not on this page. */\n element: Element | null;\n /** The current step is still looking for its target. */\n searching: boolean;\n}\n\nexport interface AgentUiQuestion {\n /** The element the question is about, if it is on screen. */\n element: Element | null;\n title: string;\n body: string | null;\n allowLabel: string;\n declineLabel: string;\n}\n\nexport interface AgentUiStageState {\n mark: AgentUiMark | null;\n cursor: AgentUiCursor | null;\n tour: AgentUiTour | null;\n question: AgentUiQuestion | null;\n}\n\nconst EMPTY: AgentUiStageState = { mark: null, cursor: null, tour: null, question: null };\n\nexport interface AgentUiStage {\n getState(): AgentUiStageState;\n subscribe(listener: () => void): () => void;\n\n /** Mark an element. `durationMs` clears it after that long; without one it stays until replaced. */\n mark(args: {\n element: Element;\n tone: AgentUiMarkTone;\n caption?: string | null;\n spotlight?: boolean;\n durationMs?: number;\n }): void;\n /** Fade the mark out. */\n clearMark(): void;\n\n /** Move the agent's cursor onto an element; resolves when it has arrived. */\n cursorTo(args: { element: Element; signal?: AbortSignal }): Promise<void>;\n /** Show a click where the cursor is. */\n press(): void;\n hideCursor(): void;\n\n /** Ask the person; resolves `true` on allow. A new question declines the one before it. */\n ask(question: AgentUiQuestion): Promise<boolean>;\n answer(allow: boolean): void;\n\n /** Play a walkthrough; replaces whatever the stage showed. */\n startTour(args: { title: string | null; steps: readonly AgentUiTourStep[] }): void;\n tourNext(): void;\n tourBack(): void;\n tourTogglePlaying(): void;\n endTour(): void;\n\n /** Take everything off the page at once. */\n clear(): void;\n}\n\nexport interface CreateAgentUiStageOptions {\n /** The pace in force now (read per use, so a host can change speed live). */\n pace: () => AgentUiPace;\n /** A step's element if it is on the page right now. */\n findStepElementNow: (target: string) => Element | null;\n /** Find a step's element, waiting for a page that is still loading; `null` when it is not there. */\n findStepElement: (args: { target: string; signal: AbortSignal }) => Promise<Element | null>;\n /** Bring an element into view. */\n reveal: (args: { element: Element; signal?: AbortSignal }) => Promise<void>;\n}\n\n/** Normalise a walkthrough's steps: its text trimmed, its duration set. */\nexport function tourStep({\n target,\n text,\n seconds,\n}: {\n target: string;\n text: string;\n seconds?: number;\n}): AgentUiTourStep {\n const trimmed = text.trim();\n return { target: target.trim(), text: trimmed, durationMs: stepDurationMs({ text: trimmed, seconds }) };\n}\n\nexport function createAgentUiStage({\n pace,\n findStepElementNow,\n findStepElement,\n reveal,\n}: CreateAgentUiStageOptions): AgentUiStage {\n let state: AgentUiStageState = EMPTY;\n const listeners = new Set<() => void>();\n let markTimer: ReturnType<typeof setTimeout> | undefined;\n let fadeTimer: ReturnType<typeof setTimeout> | undefined;\n let tourTimer: ReturnType<typeof setTimeout> | undefined;\n let tourSearch: AbortController | null = null;\n let resolveQuestion: ((allow: boolean) => void) | null = null;\n\n const set = (next: Partial<AgentUiStageState>) => {\n state = { ...state, ...next };\n for (const listener of Array.from(listeners)) listener();\n };\n\n const stopMarkTimers = () => {\n if (markTimer !== undefined) clearTimeout(markTimer);\n if (fadeTimer !== undefined) clearTimeout(fadeTimer);\n markTimer = undefined;\n fadeTimer = undefined;\n };\n\n const clearMark = () => {\n stopMarkTimers();\n const mark = state.mark;\n if (!mark) return;\n const fadeMs = pace().fadeMs;\n if (fadeMs <= 0) {\n set({ mark: null });\n return;\n }\n set({ mark: { ...mark, leaving: true } });\n fadeTimer = setTimeout(() => {\n fadeTimer = undefined;\n if (state.mark?.leaving) set({ mark: null });\n }, fadeMs);\n };\n\n const stopTourTimers = () => {\n if (tourTimer !== undefined) clearTimeout(tourTimer);\n tourTimer = undefined;\n tourSearch?.abort();\n tourSearch = null;\n };\n\n const endTour = () => {\n stopTourTimers();\n if (state.tour) set({ tour: null });\n };\n\n const scheduleAdvance = () => {\n if (tourTimer !== undefined) clearTimeout(tourTimer);\n tourTimer = undefined;\n const tour = state.tour;\n if (!tour?.playing || tour.searching) return;\n const step = tour.steps[tour.index];\n if (!step) return;\n tourTimer = setTimeout(() => {\n tourTimer = undefined;\n const current = state.tour;\n if (!current?.playing) return;\n if (current.index >= current.steps.length - 1) endTour();\n else void showStep(current.index + 1);\n }, step.durationMs);\n };\n\n const showStep = async (index: number): Promise<void> => {\n const tour = state.tour;\n const step = tour?.steps[index];\n if (!tour || !step) return;\n stopTourTimers();\n // The usual step is on the page and in view: move to it in one update, so\n // the card goes straight from one element to the next.\n const present = findStepElementNow(step.target);\n if (present && isInViewport(present)) {\n set({ tour: { ...tour, index, element: present, searching: false } });\n scheduleAdvance();\n return;\n }\n const search = new AbortController();\n tourSearch = search;\n set({ tour: { ...tour, index, element: present, searching: true } });\n let element: Element | null = present;\n try {\n element ??= await findStepElement({ target: step.target, signal: search.signal });\n if (element && !search.signal.aborted && !isInViewport(element)) {\n await reveal({ element, signal: search.signal });\n }\n } catch {\n element = null;\n }\n // Another step, or the end of the walkthrough, took over while this one looked.\n if (search.signal.aborted || tourSearch !== search) return;\n tourSearch = null;\n const current = state.tour;\n if (!current) return;\n set({ tour: { ...current, index, element, searching: false } });\n scheduleAdvance();\n };\n\n const answer = (allow: boolean) => {\n const resolve = resolveQuestion;\n resolveQuestion = null;\n if (state.question) set({ question: null });\n resolve?.(allow);\n };\n\n return {\n getState: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n\n mark({ element, tone, caption = null, spotlight = false, durationMs }) {\n stopMarkTimers();\n set({ mark: { element, tone, caption, spotlight, leaving: false } });\n if (durationMs !== undefined) {\n markTimer = setTimeout(() => {\n markTimer = undefined;\n clearMark();\n }, durationMs);\n }\n },\n clearMark,\n\n async cursorTo({ element, signal }) {\n set({ cursor: { element, presses: state.cursor?.presses ?? 0 } });\n await pause(pace().cursorGlideMs, signal);\n },\n press() {\n if (state.cursor) set({ cursor: { ...state.cursor, presses: state.cursor.presses + 1 } });\n },\n hideCursor() {\n if (state.cursor) set({ cursor: null });\n },\n\n ask(question) {\n answer(false);\n return new Promise<boolean>((resolve) => {\n resolveQuestion = resolve;\n set({ question });\n });\n },\n answer,\n\n startTour({ title, steps }) {\n stopMarkTimers();\n stopTourTimers();\n // Published by `showStep` with the first step's element, in one update.\n state = {\n ...state,\n mark: null,\n cursor: null,\n tour: { title, steps, index: 0, playing: true, element: null, searching: true },\n };\n void showStep(0);\n },\n tourNext() {\n const tour = state.tour;\n if (!tour) return;\n if (tour.index >= tour.steps.length - 1) endTour();\n else void showStep(tour.index + 1);\n },\n tourBack() {\n const tour = state.tour;\n if (tour && tour.index > 0) void showStep(tour.index - 1);\n },\n tourTogglePlaying() {\n const tour = state.tour;\n if (!tour) return;\n set({ tour: { ...tour, playing: !tour.playing } });\n if (tour.playing) {\n if (tourTimer !== undefined) clearTimeout(tourTimer);\n tourTimer = undefined;\n } else {\n scheduleAdvance();\n }\n },\n endTour,\n\n clear() {\n stopMarkTimers();\n stopTourTimers();\n answer(false);\n state = EMPTY;\n for (const listener of Array.from(listeners)) listener();\n },\n };\n}\n"],"names":["stepDurationMs","pace","isInViewport","pause"],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type AgentUiPace } from './pace';
|
|
2
|
+
/** Why the mark is on an element: the agent is showing it, operating it, or asking about it. */
|
|
3
|
+
export type AgentUiMarkTone = 'point' | 'act' | 'ask';
|
|
4
|
+
export interface AgentUiMark {
|
|
5
|
+
element: Element;
|
|
6
|
+
tone: AgentUiMarkTone;
|
|
7
|
+
/** Dim the rest of the page. */
|
|
8
|
+
spotlight: boolean;
|
|
9
|
+
/** The words shown beside the element. */
|
|
10
|
+
caption: string | null;
|
|
11
|
+
/** Fading out. */
|
|
12
|
+
leaving: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentUiCursor {
|
|
15
|
+
/** The element the cursor rests on. */
|
|
16
|
+
element: Element;
|
|
17
|
+
/** Counts presses, so the layer plays one ripple per click. */
|
|
18
|
+
presses: number;
|
|
19
|
+
}
|
|
20
|
+
export interface AgentUiTourStep {
|
|
21
|
+
/** The target the step is about, as the agent named it. */
|
|
22
|
+
target: string;
|
|
23
|
+
text: string;
|
|
24
|
+
/** How long the step stays before the next one plays. */
|
|
25
|
+
durationMs: number;
|
|
26
|
+
}
|
|
27
|
+
export interface AgentUiTour {
|
|
28
|
+
title: string | null;
|
|
29
|
+
steps: readonly AgentUiTourStep[];
|
|
30
|
+
index: number;
|
|
31
|
+
/** Advancing by itself; false while the person paused it. */
|
|
32
|
+
playing: boolean;
|
|
33
|
+
/** The current step's element, or `null` when its target is not on this page. */
|
|
34
|
+
element: Element | null;
|
|
35
|
+
/** The current step is still looking for its target. */
|
|
36
|
+
searching: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface AgentUiQuestion {
|
|
39
|
+
/** The element the question is about, if it is on screen. */
|
|
40
|
+
element: Element | null;
|
|
41
|
+
title: string;
|
|
42
|
+
body: string | null;
|
|
43
|
+
allowLabel: string;
|
|
44
|
+
declineLabel: string;
|
|
45
|
+
}
|
|
46
|
+
export interface AgentUiStageState {
|
|
47
|
+
mark: AgentUiMark | null;
|
|
48
|
+
cursor: AgentUiCursor | null;
|
|
49
|
+
tour: AgentUiTour | null;
|
|
50
|
+
question: AgentUiQuestion | null;
|
|
51
|
+
}
|
|
52
|
+
export interface AgentUiStage {
|
|
53
|
+
getState(): AgentUiStageState;
|
|
54
|
+
subscribe(listener: () => void): () => void;
|
|
55
|
+
/** Mark an element. `durationMs` clears it after that long; without one it stays until replaced. */
|
|
56
|
+
mark(args: {
|
|
57
|
+
element: Element;
|
|
58
|
+
tone: AgentUiMarkTone;
|
|
59
|
+
caption?: string | null;
|
|
60
|
+
spotlight?: boolean;
|
|
61
|
+
durationMs?: number;
|
|
62
|
+
}): void;
|
|
63
|
+
/** Fade the mark out. */
|
|
64
|
+
clearMark(): void;
|
|
65
|
+
/** Move the agent's cursor onto an element; resolves when it has arrived. */
|
|
66
|
+
cursorTo(args: {
|
|
67
|
+
element: Element;
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}): Promise<void>;
|
|
70
|
+
/** Show a click where the cursor is. */
|
|
71
|
+
press(): void;
|
|
72
|
+
hideCursor(): void;
|
|
73
|
+
/** Ask the person; resolves `true` on allow. A new question declines the one before it. */
|
|
74
|
+
ask(question: AgentUiQuestion): Promise<boolean>;
|
|
75
|
+
answer(allow: boolean): void;
|
|
76
|
+
/** Play a walkthrough; replaces whatever the stage showed. */
|
|
77
|
+
startTour(args: {
|
|
78
|
+
title: string | null;
|
|
79
|
+
steps: readonly AgentUiTourStep[];
|
|
80
|
+
}): void;
|
|
81
|
+
tourNext(): void;
|
|
82
|
+
tourBack(): void;
|
|
83
|
+
tourTogglePlaying(): void;
|
|
84
|
+
endTour(): void;
|
|
85
|
+
/** Take everything off the page at once. */
|
|
86
|
+
clear(): void;
|
|
87
|
+
}
|
|
88
|
+
export interface CreateAgentUiStageOptions {
|
|
89
|
+
/** The pace in force now (read per use, so a host can change speed live). */
|
|
90
|
+
pace: () => AgentUiPace;
|
|
91
|
+
/** A step's element if it is on the page right now. */
|
|
92
|
+
findStepElementNow: (target: string) => Element | null;
|
|
93
|
+
/** Find a step's element, waiting for a page that is still loading; `null` when it is not there. */
|
|
94
|
+
findStepElement: (args: {
|
|
95
|
+
target: string;
|
|
96
|
+
signal: AbortSignal;
|
|
97
|
+
}) => Promise<Element | null>;
|
|
98
|
+
/** Bring an element into view. */
|
|
99
|
+
reveal: (args: {
|
|
100
|
+
element: Element;
|
|
101
|
+
signal?: AbortSignal;
|
|
102
|
+
}) => Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
/** Normalise a walkthrough's steps: its text trimmed, its duration set. */
|
|
105
|
+
export declare function tourStep({ target, text, seconds, }: {
|
|
106
|
+
target: string;
|
|
107
|
+
text: string;
|
|
108
|
+
seconds?: number;
|
|
109
|
+
}): AgentUiTourStep;
|
|
110
|
+
export declare function createAgentUiStage({ pace, findStepElementNow, findStepElement, reveal, }: CreateAgentUiStageOptions): AgentUiStage;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { pause, stepDurationMs } from "./pace.js";
|
|
2
|
+
import { isInViewport } from "./screen.js";
|
|
3
|
+
const EMPTY = { mark: null, cursor: null, tour: null, question: null };
|
|
4
|
+
function tourStep({
|
|
5
|
+
target,
|
|
6
|
+
text,
|
|
7
|
+
seconds
|
|
8
|
+
}) {
|
|
9
|
+
const trimmed = text.trim();
|
|
10
|
+
return { target: target.trim(), text: trimmed, durationMs: stepDurationMs({ text: trimmed, seconds }) };
|
|
11
|
+
}
|
|
12
|
+
function createAgentUiStage({
|
|
13
|
+
pace,
|
|
14
|
+
findStepElementNow,
|
|
15
|
+
findStepElement,
|
|
16
|
+
reveal
|
|
17
|
+
}) {
|
|
18
|
+
let state = EMPTY;
|
|
19
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
20
|
+
let markTimer;
|
|
21
|
+
let fadeTimer;
|
|
22
|
+
let tourTimer;
|
|
23
|
+
let tourSearch = null;
|
|
24
|
+
let resolveQuestion = null;
|
|
25
|
+
const set = (next) => {
|
|
26
|
+
state = { ...state, ...next };
|
|
27
|
+
for (const listener of Array.from(listeners)) listener();
|
|
28
|
+
};
|
|
29
|
+
const stopMarkTimers = () => {
|
|
30
|
+
if (markTimer !== void 0) clearTimeout(markTimer);
|
|
31
|
+
if (fadeTimer !== void 0) clearTimeout(fadeTimer);
|
|
32
|
+
markTimer = void 0;
|
|
33
|
+
fadeTimer = void 0;
|
|
34
|
+
};
|
|
35
|
+
const clearMark = () => {
|
|
36
|
+
stopMarkTimers();
|
|
37
|
+
const mark = state.mark;
|
|
38
|
+
if (!mark) return;
|
|
39
|
+
const fadeMs = pace().fadeMs;
|
|
40
|
+
if (fadeMs <= 0) {
|
|
41
|
+
set({ mark: null });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
set({ mark: { ...mark, leaving: true } });
|
|
45
|
+
fadeTimer = setTimeout(() => {
|
|
46
|
+
var _a;
|
|
47
|
+
fadeTimer = void 0;
|
|
48
|
+
if ((_a = state.mark) == null ? void 0 : _a.leaving) set({ mark: null });
|
|
49
|
+
}, fadeMs);
|
|
50
|
+
};
|
|
51
|
+
const stopTourTimers = () => {
|
|
52
|
+
if (tourTimer !== void 0) clearTimeout(tourTimer);
|
|
53
|
+
tourTimer = void 0;
|
|
54
|
+
tourSearch == null ? void 0 : tourSearch.abort();
|
|
55
|
+
tourSearch = null;
|
|
56
|
+
};
|
|
57
|
+
const endTour = () => {
|
|
58
|
+
stopTourTimers();
|
|
59
|
+
if (state.tour) set({ tour: null });
|
|
60
|
+
};
|
|
61
|
+
const scheduleAdvance = () => {
|
|
62
|
+
if (tourTimer !== void 0) clearTimeout(tourTimer);
|
|
63
|
+
tourTimer = void 0;
|
|
64
|
+
const tour = state.tour;
|
|
65
|
+
if (!(tour == null ? void 0 : tour.playing) || tour.searching) return;
|
|
66
|
+
const step = tour.steps[tour.index];
|
|
67
|
+
if (!step) return;
|
|
68
|
+
tourTimer = setTimeout(() => {
|
|
69
|
+
tourTimer = void 0;
|
|
70
|
+
const current = state.tour;
|
|
71
|
+
if (!(current == null ? void 0 : current.playing)) return;
|
|
72
|
+
if (current.index >= current.steps.length - 1) endTour();
|
|
73
|
+
else void showStep(current.index + 1);
|
|
74
|
+
}, step.durationMs);
|
|
75
|
+
};
|
|
76
|
+
const showStep = async (index) => {
|
|
77
|
+
const tour = state.tour;
|
|
78
|
+
const step = tour == null ? void 0 : tour.steps[index];
|
|
79
|
+
if (!tour || !step) return;
|
|
80
|
+
stopTourTimers();
|
|
81
|
+
const present = findStepElementNow(step.target);
|
|
82
|
+
if (present && isInViewport(present)) {
|
|
83
|
+
set({ tour: { ...tour, index, element: present, searching: false } });
|
|
84
|
+
scheduleAdvance();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const search = new AbortController();
|
|
88
|
+
tourSearch = search;
|
|
89
|
+
set({ tour: { ...tour, index, element: present, searching: true } });
|
|
90
|
+
let element = present;
|
|
91
|
+
try {
|
|
92
|
+
element ?? (element = await findStepElement({ target: step.target, signal: search.signal }));
|
|
93
|
+
if (element && !search.signal.aborted && !isInViewport(element)) {
|
|
94
|
+
await reveal({ element, signal: search.signal });
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
element = null;
|
|
98
|
+
}
|
|
99
|
+
if (search.signal.aborted || tourSearch !== search) return;
|
|
100
|
+
tourSearch = null;
|
|
101
|
+
const current = state.tour;
|
|
102
|
+
if (!current) return;
|
|
103
|
+
set({ tour: { ...current, index, element, searching: false } });
|
|
104
|
+
scheduleAdvance();
|
|
105
|
+
};
|
|
106
|
+
const answer = (allow) => {
|
|
107
|
+
const resolve = resolveQuestion;
|
|
108
|
+
resolveQuestion = null;
|
|
109
|
+
if (state.question) set({ question: null });
|
|
110
|
+
resolve == null ? void 0 : resolve(allow);
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
getState: () => state,
|
|
114
|
+
subscribe(listener) {
|
|
115
|
+
listeners.add(listener);
|
|
116
|
+
return () => {
|
|
117
|
+
listeners.delete(listener);
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
mark({ element, tone, caption = null, spotlight = false, durationMs }) {
|
|
121
|
+
stopMarkTimers();
|
|
122
|
+
set({ mark: { element, tone, caption, spotlight, leaving: false } });
|
|
123
|
+
if (durationMs !== void 0) {
|
|
124
|
+
markTimer = setTimeout(() => {
|
|
125
|
+
markTimer = void 0;
|
|
126
|
+
clearMark();
|
|
127
|
+
}, durationMs);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
clearMark,
|
|
131
|
+
async cursorTo({ element, signal }) {
|
|
132
|
+
var _a;
|
|
133
|
+
set({ cursor: { element, presses: ((_a = state.cursor) == null ? void 0 : _a.presses) ?? 0 } });
|
|
134
|
+
await pause(pace().cursorGlideMs, signal);
|
|
135
|
+
},
|
|
136
|
+
press() {
|
|
137
|
+
if (state.cursor) set({ cursor: { ...state.cursor, presses: state.cursor.presses + 1 } });
|
|
138
|
+
},
|
|
139
|
+
hideCursor() {
|
|
140
|
+
if (state.cursor) set({ cursor: null });
|
|
141
|
+
},
|
|
142
|
+
ask(question) {
|
|
143
|
+
answer(false);
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
resolveQuestion = resolve;
|
|
146
|
+
set({ question });
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
answer,
|
|
150
|
+
startTour({ title, steps }) {
|
|
151
|
+
stopMarkTimers();
|
|
152
|
+
stopTourTimers();
|
|
153
|
+
state = {
|
|
154
|
+
...state,
|
|
155
|
+
mark: null,
|
|
156
|
+
cursor: null,
|
|
157
|
+
tour: { title, steps, index: 0, playing: true, element: null, searching: true }
|
|
158
|
+
};
|
|
159
|
+
void showStep(0);
|
|
160
|
+
},
|
|
161
|
+
tourNext() {
|
|
162
|
+
const tour = state.tour;
|
|
163
|
+
if (!tour) return;
|
|
164
|
+
if (tour.index >= tour.steps.length - 1) endTour();
|
|
165
|
+
else void showStep(tour.index + 1);
|
|
166
|
+
},
|
|
167
|
+
tourBack() {
|
|
168
|
+
const tour = state.tour;
|
|
169
|
+
if (tour && tour.index > 0) void showStep(tour.index - 1);
|
|
170
|
+
},
|
|
171
|
+
tourTogglePlaying() {
|
|
172
|
+
const tour = state.tour;
|
|
173
|
+
if (!tour) return;
|
|
174
|
+
set({ tour: { ...tour, playing: !tour.playing } });
|
|
175
|
+
if (tour.playing) {
|
|
176
|
+
if (tourTimer !== void 0) clearTimeout(tourTimer);
|
|
177
|
+
tourTimer = void 0;
|
|
178
|
+
} else {
|
|
179
|
+
scheduleAdvance();
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
endTour,
|
|
183
|
+
clear() {
|
|
184
|
+
stopMarkTimers();
|
|
185
|
+
stopTourTimers();
|
|
186
|
+
answer(false);
|
|
187
|
+
state = EMPTY;
|
|
188
|
+
for (const listener of Array.from(listeners)) listener();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
export {
|
|
193
|
+
createAgentUiStage,
|
|
194
|
+
tourStep
|
|
195
|
+
};
|
|
196
|
+
//# sourceMappingURL=stage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage.js","sources":["../../src/agent-ui/stage.ts"],"sourcesContent":["/*\n * The stage: what the overlay is showing right now. A mark around an element\n * with its caption, the agent's cursor, a walkthrough, a question to the\n * person. The tools write it, `AgentUiLayer` draws it, and nothing here\n * touches the page: the stage only holds which element is marked and for how\n * long.\n *\n * Framework-free, so a host without React can draw it too.\n */\n\nimport { type AgentUiPace, pause, stepDurationMs } from './pace';\nimport { isInViewport } from './screen';\n\n/** Why the mark is on an element: the agent is showing it, operating it, or asking about it. */\nexport type AgentUiMarkTone = 'point' | 'act' | 'ask';\n\nexport interface AgentUiMark {\n element: Element;\n tone: AgentUiMarkTone;\n /** Dim the rest of the page. */\n spotlight: boolean;\n /** The words shown beside the element. */\n caption: string | null;\n /** Fading out. */\n leaving: boolean;\n}\n\nexport interface AgentUiCursor {\n /** The element the cursor rests on. */\n element: Element;\n /** Counts presses, so the layer plays one ripple per click. */\n presses: number;\n}\n\nexport interface AgentUiTourStep {\n /** The target the step is about, as the agent named it. */\n target: string;\n text: string;\n /** How long the step stays before the next one plays. */\n durationMs: number;\n}\n\nexport interface AgentUiTour {\n title: string | null;\n steps: readonly AgentUiTourStep[];\n index: number;\n /** Advancing by itself; false while the person paused it. */\n playing: boolean;\n /** The current step's element, or `null` when its target is not on this page. */\n element: Element | null;\n /** The current step is still looking for its target. */\n searching: boolean;\n}\n\nexport interface AgentUiQuestion {\n /** The element the question is about, if it is on screen. */\n element: Element | null;\n title: string;\n body: string | null;\n allowLabel: string;\n declineLabel: string;\n}\n\nexport interface AgentUiStageState {\n mark: AgentUiMark | null;\n cursor: AgentUiCursor | null;\n tour: AgentUiTour | null;\n question: AgentUiQuestion | null;\n}\n\nconst EMPTY: AgentUiStageState = { mark: null, cursor: null, tour: null, question: null };\n\nexport interface AgentUiStage {\n getState(): AgentUiStageState;\n subscribe(listener: () => void): () => void;\n\n /** Mark an element. `durationMs` clears it after that long; without one it stays until replaced. */\n mark(args: {\n element: Element;\n tone: AgentUiMarkTone;\n caption?: string | null;\n spotlight?: boolean;\n durationMs?: number;\n }): void;\n /** Fade the mark out. */\n clearMark(): void;\n\n /** Move the agent's cursor onto an element; resolves when it has arrived. */\n cursorTo(args: { element: Element; signal?: AbortSignal }): Promise<void>;\n /** Show a click where the cursor is. */\n press(): void;\n hideCursor(): void;\n\n /** Ask the person; resolves `true` on allow. A new question declines the one before it. */\n ask(question: AgentUiQuestion): Promise<boolean>;\n answer(allow: boolean): void;\n\n /** Play a walkthrough; replaces whatever the stage showed. */\n startTour(args: { title: string | null; steps: readonly AgentUiTourStep[] }): void;\n tourNext(): void;\n tourBack(): void;\n tourTogglePlaying(): void;\n endTour(): void;\n\n /** Take everything off the page at once. */\n clear(): void;\n}\n\nexport interface CreateAgentUiStageOptions {\n /** The pace in force now (read per use, so a host can change speed live). */\n pace: () => AgentUiPace;\n /** A step's element if it is on the page right now. */\n findStepElementNow: (target: string) => Element | null;\n /** Find a step's element, waiting for a page that is still loading; `null` when it is not there. */\n findStepElement: (args: { target: string; signal: AbortSignal }) => Promise<Element | null>;\n /** Bring an element into view. */\n reveal: (args: { element: Element; signal?: AbortSignal }) => Promise<void>;\n}\n\n/** Normalise a walkthrough's steps: its text trimmed, its duration set. */\nexport function tourStep({\n target,\n text,\n seconds,\n}: {\n target: string;\n text: string;\n seconds?: number;\n}): AgentUiTourStep {\n const trimmed = text.trim();\n return { target: target.trim(), text: trimmed, durationMs: stepDurationMs({ text: trimmed, seconds }) };\n}\n\nexport function createAgentUiStage({\n pace,\n findStepElementNow,\n findStepElement,\n reveal,\n}: CreateAgentUiStageOptions): AgentUiStage {\n let state: AgentUiStageState = EMPTY;\n const listeners = new Set<() => void>();\n let markTimer: ReturnType<typeof setTimeout> | undefined;\n let fadeTimer: ReturnType<typeof setTimeout> | undefined;\n let tourTimer: ReturnType<typeof setTimeout> | undefined;\n let tourSearch: AbortController | null = null;\n let resolveQuestion: ((allow: boolean) => void) | null = null;\n\n const set = (next: Partial<AgentUiStageState>) => {\n state = { ...state, ...next };\n for (const listener of Array.from(listeners)) listener();\n };\n\n const stopMarkTimers = () => {\n if (markTimer !== undefined) clearTimeout(markTimer);\n if (fadeTimer !== undefined) clearTimeout(fadeTimer);\n markTimer = undefined;\n fadeTimer = undefined;\n };\n\n const clearMark = () => {\n stopMarkTimers();\n const mark = state.mark;\n if (!mark) return;\n const fadeMs = pace().fadeMs;\n if (fadeMs <= 0) {\n set({ mark: null });\n return;\n }\n set({ mark: { ...mark, leaving: true } });\n fadeTimer = setTimeout(() => {\n fadeTimer = undefined;\n if (state.mark?.leaving) set({ mark: null });\n }, fadeMs);\n };\n\n const stopTourTimers = () => {\n if (tourTimer !== undefined) clearTimeout(tourTimer);\n tourTimer = undefined;\n tourSearch?.abort();\n tourSearch = null;\n };\n\n const endTour = () => {\n stopTourTimers();\n if (state.tour) set({ tour: null });\n };\n\n const scheduleAdvance = () => {\n if (tourTimer !== undefined) clearTimeout(tourTimer);\n tourTimer = undefined;\n const tour = state.tour;\n if (!tour?.playing || tour.searching) return;\n const step = tour.steps[tour.index];\n if (!step) return;\n tourTimer = setTimeout(() => {\n tourTimer = undefined;\n const current = state.tour;\n if (!current?.playing) return;\n if (current.index >= current.steps.length - 1) endTour();\n else void showStep(current.index + 1);\n }, step.durationMs);\n };\n\n const showStep = async (index: number): Promise<void> => {\n const tour = state.tour;\n const step = tour?.steps[index];\n if (!tour || !step) return;\n stopTourTimers();\n // The usual step is on the page and in view: move to it in one update, so\n // the card goes straight from one element to the next.\n const present = findStepElementNow(step.target);\n if (present && isInViewport(present)) {\n set({ tour: { ...tour, index, element: present, searching: false } });\n scheduleAdvance();\n return;\n }\n const search = new AbortController();\n tourSearch = search;\n set({ tour: { ...tour, index, element: present, searching: true } });\n let element: Element | null = present;\n try {\n element ??= await findStepElement({ target: step.target, signal: search.signal });\n if (element && !search.signal.aborted && !isInViewport(element)) {\n await reveal({ element, signal: search.signal });\n }\n } catch {\n element = null;\n }\n // Another step, or the end of the walkthrough, took over while this one looked.\n if (search.signal.aborted || tourSearch !== search) return;\n tourSearch = null;\n const current = state.tour;\n if (!current) return;\n set({ tour: { ...current, index, element, searching: false } });\n scheduleAdvance();\n };\n\n const answer = (allow: boolean) => {\n const resolve = resolveQuestion;\n resolveQuestion = null;\n if (state.question) set({ question: null });\n resolve?.(allow);\n };\n\n return {\n getState: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n\n mark({ element, tone, caption = null, spotlight = false, durationMs }) {\n stopMarkTimers();\n set({ mark: { element, tone, caption, spotlight, leaving: false } });\n if (durationMs !== undefined) {\n markTimer = setTimeout(() => {\n markTimer = undefined;\n clearMark();\n }, durationMs);\n }\n },\n clearMark,\n\n async cursorTo({ element, signal }) {\n set({ cursor: { element, presses: state.cursor?.presses ?? 0 } });\n await pause(pace().cursorGlideMs, signal);\n },\n press() {\n if (state.cursor) set({ cursor: { ...state.cursor, presses: state.cursor.presses + 1 } });\n },\n hideCursor() {\n if (state.cursor) set({ cursor: null });\n },\n\n ask(question) {\n answer(false);\n return new Promise<boolean>((resolve) => {\n resolveQuestion = resolve;\n set({ question });\n });\n },\n answer,\n\n startTour({ title, steps }) {\n stopMarkTimers();\n stopTourTimers();\n // Published by `showStep` with the first step's element, in one update.\n state = {\n ...state,\n mark: null,\n cursor: null,\n tour: { title, steps, index: 0, playing: true, element: null, searching: true },\n };\n void showStep(0);\n },\n tourNext() {\n const tour = state.tour;\n if (!tour) return;\n if (tour.index >= tour.steps.length - 1) endTour();\n else void showStep(tour.index + 1);\n },\n tourBack() {\n const tour = state.tour;\n if (tour && tour.index > 0) void showStep(tour.index - 1);\n },\n tourTogglePlaying() {\n const tour = state.tour;\n if (!tour) return;\n set({ tour: { ...tour, playing: !tour.playing } });\n if (tour.playing) {\n if (tourTimer !== undefined) clearTimeout(tourTimer);\n tourTimer = undefined;\n } else {\n scheduleAdvance();\n }\n },\n endTour,\n\n clear() {\n stopMarkTimers();\n stopTourTimers();\n answer(false);\n state = EMPTY;\n for (const listener of Array.from(listeners)) listener();\n },\n };\n}\n"],"names":[],"mappings":"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"}
|