@fiodos/web-core 0.1.12 → 0.1.14
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/dist/cjs/adapters/webCredentialAutofillAdapter.d.ts +16 -0
- package/dist/cjs/adapters/webCredentialAutofillAdapter.js +47 -0
- package/dist/cjs/api/backendClient.js +14 -0
- package/dist/cjs/config/types.d.ts +15 -2
- package/dist/cjs/controller/AgentController.d.ts +14 -0
- package/dist/cjs/controller/AgentController.js +63 -0
- package/dist/cjs/dropin/createFiodosAgent.d.ts +27 -1
- package/dist/cjs/dropin/createFiodosAgent.js +32 -1
- package/dist/cjs/embed/dynamics.d.ts +61 -0
- package/dist/cjs/embed/dynamics.js +173 -0
- package/dist/cjs/embed/global.d.ts +45 -0
- package/dist/cjs/embed/global.js +48 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/orb/mountOrb.js +111 -18
- package/dist/cjs/orb/orbView.js +40 -7
- package/dist/cjs/orb/publishedConfig.d.ts +4 -2
- package/dist/cjs/orb/publishedConfig.js +56 -7
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/embed/fiodos-embed.js +66 -0
- package/dist/esm/adapters/webCredentialAutofillAdapter.d.ts +16 -0
- package/dist/esm/adapters/webCredentialAutofillAdapter.js +44 -0
- package/dist/esm/api/backendClient.js +14 -0
- package/dist/esm/config/types.d.ts +15 -2
- package/dist/esm/controller/AgentController.d.ts +14 -0
- package/dist/esm/controller/AgentController.js +63 -0
- package/dist/esm/dropin/createFiodosAgent.d.ts +27 -1
- package/dist/esm/dropin/createFiodosAgent.js +32 -1
- package/dist/esm/embed/dynamics.d.ts +61 -0
- package/dist/esm/embed/dynamics.js +168 -0
- package/dist/esm/embed/global.d.ts +45 -0
- package/dist/esm/embed/global.js +46 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/orb/mountOrb.js +112 -19
- package/dist/esm/orb/orbView.js +41 -8
- package/dist/esm/orb/publishedConfig.d.ts +4 -2
- package/dist/esm/orb/publishedConfig.js +56 -7
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Embed entrypoint — single-file IIFE bundle for CLOSED third-party hosts
|
|
5
|
+
* where npm/ESM is not available: Dynamics 365 web resources, WinCC Unified
|
|
6
|
+
* Custom Web Controls, Salesforce LWC iframes, plain <script> tags.
|
|
7
|
+
*
|
|
8
|
+
* Built by `npm run build:embed` into dist/embed/fiodos-embed.js and exposed
|
|
9
|
+
* as the `Fiodos` global:
|
|
10
|
+
*
|
|
11
|
+
* <script src="fiodos-embed.js"></script>
|
|
12
|
+
* <script>
|
|
13
|
+
* const agent = Fiodos.createFiodosAgent({
|
|
14
|
+
* manifest, // e.g. from `fiodos from-odata`
|
|
15
|
+
* registries: Fiodos.buildApiActionRegistries(manifest, {
|
|
16
|
+
* baseUrl: 'https://org.crm.dynamics.com/api/data/v9.2',
|
|
17
|
+
* authProviders: { dataverse: () => ({ Authorization: `Bearer ${token}` }) },
|
|
18
|
+
* }),
|
|
19
|
+
* });
|
|
20
|
+
* </script>
|
|
21
|
+
*
|
|
22
|
+
* The bundle is self-contained (core + web-core inlined) and side-effect-free
|
|
23
|
+
* beyond assigning the global — it never auto-mounts; the host decides when.
|
|
24
|
+
*/
|
|
25
|
+
const createFiodosAgent_1 = require("../dropin/createFiodosAgent");
|
|
26
|
+
const mountOrb_1 = require("../orb/mountOrb");
|
|
27
|
+
const AgentController_1 = require("../controller/AgentController");
|
|
28
|
+
const screenContextStore_1 = require("../context/screenContextStore");
|
|
29
|
+
const dynamics_1 = require("./dynamics");
|
|
30
|
+
const version_1 = require("../version");
|
|
31
|
+
const core_1 = require("@fiodos/core");
|
|
32
|
+
const api = {
|
|
33
|
+
version: version_1.SDK_VERSION,
|
|
34
|
+
createFiodosAgent: createFiodosAgent_1.createFiodosAgent,
|
|
35
|
+
mountOrb: mountOrb_1.mountOrb,
|
|
36
|
+
AgentController: AgentController_1.AgentController,
|
|
37
|
+
createScreenContextStore: screenContextStore_1.createScreenContextStore,
|
|
38
|
+
// Closed-software mode: turn ApiExecutionSpec manifests into live handlers,
|
|
39
|
+
// and validate hand-written / from-odata manifests before mounting.
|
|
40
|
+
buildApiActionRegistries: core_1.buildApiActionRegistries,
|
|
41
|
+
validateManifest: core_1.validateManifest,
|
|
42
|
+
// Context bridges: tell the orb what the host is showing. Dynamics gets a
|
|
43
|
+
// dedicated mapper (formContext); any other host calls agent.setScreenContext.
|
|
44
|
+
connectDynamicsFormContext: dynamics_1.connectDynamicsFormContext,
|
|
45
|
+
dynamicsFormSnapshot: dynamics_1.dynamicsFormSnapshot,
|
|
46
|
+
normalizeDynamicsId: dynamics_1.normalizeDynamicsId,
|
|
47
|
+
};
|
|
48
|
+
globalThis.Fiodos = api;
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export { createWebNavigationAdapter, BACK_ROUTE, } from './adapters/webNavigatio
|
|
|
26
26
|
export type { WebNavigationAdapter, WebNavigationAdapterOptions, } from './adapters/webNavigationAdapter';
|
|
27
27
|
export { createWebVoiceAdapter } from './adapters/webVoiceAdapter';
|
|
28
28
|
export type { WebVoiceAdapterOptions } from './adapters/webVoiceAdapter';
|
|
29
|
+
export { createWebCredentialAutofillAdapter } from './adapters/webCredentialAutofillAdapter';
|
|
29
30
|
export { createFiodosBackendClient, buildManifestPayload } from './api/backendClient';
|
|
30
31
|
export type { FiodosBackendClientOptions } from './api/backendClient';
|
|
31
32
|
export { createFiodosTelemetry } from './api/backendTelemetry';
|
|
@@ -36,6 +37,8 @@ export { createBridge } from './bridge/createBridge';
|
|
|
36
37
|
export type { Bridge } from './bridge/createBridge';
|
|
37
38
|
export { createScreenContextStore } from './context/screenContextStore';
|
|
38
39
|
export type { ScreenContextStore, AgentScreenSnapshot } from './context/screenContextStore';
|
|
40
|
+
export { connectDynamicsFormContext, dynamicsFormSnapshot, normalizeDynamicsId, } from './embed/dynamics';
|
|
41
|
+
export type { DynamicsFormContextLike, DynamicsSnapshotOptions, DynamicsContextBridge, ScreenContextSink, } from './embed/dynamics';
|
|
39
42
|
export { createSpeechSession } from './speech/speechSession';
|
|
40
43
|
export type { SpeechSession, SpeechSessionOptions, SpeechSessionState, SpeechSessionSnapshot, } from './speech/speechSession';
|
|
41
44
|
export { DEFAULT_AGENT_TIMINGS, DEFAULT_AGENT_CHAINING } from './config/types';
|
package/dist/cjs/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* behaviour and security. No framework imports live here.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.classifyPendingReply = exports.decideAction = exports.resolveUiMessages = exports.DEFAULT_AGENT_CHAINING = exports.DEFAULT_AGENT_TIMINGS = exports.createSpeechSession = exports.createScreenContextStore = exports.createBridge = exports.isAgentApiError = exports.AgentApiError = exports.createFiodosTelemetry = exports.buildManifestPayload = exports.createFiodosBackendClient = exports.createWebVoiceAdapter = exports.BACK_ROUTE = exports.createWebNavigationAdapter = exports.createWebStorageAdapter = exports.watchPublishedConfig = exports.DEFAULT_ORB_APPEARANCE = exports.buildKeyboardChip = exports.createOrbVisual = exports.mountOrb = exports.sendOrbSeen = exports.fetchClientManifest = exports.DEFAULT_WEB_API_URL = exports.createFiodosAgent = exports.AgentController = exports.SDK_PACKAGE = exports.SDK_VERSION = void 0;
|
|
12
|
+
exports.classifyPendingReply = exports.decideAction = exports.resolveUiMessages = exports.DEFAULT_AGENT_CHAINING = exports.DEFAULT_AGENT_TIMINGS = exports.createSpeechSession = exports.normalizeDynamicsId = exports.dynamicsFormSnapshot = exports.connectDynamicsFormContext = exports.createScreenContextStore = exports.createBridge = exports.isAgentApiError = exports.AgentApiError = exports.createFiodosTelemetry = exports.buildManifestPayload = exports.createFiodosBackendClient = exports.createWebCredentialAutofillAdapter = exports.createWebVoiceAdapter = exports.BACK_ROUTE = exports.createWebNavigationAdapter = exports.createWebStorageAdapter = exports.watchPublishedConfig = exports.DEFAULT_ORB_APPEARANCE = exports.buildKeyboardChip = exports.createOrbVisual = exports.mountOrb = exports.sendOrbSeen = exports.fetchClientManifest = exports.DEFAULT_WEB_API_URL = exports.createFiodosAgent = exports.AgentController = exports.SDK_PACKAGE = exports.SDK_VERSION = void 0;
|
|
13
13
|
// ── Version (reported by the orb heartbeat) ───────────────────────────────────
|
|
14
14
|
var version_1 = require("./version");
|
|
15
15
|
Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return version_1.SDK_VERSION; } });
|
|
@@ -42,6 +42,8 @@ Object.defineProperty(exports, "createWebNavigationAdapter", { enumerable: true,
|
|
|
42
42
|
Object.defineProperty(exports, "BACK_ROUTE", { enumerable: true, get: function () { return webNavigationAdapter_1.BACK_ROUTE; } });
|
|
43
43
|
var webVoiceAdapter_1 = require("./adapters/webVoiceAdapter");
|
|
44
44
|
Object.defineProperty(exports, "createWebVoiceAdapter", { enumerable: true, get: function () { return webVoiceAdapter_1.createWebVoiceAdapter; } });
|
|
45
|
+
var webCredentialAutofillAdapter_1 = require("./adapters/webCredentialAutofillAdapter");
|
|
46
|
+
Object.defineProperty(exports, "createWebCredentialAutofillAdapter", { enumerable: true, get: function () { return webCredentialAutofillAdapter_1.createWebCredentialAutofillAdapter; } });
|
|
45
47
|
// ── Backend + telemetry (same wire contract as every platform) ────────────────
|
|
46
48
|
var backendClient_1 = require("./api/backendClient");
|
|
47
49
|
Object.defineProperty(exports, "createFiodosBackendClient", { enumerable: true, get: function () { return backendClient_1.createFiodosBackendClient; } });
|
|
@@ -57,6 +59,11 @@ Object.defineProperty(exports, "createBridge", { enumerable: true, get: function
|
|
|
57
59
|
// ── Screen context ────────────────────────────────────────────────────────────
|
|
58
60
|
var screenContextStore_1 = require("./context/screenContextStore");
|
|
59
61
|
Object.defineProperty(exports, "createScreenContextStore", { enumerable: true, get: function () { return screenContextStore_1.createScreenContextStore; } });
|
|
62
|
+
// ── Closed-host context adapters (embed mode) ─────────────────────────────────
|
|
63
|
+
var dynamics_1 = require("./embed/dynamics");
|
|
64
|
+
Object.defineProperty(exports, "connectDynamicsFormContext", { enumerable: true, get: function () { return dynamics_1.connectDynamicsFormContext; } });
|
|
65
|
+
Object.defineProperty(exports, "dynamicsFormSnapshot", { enumerable: true, get: function () { return dynamics_1.dynamicsFormSnapshot; } });
|
|
66
|
+
Object.defineProperty(exports, "normalizeDynamicsId", { enumerable: true, get: function () { return dynamics_1.normalizeDynamicsId; } });
|
|
60
67
|
// ── Speech session ──────────────────────────────────────────────────────────────
|
|
61
68
|
var speechSession_1 = require("./speech/speechSession");
|
|
62
69
|
Object.defineProperty(exports, "createSpeechSession", { enumerable: true, get: function () { return speechSession_1.createSpeechSession; } });
|
package/dist/cjs/orb/mountOrb.js
CHANGED
|
@@ -30,8 +30,23 @@ const CSS = `
|
|
|
30
30
|
.fyodos-orb-btn img{pointer-events:none;-webkit-user-drag:none;user-select:none;}
|
|
31
31
|
.fyodos-orb-btn:focus-visible{outline:2px solid #8ab6ff;outline-offset:4px;border-radius:50%;}
|
|
32
32
|
.fyodos-orb-anchor{position:relative;display:inline-flex;}
|
|
33
|
-
.fyodos-bubble{position:absolute;display:flex;flex-direction:column;width:min(320px,calc(100vw - 48px));max-height:340px;overflow:hidden;border-radius:16px;box-shadow:0 16px 40px rgba(0,0,0,0.28);border:1px solid rgba(160,180,220,0.22);animation:fyodos-fade-in .14s ease-out;}
|
|
34
|
-
.fyodos-bubble
|
|
33
|
+
.fyodos-bubble{position:absolute;display:flex;flex-direction:column;width:min(320px,calc(100vw - 48px));max-height:340px;overflow:hidden;border-radius:16px;box-shadow:0 16px 40px rgba(0,0,0,0.28);border:1px solid rgba(160,180,220,0.22);animation:fyodos-fade-in .14s ease-out;transition:width .25s cubic-bezier(0.22,1,0.36,1),max-height .25s cubic-bezier(0.22,1,0.36,1);cursor:zoom-in;}
|
|
34
|
+
.fyodos-bubble--expanded{width:min(560px,calc(100vw - 32px));max-height:min(620px,75vh);cursor:auto;}
|
|
35
|
+
.fyodos-bubble-user,.fyodos-bubble-reply{cursor:text;}
|
|
36
|
+
.fyodos-bubble-row{cursor:default;}
|
|
37
|
+
.fyodos-bubble--expanded .fyodos-bubble-scroll{padding:22px 24px 8px;gap:10px;}
|
|
38
|
+
.fyodos-bubble--expanded .fyodos-bubble-exchanges{gap:14px;}
|
|
39
|
+
.fyodos-bubble--expanded .fyodos-bubble-exchange{gap:9px;}
|
|
40
|
+
.fyodos-bubble--expanded .fyodos-bubble-exchange:first-child .fyodos-bubble-user{padding-right:0;}
|
|
41
|
+
.fyodos-bubble--expanded .fyodos-bubble-user{font-size:14.5px;line-height:21px;}
|
|
42
|
+
.fyodos-bubble--expanded .fyodos-bubble-reply{font-size:17px;line-height:25px;}
|
|
43
|
+
.fyodos-bubble--expanded .fyodos-bubble-row{padding:12px 16px;gap:10px;}
|
|
44
|
+
.fyodos-bubble--expanded .fyodos-bubble-busy{padding:8px 24px;}
|
|
45
|
+
.fyodos-bubble--expanded .fyodos-bubble-row input{padding:12px 14px;font-size:16px;}
|
|
46
|
+
.fyodos-bubble--expanded .fyodos-bubble-mic,.fyodos-bubble--expanded .fyodos-bubble-row button{width:38px;height:38px;min-width:38px;font-size:19px;}
|
|
47
|
+
.fyodos-bubble-close,.fyodos-bubble-unexpand{position:absolute;top:4px;z-index:1;display:flex;align-items:center;justify-content:center;border:none;background:transparent;font-size:18px;line-height:18px;width:28px;height:28px;cursor:pointer;padding:0;opacity:.75;}
|
|
48
|
+
.fyodos-bubble-close{right:6px;}
|
|
49
|
+
.fyodos-bubble-unexpand{right:38px;display:none;}
|
|
35
50
|
.fyodos-bubble-user-row{display:flex;flex-direction:row;align-items:flex-start;gap:6px;}
|
|
36
51
|
.fyodos-bubble-scroll{overflow-y:auto;padding:12px 14px 4px;display:flex;flex-direction:column;gap:6px;min-height:18px;scrollbar-width:none;-ms-overflow-style:none;}
|
|
37
52
|
.fyodos-bubble-scroll::-webkit-scrollbar{display:none;width:0;height:0;}
|
|
@@ -373,6 +388,11 @@ function mountOrb(controller, options = {}) {
|
|
|
373
388
|
btn.addEventListener('lostpointercapture', onPointerUp);
|
|
374
389
|
// ── Text bubble ─────────────────────────────────────────────────────────────
|
|
375
390
|
let bubbleOpen = false;
|
|
391
|
+
// Reduced (default) size stays exactly as before. Tapping any EMPTY area of
|
|
392
|
+
// the panel (not a message, not the input row, not a button — so text stays
|
|
393
|
+
// selectable/copyable) grows it to the expanded layout; shrinking back is an
|
|
394
|
+
// explicit tap on `unexpandBtn` next to the close button.
|
|
395
|
+
let bubbleExpanded = false;
|
|
376
396
|
// When the mic is denied/unavailable, tapping the orb still puts it in the
|
|
377
397
|
// SAME "active/listening" visual state and reveals the keyboard chip — the
|
|
378
398
|
// user is never blocked, text is always an open avenue (Change 1).
|
|
@@ -409,6 +429,19 @@ function mountOrb(controller, options = {}) {
|
|
|
409
429
|
closeBtn.textContent = '×';
|
|
410
430
|
closeBtn.setAttribute('aria-label', 'Close');
|
|
411
431
|
closeBtn.addEventListener('click', () => setBubble(false));
|
|
432
|
+
// Collapse button — only shown while expanded, right next to the close
|
|
433
|
+
// button (same minimalist style). Tapping an empty area of the panel is
|
|
434
|
+
// what EXPANDS it; shrinking back is always an explicit, discoverable tap.
|
|
435
|
+
const unexpandBtn = document.createElement('button');
|
|
436
|
+
unexpandBtn.type = 'button';
|
|
437
|
+
unexpandBtn.className = 'fyodos-bubble-unexpand';
|
|
438
|
+
unexpandBtn.innerHTML =
|
|
439
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7"></path></svg>';
|
|
440
|
+
unexpandBtn.setAttribute('aria-label', 'Collapse');
|
|
441
|
+
unexpandBtn.addEventListener('click', (e) => {
|
|
442
|
+
e.stopPropagation();
|
|
443
|
+
setBubbleExpanded(false);
|
|
444
|
+
});
|
|
412
445
|
// Loading row shown at the top while an older page of the session history
|
|
413
446
|
// streams in (scroll-to-top pagination).
|
|
414
447
|
const loaderRow = document.createElement('div');
|
|
@@ -562,8 +595,71 @@ function mountOrb(controller, options = {}) {
|
|
|
562
595
|
micBtn.setAttribute('aria-label', active ? ui.micStopLabel : ui.micLabel);
|
|
563
596
|
micBtn.title = active ? ui.micStopLabel : ui.micLabel;
|
|
564
597
|
}
|
|
565
|
-
bubble.append(closeBtn, exchangeScroll, busyRow, inputRow);
|
|
598
|
+
bubble.append(closeBtn, unexpandBtn, exchangeScroll, busyRow, inputRow);
|
|
566
599
|
anchor.appendChild(bubble);
|
|
600
|
+
function setBubbleExpanded(expanded) {
|
|
601
|
+
if (bubbleExpanded === expanded)
|
|
602
|
+
return;
|
|
603
|
+
bubbleExpanded = expanded;
|
|
604
|
+
bubble.classList.toggle('fyodos-bubble--expanded', expanded);
|
|
605
|
+
unexpandBtn.style.display = expanded ? 'flex' : 'none';
|
|
606
|
+
positionBubble();
|
|
607
|
+
}
|
|
608
|
+
// Tap-to-expand: a plain click on the panel grows it. Text selection is
|
|
609
|
+
// never broken: a drag-selection is a move (threshold guard), a double/
|
|
610
|
+
// triple-click word/paragraph selection cancels the pending expand (detail
|
|
611
|
+
// guard + delay), and any active selection blocks it. Controls (input row,
|
|
612
|
+
// buttons) never expand.
|
|
613
|
+
let bubbleTapX = 0;
|
|
614
|
+
let bubbleTapY = 0;
|
|
615
|
+
let pendingExpandTimer = null;
|
|
616
|
+
const TAP_MOVE_THRESHOLD = 8;
|
|
617
|
+
const hasActiveSelection = () => {
|
|
618
|
+
const selection = typeof window !== 'undefined' ? window.getSelection?.() : null;
|
|
619
|
+
return !!selection && !selection.isCollapsed && selection.toString().length > 0;
|
|
620
|
+
};
|
|
621
|
+
const cancelPendingExpand = () => {
|
|
622
|
+
if (pendingExpandTimer != null) {
|
|
623
|
+
window.clearTimeout(pendingExpandTimer);
|
|
624
|
+
pendingExpandTimer = null;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
bubble.addEventListener('pointerdown', (e) => {
|
|
628
|
+
bubbleTapX = e.clientX;
|
|
629
|
+
bubbleTapY = e.clientY;
|
|
630
|
+
});
|
|
631
|
+
bubble.addEventListener('click', (e) => {
|
|
632
|
+
if (bubbleExpanded)
|
|
633
|
+
return;
|
|
634
|
+
const target = e.target;
|
|
635
|
+
if (target?.closest('.fyodos-bubble-row, .fyodos-bubble-close, .fyodos-bubble-unexpand, button, input, a')) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const moved = Math.hypot(e.clientX - bubbleTapX, e.clientY - bubbleTapY);
|
|
639
|
+
if (moved > TAP_MOVE_THRESHOLD)
|
|
640
|
+
return;
|
|
641
|
+
// Double/triple click is a word/paragraph selection gesture, never expand
|
|
642
|
+
// (it also cancels the pending expand from its own first click).
|
|
643
|
+
if (e.detail > 1) {
|
|
644
|
+
cancelPendingExpand();
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
cancelPendingExpand();
|
|
648
|
+
const overText = !!target?.closest('.fyodos-bubble-user, .fyodos-bubble-reply');
|
|
649
|
+
if (!overText && !hasActiveSelection()) {
|
|
650
|
+
// Genuinely empty area, nothing selected — expand immediately.
|
|
651
|
+
setBubbleExpanded(true);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// Over message text, or a selection may still be collapsing: expand after
|
|
655
|
+
// a short delay, re-checking the selection right before — a double-click's
|
|
656
|
+
// second click or a real selection cancels it.
|
|
657
|
+
pendingExpandTimer = window.setTimeout(() => {
|
|
658
|
+
pendingExpandTimer = null;
|
|
659
|
+
if (!hasActiveSelection())
|
|
660
|
+
setBubbleExpanded(true);
|
|
661
|
+
}, 280);
|
|
662
|
+
});
|
|
567
663
|
// Initial placement (after the bubble exists, so applyDeployment can lay it out).
|
|
568
664
|
reposition();
|
|
569
665
|
function applyBubbleTheme() {
|
|
@@ -574,6 +670,7 @@ function mountOrb(controller, options = {}) {
|
|
|
574
670
|
if (modalTheme?.borderRadius != null)
|
|
575
671
|
bubble.style.borderRadius = `${modalTheme.borderRadius}px`;
|
|
576
672
|
closeBtn.style.color = t.bodyColor;
|
|
673
|
+
unexpandBtn.style.color = t.bodyColor;
|
|
577
674
|
exchangeList.querySelectorAll('.fyodos-bubble-user').forEach((node) => {
|
|
578
675
|
node.style.color = t.bodyColor;
|
|
579
676
|
});
|
|
@@ -608,6 +705,9 @@ function mountOrb(controller, options = {}) {
|
|
|
608
705
|
controller.resetBubbleWindow();
|
|
609
706
|
// Closing the panel returns the orb to idle (also clears mic fallback).
|
|
610
707
|
micFallbackActive = false;
|
|
708
|
+
// Always re-open at the reduced size.
|
|
709
|
+
cancelPendingExpand();
|
|
710
|
+
setBubbleExpanded(false);
|
|
611
711
|
}
|
|
612
712
|
syncChip(controller.getState());
|
|
613
713
|
}
|
|
@@ -710,29 +810,17 @@ function mountOrb(controller, options = {}) {
|
|
|
710
810
|
track.appendChild(second);
|
|
711
811
|
thoughtBubbleEl.appendChild(track);
|
|
712
812
|
}
|
|
713
|
-
function applyThoughtBubbleTheme() {
|
|
813
|
+
function applyThoughtBubbleTheme(widthPx) {
|
|
714
814
|
const t = bubbleTheme;
|
|
715
815
|
const { fontPx, padH, approxHeight } = (0, core_1.resolveBubblePadding)(t);
|
|
716
|
-
const shapeCss = (0, core_1.bubbleShapeToCss)(t, approxHeight);
|
|
816
|
+
const shapeCss = (0, core_1.bubbleShapeToCss)(t, approxHeight, 1, widthPx);
|
|
717
817
|
thoughtBubbleEl.style.fontSize = `${fontPx}px`;
|
|
718
818
|
thoughtBubbleEl.style.height = `${approxHeight}px`;
|
|
719
819
|
thoughtBubbleEl.style.padding = `0 ${padH}px`;
|
|
720
820
|
thoughtBubbleEl.style.color = t.textColor;
|
|
721
821
|
thoughtBubbleEl.style.background = t.backgroundColor;
|
|
722
822
|
thoughtBubbleEl.style.border = `${t.borderWidth}px solid ${t.borderColor}`;
|
|
723
|
-
|
|
724
|
-
if (!value) {
|
|
725
|
-
thoughtBubbleEl.style.removeProperty(prop);
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
thoughtBubbleEl.style.setProperty(prop, value, 'important');
|
|
729
|
-
};
|
|
730
|
-
setImportant('border-radius', shapeCss.borderRadius);
|
|
731
|
-
setImportant('border-top-left-radius', shapeCss.borderTopLeftRadius);
|
|
732
|
-
setImportant('border-top-right-radius', shapeCss.borderTopRightRadius);
|
|
733
|
-
setImportant('border-bottom-left-radius', shapeCss.borderBottomLeftRadius);
|
|
734
|
-
setImportant('border-bottom-right-radius', shapeCss.borderBottomRightRadius);
|
|
735
|
-
setImportant('clip-path', shapeCss.clipPath);
|
|
823
|
+
(0, core_1.applyBubbleShapeCss)(thoughtBubbleEl, shapeCss);
|
|
736
824
|
}
|
|
737
825
|
function positionThoughtBubble() {
|
|
738
826
|
const dep = (0, core_1.resolveOrbDeployment)(effectivePosition());
|
|
@@ -767,6 +855,11 @@ function mountOrb(controller, options = {}) {
|
|
|
767
855
|
thoughtBubbleEl.style.display = 'flex';
|
|
768
856
|
applyThoughtBubbleTheme();
|
|
769
857
|
layoutThoughtContent(prompt);
|
|
858
|
+
if (bubbleTheme.shape === 'cloud') {
|
|
859
|
+
const widthPx = thoughtBubbleEl.offsetWidth;
|
|
860
|
+
if (widthPx > 0)
|
|
861
|
+
applyThoughtBubbleTheme(widthPx);
|
|
862
|
+
}
|
|
770
863
|
positionThoughtBubble();
|
|
771
864
|
}
|
|
772
865
|
// ── Overlays (confirmation + consent) ────────────────────────────────────────
|
package/dist/cjs/orb/orbView.js
CHANGED
|
@@ -78,9 +78,14 @@ function buildDoubleBorder(opts) {
|
|
|
78
78
|
root.appendChild(mid);
|
|
79
79
|
return { root, fill };
|
|
80
80
|
}
|
|
81
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Neutral Fiodos brand mark (idle, no logo). Breathes very gently on web — a
|
|
83
|
+
* direct port of the landing hero orbs' waveform easing (see
|
|
84
|
+
* `fiodosOrbMarkIdleHalfHeight` in @fiodos/core) — deliberately subtler than
|
|
85
|
+
* and distinct from the volume-reactive waveform shown while listening.
|
|
86
|
+
*/
|
|
82
87
|
function buildOrbMark(orbSize) {
|
|
83
|
-
const { bars, color, radius } = (0, core_1.
|
|
88
|
+
const { bars, color, radius } = (0, core_1.resolveFiodosOrbMarkAnimatedBars)(orbSize);
|
|
84
89
|
const svg = svgEl('svg', {
|
|
85
90
|
width: orbSize,
|
|
86
91
|
height: orbSize,
|
|
@@ -88,10 +93,31 @@ function buildOrbMark(orbSize) {
|
|
|
88
93
|
'aria-hidden': 'true',
|
|
89
94
|
});
|
|
90
95
|
svg.style.display = 'block';
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
const rects = bars.map((b) => {
|
|
97
|
+
const rect = svgEl('rect', { x: b.x, y: b.y, width: b.width, height: b.height, rx: radius, ry: radius, fill: color });
|
|
98
|
+
svg.appendChild(rect);
|
|
99
|
+
return rect;
|
|
100
|
+
});
|
|
101
|
+
let raf = 0;
|
|
102
|
+
return {
|
|
103
|
+
el: svg,
|
|
104
|
+
start() {
|
|
105
|
+
const t0 = performance.now();
|
|
106
|
+
const loop = (now) => {
|
|
107
|
+
raf = requestAnimationFrame(loop);
|
|
108
|
+
const t = (now - t0) / 1000;
|
|
109
|
+
bars.forEach((b, i) => {
|
|
110
|
+
const half = (0, core_1.fiodosOrbMarkIdleHalfHeight)(t, i, b.baseHalfHeight, b.maxHalfHeight);
|
|
111
|
+
rects[i].setAttribute('y', (b.centerY - half).toFixed(2));
|
|
112
|
+
rects[i].setAttribute('height', (2 * half).toFixed(2));
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
raf = requestAnimationFrame(loop);
|
|
116
|
+
},
|
|
117
|
+
stop() {
|
|
118
|
+
cancelAnimationFrame(raf);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
95
121
|
}
|
|
96
122
|
// ── Waveform (port of VoiceWaveform: 9 bars, bell profile, rAF) ───────────────
|
|
97
123
|
const WAVE_BARS = 9;
|
|
@@ -382,6 +408,7 @@ function createOrbVisual(initial) {
|
|
|
382
408
|
let volume = 0;
|
|
383
409
|
let fillEl = null;
|
|
384
410
|
let waveform = null;
|
|
411
|
+
let markAnim = null;
|
|
385
412
|
function rebuild() {
|
|
386
413
|
const size = appearance.size ?? exports.DEFAULT_ORB_APPEARANCE.size;
|
|
387
414
|
const fill = (0, core_1.resolveOrbBackgroundColor)({
|
|
@@ -393,6 +420,8 @@ function createOrbVisual(initial) {
|
|
|
393
420
|
const interiorColor = (0, core_1.resolveInnerBorderColor)(appearance.innerBorderColor);
|
|
394
421
|
waveform?.stop();
|
|
395
422
|
waveform = null;
|
|
423
|
+
markAnim?.stop();
|
|
424
|
+
markAnim = null;
|
|
396
425
|
wrapper.replaceChildren();
|
|
397
426
|
const { root, fill: fillNode } = buildDoubleBorder({
|
|
398
427
|
fillDiameter: size,
|
|
@@ -414,6 +443,8 @@ function createOrbVisual(initial) {
|
|
|
414
443
|
const waveColor = appearance.waveformColor ?? '#ffffff';
|
|
415
444
|
waveform?.stop();
|
|
416
445
|
waveform = null;
|
|
446
|
+
markAnim?.stop();
|
|
447
|
+
markAnim = null;
|
|
417
448
|
fillEl.replaceChildren();
|
|
418
449
|
// The "speaking" feedback now lives INSIDE the fill (a pulsing logo, or the
|
|
419
450
|
// rings + dot when there's no photo) to mirror the dashboard editor, so the
|
|
@@ -458,7 +489,9 @@ function createOrbVisual(initial) {
|
|
|
458
489
|
return;
|
|
459
490
|
}
|
|
460
491
|
if (state === 'idle') {
|
|
461
|
-
|
|
492
|
+
markAnim = buildOrbMark(size);
|
|
493
|
+
fillEl.appendChild(markAnim.el);
|
|
494
|
+
markAnim.start();
|
|
462
495
|
}
|
|
463
496
|
}
|
|
464
497
|
rebuild();
|
|
@@ -60,7 +60,9 @@ export interface WatchPublishedConfigOptions {
|
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* Starts watching the published config. Returns an unsubscribe function.
|
|
63
|
-
* Best-effort: a failed fetch
|
|
64
|
-
*
|
|
63
|
+
* Best-effort: a failed fetch is SILENTLY skipped — the orb keeps its
|
|
64
|
+
* last-known-good (or cached) look instead of reverting to the default — and
|
|
65
|
+
* never throws into the host app. `onChange(null)` is reported ONLY when the
|
|
66
|
+
* backend confirms nothing is published.
|
|
65
67
|
*/
|
|
66
68
|
export declare function watchPublishedConfig(controller: AgentController, options: WatchPublishedConfigOptions): () => void;
|
|
@@ -14,6 +14,38 @@ exports.watchPublishedConfig = watchPublishedConfig;
|
|
|
14
14
|
*/
|
|
15
15
|
const core_1 = require("@fiodos/core");
|
|
16
16
|
const orbView_1 = require("./orbView");
|
|
17
|
+
/**
|
|
18
|
+
* Last-known-good published config, persisted so the customized orb survives
|
|
19
|
+
* page reloads during a backend outage and shows instantly on the next visit.
|
|
20
|
+
* Best-effort: storage failures (private mode, quota) are ignored.
|
|
21
|
+
*/
|
|
22
|
+
const PUBLISHED_CONFIG_CACHE_KEY = 'fyodos:published-config:v1';
|
|
23
|
+
function readCachedPublishedConfig() {
|
|
24
|
+
try {
|
|
25
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
26
|
+
return null;
|
|
27
|
+
const raw = window.localStorage.getItem(PUBLISHED_CONFIG_CACHE_KEY);
|
|
28
|
+
return raw ? JSON.parse(raw) : null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function writeCachedPublishedConfig(published) {
|
|
35
|
+
try {
|
|
36
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
37
|
+
return;
|
|
38
|
+
if (published) {
|
|
39
|
+
window.localStorage.setItem(PUBLISHED_CONFIG_CACHE_KEY, JSON.stringify(published));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
window.localStorage.removeItem(PUBLISHED_CONFIG_CACHE_KEY);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
/* best-effort cache */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
17
49
|
function toResolved(props, published) {
|
|
18
50
|
const enabled = published.orbEnabled !== false;
|
|
19
51
|
const theme = props.theme;
|
|
@@ -48,8 +80,10 @@ function toResolved(props, published) {
|
|
|
48
80
|
}
|
|
49
81
|
/**
|
|
50
82
|
* Starts watching the published config. Returns an unsubscribe function.
|
|
51
|
-
* Best-effort: a failed fetch
|
|
52
|
-
*
|
|
83
|
+
* Best-effort: a failed fetch is SILENTLY skipped — the orb keeps its
|
|
84
|
+
* last-known-good (or cached) look instead of reverting to the default — and
|
|
85
|
+
* never throws into the host app. `onChange(null)` is reported ONLY when the
|
|
86
|
+
* backend confirms nothing is published.
|
|
53
87
|
*/
|
|
54
88
|
function watchPublishedConfig(controller, options) {
|
|
55
89
|
const fetcher = controller.config.backend.fetchPublishedConfig;
|
|
@@ -58,18 +92,33 @@ function watchPublishedConfig(controller, options) {
|
|
|
58
92
|
return () => { };
|
|
59
93
|
}
|
|
60
94
|
let cancelled = false;
|
|
95
|
+
// Hydrate from the local cache so the customized orb shows instantly (and
|
|
96
|
+
// survives page loads while the backend is unreachable). The first network
|
|
97
|
+
// response below overwrites it as the source of truth.
|
|
98
|
+
const cached = readCachedPublishedConfig();
|
|
99
|
+
if (cached) {
|
|
100
|
+
options.onChange(toResolved((0, core_1.mapPublishedConfigToOrbProps)(cached), cached));
|
|
101
|
+
}
|
|
61
102
|
const load = async () => {
|
|
62
103
|
try {
|
|
63
104
|
const res = await fetcher.call(controller.config.backend);
|
|
64
105
|
if (cancelled)
|
|
65
106
|
return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
107
|
+
if (res.published) {
|
|
108
|
+
options.onChange(toResolved((0, core_1.mapPublishedConfigToOrbProps)(res.published), res.published));
|
|
109
|
+
writeCachedPublishedConfig(res.published);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// A SUCCESSFUL response with nothing published: the project truly has
|
|
113
|
+
// no custom design, so the defaults are correct.
|
|
114
|
+
options.onChange(null);
|
|
115
|
+
writeCachedPublishedConfig(null);
|
|
116
|
+
}
|
|
69
117
|
}
|
|
70
118
|
catch {
|
|
71
|
-
|
|
72
|
-
|
|
119
|
+
// Transient failure (timeout, network blip after hours open, backend
|
|
120
|
+
// redeploy…): keep the current appearance. The next successful poll
|
|
121
|
+
// refreshes it.
|
|
73
122
|
}
|
|
74
123
|
};
|
|
75
124
|
void load();
|
package/dist/cjs/version.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
|
|
6
6
|
* by hand — change package.json `version` and re-run the sync/release script.
|
|
7
7
|
*/
|
|
8
|
-
export declare const SDK_VERSION = "0.1.
|
|
8
|
+
export declare const SDK_VERSION = "0.1.14";
|
|
9
9
|
export declare const SDK_PACKAGE = "@fiodos/web-core";
|
package/dist/cjs/version.js
CHANGED
|
@@ -8,5 +8,5 @@ exports.SDK_PACKAGE = exports.SDK_VERSION = void 0;
|
|
|
8
8
|
* Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
|
|
9
9
|
* by hand — change package.json `version` and re-run the sync/release script.
|
|
10
10
|
*/
|
|
11
|
-
exports.SDK_VERSION = '0.1.
|
|
11
|
+
exports.SDK_VERSION = '0.1.14';
|
|
12
12
|
exports.SDK_PACKAGE = '@fiodos/web-core';
|