chatablex-web-sdk 1.0.32 → 1.0.34
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/index.d.mts +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +225 -6
- package/dist/index.mjs +225 -6
- package/package.json +3 -3
- package/src/assets/bee.png +0 -0
- package/src/assets.d.ts +4 -0
- package/src/index.ts +5 -1
- package/src/modules/agentLock.ts +255 -0
- package/src/modules/tool.ts +12 -1
- package/src/types.ts +51 -0
package/dist/index.d.mts
CHANGED
|
@@ -127,6 +127,8 @@ interface ChatableXInitConfig {
|
|
|
127
127
|
* calls reject with a clear error.
|
|
128
128
|
*/
|
|
129
129
|
apiBaseUrl?: string;
|
|
130
|
+
/** Agent lock configuration — blocks user input during tool execution. */
|
|
131
|
+
agentLock?: AgentLockConfig;
|
|
130
132
|
}
|
|
131
133
|
interface ChatableXAI {
|
|
132
134
|
chat(message: string, options?: ChatOptions): Promise<ChatResponse>;
|
|
@@ -261,6 +263,48 @@ interface ChatableXCloud {
|
|
|
261
263
|
/** Read the account's storage usage / quota. */
|
|
262
264
|
usage(): Promise<CloudUsage>;
|
|
263
265
|
}
|
|
266
|
+
interface AgentLockConfig {
|
|
267
|
+
/** Enable the agent lock feature (default: true). */
|
|
268
|
+
enabled?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* `"overlay"` — SDK renders a built-in transparent overlay (default).
|
|
271
|
+
* `"events-only"` — SDK only emits lock/unlock events; no overlay injected.
|
|
272
|
+
*/
|
|
273
|
+
mode?: 'overlay' | 'events-only';
|
|
274
|
+
/** URL of the logo displayed in the overlay centre. Defaults to built-in Chatablex SVG. */
|
|
275
|
+
logoUrl?: string;
|
|
276
|
+
/** Message shown below the logo (default: "Agent 正在操作,请稍候…"). */
|
|
277
|
+
message?: string;
|
|
278
|
+
/** Show a cancel button on the overlay (default: true). */
|
|
279
|
+
allowCancel?: boolean;
|
|
280
|
+
/** Overlay background opacity, 0–1 (default: 0.3). */
|
|
281
|
+
opacity?: number;
|
|
282
|
+
/** Auto-unlock timeout in ms (default: 30000). 0 disables. */
|
|
283
|
+
timeout?: number;
|
|
284
|
+
/** Delay before actually removing the overlay after unlock, to avoid flicker between consecutive tools (default: 200ms). */
|
|
285
|
+
debounceUnlock?: number;
|
|
286
|
+
}
|
|
287
|
+
type AgentLockEventType = 'lock' | 'unlock' | 'cancel' | 'timeout';
|
|
288
|
+
interface AgentLockEventData {
|
|
289
|
+
requestId?: string;
|
|
290
|
+
timestamp: number;
|
|
291
|
+
}
|
|
292
|
+
type AgentLockEventHandler = (data: AgentLockEventData) => void;
|
|
293
|
+
interface ChatableXAgentLock {
|
|
294
|
+
/** Manually lock user interaction with an optional custom message / timeout. */
|
|
295
|
+
lock(opts?: {
|
|
296
|
+
message?: string;
|
|
297
|
+
timeout?: number;
|
|
298
|
+
}): void;
|
|
299
|
+
/** Manually unlock. Safe to call when already unlocked. */
|
|
300
|
+
unlock(): void;
|
|
301
|
+
/** Whether the overlay is currently active. */
|
|
302
|
+
isLocked(): boolean;
|
|
303
|
+
/** Subscribe to lock lifecycle events. Returns an unsubscribe function. */
|
|
304
|
+
on(event: AgentLockEventType, handler: AgentLockEventHandler): () => void;
|
|
305
|
+
/** Remove a previously registered handler. */
|
|
306
|
+
off(event: AgentLockEventType, handler: AgentLockEventHandler): void;
|
|
307
|
+
}
|
|
264
308
|
interface ChatableXSDK {
|
|
265
309
|
ai: ChatableXAI;
|
|
266
310
|
tools: ChatableXTools;
|
|
@@ -271,6 +315,7 @@ interface ChatableXSDK {
|
|
|
271
315
|
platform: ChatableXPlatform;
|
|
272
316
|
auth: ChatableXAuth;
|
|
273
317
|
cloud: ChatableXCloud;
|
|
318
|
+
agentLock: ChatableXAgentLock;
|
|
274
319
|
}
|
|
275
320
|
declare global {
|
|
276
321
|
interface Window {
|
|
@@ -386,4 +431,4 @@ declare const ChatableX: {
|
|
|
386
431
|
version: string;
|
|
387
432
|
};
|
|
388
433
|
|
|
389
|
-
export { type AiResponseEventData, type AuthTokenData, Bridge, type ChatOptions, type ChatResponse, ChatableX, type ChatableXAI, type ChatableXAuth, type ChatableXCloud, type ChatableXEvents, type ChatableXInitConfig, type ChatableXPlatform, type ChatableXSDK, type ChatableXStorage, type ChatableXToolModule, type ChatableXTools, type ChatableXUI, type CloseEventData, CloudAuthRequiredError, CloudError, type CloudFileInfo, type CloudListOptions, CloudQuotaExceededError, CloudSubscriptionRequiredError, type CloudUploadData, type CloudUploadOptions, type CloudUploadResult, type CloudUsage, type EventCallbackMap, type EventType, type FilePickerOptions, type Message, type NotificationType, SDK_VERSION, type SessionContext, type StateUpdate, type StreamingContentEventData, type TabConfig, type ToolCall, type ToolExecuteHandler, type ToolExecutionEventData, type ToolInfo, type ToolParameter, type ToolResult, type Unsubscribe, type UserMessageEventData };
|
|
434
|
+
export { type AgentLockConfig, type AgentLockEventData, type AgentLockEventHandler, type AgentLockEventType, type AiResponseEventData, type AuthTokenData, Bridge, type ChatOptions, type ChatResponse, ChatableX, type ChatableXAI, type ChatableXAgentLock, type ChatableXAuth, type ChatableXCloud, type ChatableXEvents, type ChatableXInitConfig, type ChatableXPlatform, type ChatableXSDK, type ChatableXStorage, type ChatableXToolModule, type ChatableXTools, type ChatableXUI, type CloseEventData, CloudAuthRequiredError, CloudError, type CloudFileInfo, type CloudListOptions, CloudQuotaExceededError, CloudSubscriptionRequiredError, type CloudUploadData, type CloudUploadOptions, type CloudUploadResult, type CloudUsage, type EventCallbackMap, type EventType, type FilePickerOptions, type Message, type NotificationType, SDK_VERSION, type SessionContext, type StateUpdate, type StreamingContentEventData, type TabConfig, type ToolCall, type ToolExecuteHandler, type ToolExecutionEventData, type ToolInfo, type ToolParameter, type ToolResult, type Unsubscribe, type UserMessageEventData };
|
package/dist/index.d.ts
CHANGED
|
@@ -127,6 +127,8 @@ interface ChatableXInitConfig {
|
|
|
127
127
|
* calls reject with a clear error.
|
|
128
128
|
*/
|
|
129
129
|
apiBaseUrl?: string;
|
|
130
|
+
/** Agent lock configuration — blocks user input during tool execution. */
|
|
131
|
+
agentLock?: AgentLockConfig;
|
|
130
132
|
}
|
|
131
133
|
interface ChatableXAI {
|
|
132
134
|
chat(message: string, options?: ChatOptions): Promise<ChatResponse>;
|
|
@@ -261,6 +263,48 @@ interface ChatableXCloud {
|
|
|
261
263
|
/** Read the account's storage usage / quota. */
|
|
262
264
|
usage(): Promise<CloudUsage>;
|
|
263
265
|
}
|
|
266
|
+
interface AgentLockConfig {
|
|
267
|
+
/** Enable the agent lock feature (default: true). */
|
|
268
|
+
enabled?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* `"overlay"` — SDK renders a built-in transparent overlay (default).
|
|
271
|
+
* `"events-only"` — SDK only emits lock/unlock events; no overlay injected.
|
|
272
|
+
*/
|
|
273
|
+
mode?: 'overlay' | 'events-only';
|
|
274
|
+
/** URL of the logo displayed in the overlay centre. Defaults to built-in Chatablex SVG. */
|
|
275
|
+
logoUrl?: string;
|
|
276
|
+
/** Message shown below the logo (default: "Agent 正在操作,请稍候…"). */
|
|
277
|
+
message?: string;
|
|
278
|
+
/** Show a cancel button on the overlay (default: true). */
|
|
279
|
+
allowCancel?: boolean;
|
|
280
|
+
/** Overlay background opacity, 0–1 (default: 0.3). */
|
|
281
|
+
opacity?: number;
|
|
282
|
+
/** Auto-unlock timeout in ms (default: 30000). 0 disables. */
|
|
283
|
+
timeout?: number;
|
|
284
|
+
/** Delay before actually removing the overlay after unlock, to avoid flicker between consecutive tools (default: 200ms). */
|
|
285
|
+
debounceUnlock?: number;
|
|
286
|
+
}
|
|
287
|
+
type AgentLockEventType = 'lock' | 'unlock' | 'cancel' | 'timeout';
|
|
288
|
+
interface AgentLockEventData {
|
|
289
|
+
requestId?: string;
|
|
290
|
+
timestamp: number;
|
|
291
|
+
}
|
|
292
|
+
type AgentLockEventHandler = (data: AgentLockEventData) => void;
|
|
293
|
+
interface ChatableXAgentLock {
|
|
294
|
+
/** Manually lock user interaction with an optional custom message / timeout. */
|
|
295
|
+
lock(opts?: {
|
|
296
|
+
message?: string;
|
|
297
|
+
timeout?: number;
|
|
298
|
+
}): void;
|
|
299
|
+
/** Manually unlock. Safe to call when already unlocked. */
|
|
300
|
+
unlock(): void;
|
|
301
|
+
/** Whether the overlay is currently active. */
|
|
302
|
+
isLocked(): boolean;
|
|
303
|
+
/** Subscribe to lock lifecycle events. Returns an unsubscribe function. */
|
|
304
|
+
on(event: AgentLockEventType, handler: AgentLockEventHandler): () => void;
|
|
305
|
+
/** Remove a previously registered handler. */
|
|
306
|
+
off(event: AgentLockEventType, handler: AgentLockEventHandler): void;
|
|
307
|
+
}
|
|
264
308
|
interface ChatableXSDK {
|
|
265
309
|
ai: ChatableXAI;
|
|
266
310
|
tools: ChatableXTools;
|
|
@@ -271,6 +315,7 @@ interface ChatableXSDK {
|
|
|
271
315
|
platform: ChatableXPlatform;
|
|
272
316
|
auth: ChatableXAuth;
|
|
273
317
|
cloud: ChatableXCloud;
|
|
318
|
+
agentLock: ChatableXAgentLock;
|
|
274
319
|
}
|
|
275
320
|
declare global {
|
|
276
321
|
interface Window {
|
|
@@ -386,4 +431,4 @@ declare const ChatableX: {
|
|
|
386
431
|
version: string;
|
|
387
432
|
};
|
|
388
433
|
|
|
389
|
-
export { type AiResponseEventData, type AuthTokenData, Bridge, type ChatOptions, type ChatResponse, ChatableX, type ChatableXAI, type ChatableXAuth, type ChatableXCloud, type ChatableXEvents, type ChatableXInitConfig, type ChatableXPlatform, type ChatableXSDK, type ChatableXStorage, type ChatableXToolModule, type ChatableXTools, type ChatableXUI, type CloseEventData, CloudAuthRequiredError, CloudError, type CloudFileInfo, type CloudListOptions, CloudQuotaExceededError, CloudSubscriptionRequiredError, type CloudUploadData, type CloudUploadOptions, type CloudUploadResult, type CloudUsage, type EventCallbackMap, type EventType, type FilePickerOptions, type Message, type NotificationType, SDK_VERSION, type SessionContext, type StateUpdate, type StreamingContentEventData, type TabConfig, type ToolCall, type ToolExecuteHandler, type ToolExecutionEventData, type ToolInfo, type ToolParameter, type ToolResult, type Unsubscribe, type UserMessageEventData };
|
|
434
|
+
export { type AgentLockConfig, type AgentLockEventData, type AgentLockEventHandler, type AgentLockEventType, type AiResponseEventData, type AuthTokenData, Bridge, type ChatOptions, type ChatResponse, ChatableX, type ChatableXAI, type ChatableXAgentLock, type ChatableXAuth, type ChatableXCloud, type ChatableXEvents, type ChatableXInitConfig, type ChatableXPlatform, type ChatableXSDK, type ChatableXStorage, type ChatableXToolModule, type ChatableXTools, type ChatableXUI, type CloseEventData, CloudAuthRequiredError, CloudError, type CloudFileInfo, type CloudListOptions, CloudQuotaExceededError, CloudSubscriptionRequiredError, type CloudUploadData, type CloudUploadOptions, type CloudUploadResult, type CloudUsage, type EventCallbackMap, type EventType, type FilePickerOptions, type Message, type NotificationType, SDK_VERSION, type SessionContext, type StateUpdate, type StreamingContentEventData, type TabConfig, type ToolCall, type ToolExecuteHandler, type ToolExecutionEventData, type ToolInfo, type ToolParameter, type ToolResult, type Unsubscribe, type UserMessageEventData };
|
package/dist/index.js
CHANGED
|
@@ -164,7 +164,7 @@ var Bridge = class {
|
|
|
164
164
|
};
|
|
165
165
|
|
|
166
166
|
// src/modules/tool.ts
|
|
167
|
-
function createToolModule(bridge, appId) {
|
|
167
|
+
function createToolModule(bridge, appId, agentLock) {
|
|
168
168
|
let _info = { id: appId, name: appId, version: "1.0.0", description: "" };
|
|
169
169
|
let _handler = null;
|
|
170
170
|
const dispatch = async (params) => {
|
|
@@ -182,6 +182,7 @@ function createToolModule(bridge, appId) {
|
|
|
182
182
|
bridge.addEventListener("toolExecution", async (data) => {
|
|
183
183
|
const params = data;
|
|
184
184
|
const requestId = params._requestId;
|
|
185
|
+
if (requestId && agentLock) agentLock._autoLock(requestId);
|
|
185
186
|
const result = await dispatch(params);
|
|
186
187
|
if (requestId && window.ChatableXBridge) {
|
|
187
188
|
window.ChatableXBridge.postMessage(JSON.stringify({
|
|
@@ -189,6 +190,7 @@ function createToolModule(bridge, appId) {
|
|
|
189
190
|
params: { _requestId: requestId, ...result }
|
|
190
191
|
}));
|
|
191
192
|
}
|
|
193
|
+
if (requestId && agentLock) agentLock._autoUnlock(requestId);
|
|
192
194
|
});
|
|
193
195
|
return {
|
|
194
196
|
getInfo() {
|
|
@@ -539,10 +541,225 @@ function createCloudModule(bridge, deps) {
|
|
|
539
541
|
};
|
|
540
542
|
}
|
|
541
543
|
|
|
544
|
+
// src/assets/bee.png
|
|
545
|
+
var bee_default = "data:image/png;base64,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";
|
|
546
|
+
|
|
547
|
+
// src/modules/agentLock.ts
|
|
548
|
+
var DEFAULT_CONFIG = {
|
|
549
|
+
enabled: true,
|
|
550
|
+
mode: "overlay",
|
|
551
|
+
logoUrl: "",
|
|
552
|
+
message: "Agent \u6B63\u5728\u64CD\u4F5C\uFF0C\u8BF7\u7A0D\u5019\u2026",
|
|
553
|
+
allowCancel: true,
|
|
554
|
+
opacity: 0.3,
|
|
555
|
+
timeout: 3e4,
|
|
556
|
+
debounceUnlock: 200
|
|
557
|
+
};
|
|
558
|
+
var OVERLAY_ID = "__chatablex_agent_lock_overlay__";
|
|
559
|
+
var BLOCKED_EVENTS = [
|
|
560
|
+
"mousedown",
|
|
561
|
+
"mouseup",
|
|
562
|
+
"click",
|
|
563
|
+
"dblclick",
|
|
564
|
+
"contextmenu",
|
|
565
|
+
"keydown",
|
|
566
|
+
"keyup",
|
|
567
|
+
"keypress",
|
|
568
|
+
"touchstart",
|
|
569
|
+
"touchmove",
|
|
570
|
+
"touchend",
|
|
571
|
+
"wheel",
|
|
572
|
+
"scroll",
|
|
573
|
+
"pointerdown",
|
|
574
|
+
"pointerup"
|
|
575
|
+
];
|
|
576
|
+
var CANCEL_BTN_ID = "__ctx_agent_lock_cancel__";
|
|
577
|
+
function blockEvent(e) {
|
|
578
|
+
const target = e.target;
|
|
579
|
+
if (target?.id === CANCEL_BTN_ID) return;
|
|
580
|
+
e.stopPropagation();
|
|
581
|
+
e.preventDefault();
|
|
582
|
+
}
|
|
583
|
+
function createAgentLockModule(_bridge, userConfig = {}) {
|
|
584
|
+
const cfg = { ...DEFAULT_CONFIG, ...userConfig };
|
|
585
|
+
const logoSrc = cfg.logoUrl || bee_default;
|
|
586
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
587
|
+
let overlayEl = null;
|
|
588
|
+
let locked = false;
|
|
589
|
+
let lockCount = 0;
|
|
590
|
+
let timeoutTimer = null;
|
|
591
|
+
let debounceTimer = null;
|
|
592
|
+
let currentMessage = cfg.message;
|
|
593
|
+
function emit(event, data = {}) {
|
|
594
|
+
const payload = { timestamp: Date.now(), ...data };
|
|
595
|
+
const handlers = listeners.get(event);
|
|
596
|
+
if (handlers) {
|
|
597
|
+
for (const fn of handlers) {
|
|
598
|
+
try {
|
|
599
|
+
fn(payload);
|
|
600
|
+
} catch (e) {
|
|
601
|
+
console.error("[ChatableX AgentLock] event handler error:", e);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function injectOverlay(message) {
|
|
607
|
+
if (typeof document === "undefined") return;
|
|
608
|
+
if (document.getElementById(OVERLAY_ID)) return;
|
|
609
|
+
const el = document.createElement("div");
|
|
610
|
+
el.id = OVERLAY_ID;
|
|
611
|
+
el.setAttribute("aria-hidden", "true");
|
|
612
|
+
el.style.cssText = [
|
|
613
|
+
"position:fixed",
|
|
614
|
+
"inset:0",
|
|
615
|
+
`z-index:2147483646`,
|
|
616
|
+
`background:rgba(255,255,255,${cfg.opacity})`,
|
|
617
|
+
"display:flex",
|
|
618
|
+
"flex-direction:column",
|
|
619
|
+
"align-items:center",
|
|
620
|
+
"justify-content:center",
|
|
621
|
+
"pointer-events:all",
|
|
622
|
+
"user-select:none",
|
|
623
|
+
"backdrop-filter:blur(1px)",
|
|
624
|
+
"-webkit-backdrop-filter:blur(1px)"
|
|
625
|
+
].join(";");
|
|
626
|
+
el.innerHTML = `
|
|
627
|
+
<img src="${logoSrc}" alt="" style="width:48px;height:48px;animation:__ctx_spin 1.5s linear infinite;" />
|
|
628
|
+
<p style="margin:12px 0 0;font:14px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#666;">${message}</p>
|
|
629
|
+
${cfg.allowCancel ? '<button id="__ctx_agent_lock_cancel__" style="margin-top:16px;background:none;border:none;color:#6366f1;font:13px -apple-system,BlinkMacSystemFont,sans-serif;cursor:pointer;text-decoration:underline;padding:4px 8px;">\u53D6\u6D88</button>' : ""}
|
|
630
|
+
`;
|
|
631
|
+
if (!document.getElementById("__ctx_agent_lock_style__")) {
|
|
632
|
+
const style = document.createElement("style");
|
|
633
|
+
style.id = "__ctx_agent_lock_style__";
|
|
634
|
+
style.textContent = "@keyframes __ctx_spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}";
|
|
635
|
+
document.head.appendChild(style);
|
|
636
|
+
}
|
|
637
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
638
|
+
el.addEventListener(evt, blockEvent, { capture: true, passive: false });
|
|
639
|
+
}
|
|
640
|
+
if (cfg.allowCancel) {
|
|
641
|
+
queueMicrotask(() => {
|
|
642
|
+
const btn = el.querySelector("#__ctx_agent_lock_cancel__");
|
|
643
|
+
if (btn) {
|
|
644
|
+
btn.addEventListener("click", (e) => {
|
|
645
|
+
e.stopPropagation();
|
|
646
|
+
handleCancel();
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
document.body.appendChild(el);
|
|
652
|
+
overlayEl = el;
|
|
653
|
+
}
|
|
654
|
+
function removeOverlay() {
|
|
655
|
+
if (overlayEl) {
|
|
656
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
657
|
+
overlayEl.removeEventListener(evt, blockEvent, { capture: true });
|
|
658
|
+
}
|
|
659
|
+
overlayEl.remove();
|
|
660
|
+
overlayEl = null;
|
|
661
|
+
}
|
|
662
|
+
const style = typeof document !== "undefined" ? document.getElementById("__ctx_agent_lock_style__") : null;
|
|
663
|
+
if (style) style.remove();
|
|
664
|
+
}
|
|
665
|
+
function startTimeout(ms, requestId) {
|
|
666
|
+
clearTimeoutTimer();
|
|
667
|
+
if (ms <= 0) return;
|
|
668
|
+
timeoutTimer = setTimeout(() => {
|
|
669
|
+
console.warn("[ChatableX] Agent lock timeout \u2014 auto-unlocking");
|
|
670
|
+
forceUnlock();
|
|
671
|
+
emit("timeout", { requestId });
|
|
672
|
+
}, ms);
|
|
673
|
+
}
|
|
674
|
+
function clearTimeoutTimer() {
|
|
675
|
+
if (timeoutTimer !== null) {
|
|
676
|
+
clearTimeout(timeoutTimer);
|
|
677
|
+
timeoutTimer = null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function clearDebounceTimer() {
|
|
681
|
+
if (debounceTimer !== null) {
|
|
682
|
+
clearTimeout(debounceTimer);
|
|
683
|
+
debounceTimer = null;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function doLock(message, timeout, requestId) {
|
|
687
|
+
if (locked) return;
|
|
688
|
+
locked = true;
|
|
689
|
+
currentMessage = message;
|
|
690
|
+
if (cfg.mode === "overlay") {
|
|
691
|
+
injectOverlay(currentMessage);
|
|
692
|
+
}
|
|
693
|
+
startTimeout(timeout, requestId);
|
|
694
|
+
emit("lock", { requestId });
|
|
695
|
+
}
|
|
696
|
+
function forceUnlock(requestId) {
|
|
697
|
+
if (!locked) return;
|
|
698
|
+
locked = false;
|
|
699
|
+
lockCount = 0;
|
|
700
|
+
clearTimeoutTimer();
|
|
701
|
+
clearDebounceTimer();
|
|
702
|
+
if (cfg.mode === "overlay") {
|
|
703
|
+
removeOverlay();
|
|
704
|
+
}
|
|
705
|
+
emit("unlock", { requestId });
|
|
706
|
+
}
|
|
707
|
+
function handleCancel() {
|
|
708
|
+
const rid = void 0;
|
|
709
|
+
forceUnlock(rid);
|
|
710
|
+
emit("cancel", { requestId: rid });
|
|
711
|
+
}
|
|
712
|
+
function lock(opts) {
|
|
713
|
+
if (!cfg.enabled) return;
|
|
714
|
+
const msg = opts?.message ?? cfg.message;
|
|
715
|
+
const timeout = opts?.timeout ?? cfg.timeout;
|
|
716
|
+
doLock(msg, timeout);
|
|
717
|
+
}
|
|
718
|
+
function unlock() {
|
|
719
|
+
forceUnlock();
|
|
720
|
+
}
|
|
721
|
+
function isLocked() {
|
|
722
|
+
return locked;
|
|
723
|
+
}
|
|
724
|
+
function on(event, handler) {
|
|
725
|
+
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
726
|
+
listeners.get(event).add(handler);
|
|
727
|
+
return () => off(event, handler);
|
|
728
|
+
}
|
|
729
|
+
function off(event, handler) {
|
|
730
|
+
listeners.get(event)?.delete(handler);
|
|
731
|
+
}
|
|
732
|
+
function _autoLock(requestId) {
|
|
733
|
+
if (!cfg.enabled) return;
|
|
734
|
+
clearDebounceTimer();
|
|
735
|
+
lockCount++;
|
|
736
|
+
if (!locked) {
|
|
737
|
+
doLock(cfg.message, cfg.timeout, requestId);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function _autoUnlock(requestId) {
|
|
741
|
+
if (!cfg.enabled) return;
|
|
742
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
743
|
+
if (lockCount === 0) {
|
|
744
|
+
clearDebounceTimer();
|
|
745
|
+
debounceTimer = setTimeout(() => {
|
|
746
|
+
if (lockCount === 0) {
|
|
747
|
+
forceUnlock(requestId);
|
|
748
|
+
}
|
|
749
|
+
}, cfg.debounceUnlock);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
function _destroy() {
|
|
753
|
+
forceUnlock();
|
|
754
|
+
listeners.clear();
|
|
755
|
+
}
|
|
756
|
+
return { lock, unlock, isLocked, on, off, _autoLock, _autoUnlock, _destroy };
|
|
757
|
+
}
|
|
758
|
+
|
|
542
759
|
// package.json
|
|
543
760
|
var package_default = {
|
|
544
761
|
name: "chatablex-web-sdk",
|
|
545
|
-
version: "1.0.
|
|
762
|
+
version: "1.0.34",
|
|
546
763
|
description: "ChatableX Web SDK for AI App WebUI development. Provides bridge communication with the ChatableX Flutter client.",
|
|
547
764
|
main: "dist/index.js",
|
|
548
765
|
module: "dist/index.mjs",
|
|
@@ -561,8 +778,8 @@ var package_default = {
|
|
|
561
778
|
"README.zh-CN.md"
|
|
562
779
|
],
|
|
563
780
|
scripts: {
|
|
564
|
-
build: "tsup
|
|
565
|
-
dev: "tsup
|
|
781
|
+
build: "tsup",
|
|
782
|
+
dev: "tsup --watch",
|
|
566
783
|
typecheck: "tsc --noEmit",
|
|
567
784
|
test: "vitest run",
|
|
568
785
|
"test:watch": "vitest",
|
|
@@ -632,7 +849,8 @@ var ChatableX = {
|
|
|
632
849
|
} catch {
|
|
633
850
|
if (debug) console.warn("[ChatableX] sdk_init handshake failed, continuing with defaults");
|
|
634
851
|
}
|
|
635
|
-
const
|
|
852
|
+
const agentLockModule = createAgentLockModule(bridge, config.agentLock);
|
|
853
|
+
const toolModule = createToolModule(bridge, config.appId, agentLockModule);
|
|
636
854
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
637
855
|
const authModule = createAuthModule(bridge);
|
|
638
856
|
const sdk = {
|
|
@@ -648,7 +866,8 @@ var ChatableX = {
|
|
|
648
866
|
appId: config.appId,
|
|
649
867
|
auth: authModule,
|
|
650
868
|
apiBaseUrl: config.apiBaseUrl
|
|
651
|
-
})
|
|
869
|
+
}),
|
|
870
|
+
agentLock: agentLockModule
|
|
652
871
|
};
|
|
653
872
|
window.ChatableX = sdk;
|
|
654
873
|
_instance = sdk;
|
package/dist/index.mjs
CHANGED
|
@@ -132,7 +132,7 @@ var Bridge = class {
|
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
// src/modules/tool.ts
|
|
135
|
-
function createToolModule(bridge, appId) {
|
|
135
|
+
function createToolModule(bridge, appId, agentLock) {
|
|
136
136
|
let _info = { id: appId, name: appId, version: "1.0.0", description: "" };
|
|
137
137
|
let _handler = null;
|
|
138
138
|
const dispatch = async (params) => {
|
|
@@ -150,6 +150,7 @@ function createToolModule(bridge, appId) {
|
|
|
150
150
|
bridge.addEventListener("toolExecution", async (data) => {
|
|
151
151
|
const params = data;
|
|
152
152
|
const requestId = params._requestId;
|
|
153
|
+
if (requestId && agentLock) agentLock._autoLock(requestId);
|
|
153
154
|
const result = await dispatch(params);
|
|
154
155
|
if (requestId && window.ChatableXBridge) {
|
|
155
156
|
window.ChatableXBridge.postMessage(JSON.stringify({
|
|
@@ -157,6 +158,7 @@ function createToolModule(bridge, appId) {
|
|
|
157
158
|
params: { _requestId: requestId, ...result }
|
|
158
159
|
}));
|
|
159
160
|
}
|
|
161
|
+
if (requestId && agentLock) agentLock._autoUnlock(requestId);
|
|
160
162
|
});
|
|
161
163
|
return {
|
|
162
164
|
getInfo() {
|
|
@@ -507,10 +509,225 @@ function createCloudModule(bridge, deps) {
|
|
|
507
509
|
};
|
|
508
510
|
}
|
|
509
511
|
|
|
512
|
+
// src/assets/bee.png
|
|
513
|
+
var bee_default = "data:image/png;base64,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";
|
|
514
|
+
|
|
515
|
+
// src/modules/agentLock.ts
|
|
516
|
+
var DEFAULT_CONFIG = {
|
|
517
|
+
enabled: true,
|
|
518
|
+
mode: "overlay",
|
|
519
|
+
logoUrl: "",
|
|
520
|
+
message: "Agent \u6B63\u5728\u64CD\u4F5C\uFF0C\u8BF7\u7A0D\u5019\u2026",
|
|
521
|
+
allowCancel: true,
|
|
522
|
+
opacity: 0.3,
|
|
523
|
+
timeout: 3e4,
|
|
524
|
+
debounceUnlock: 200
|
|
525
|
+
};
|
|
526
|
+
var OVERLAY_ID = "__chatablex_agent_lock_overlay__";
|
|
527
|
+
var BLOCKED_EVENTS = [
|
|
528
|
+
"mousedown",
|
|
529
|
+
"mouseup",
|
|
530
|
+
"click",
|
|
531
|
+
"dblclick",
|
|
532
|
+
"contextmenu",
|
|
533
|
+
"keydown",
|
|
534
|
+
"keyup",
|
|
535
|
+
"keypress",
|
|
536
|
+
"touchstart",
|
|
537
|
+
"touchmove",
|
|
538
|
+
"touchend",
|
|
539
|
+
"wheel",
|
|
540
|
+
"scroll",
|
|
541
|
+
"pointerdown",
|
|
542
|
+
"pointerup"
|
|
543
|
+
];
|
|
544
|
+
var CANCEL_BTN_ID = "__ctx_agent_lock_cancel__";
|
|
545
|
+
function blockEvent(e) {
|
|
546
|
+
const target = e.target;
|
|
547
|
+
if (target?.id === CANCEL_BTN_ID) return;
|
|
548
|
+
e.stopPropagation();
|
|
549
|
+
e.preventDefault();
|
|
550
|
+
}
|
|
551
|
+
function createAgentLockModule(_bridge, userConfig = {}) {
|
|
552
|
+
const cfg = { ...DEFAULT_CONFIG, ...userConfig };
|
|
553
|
+
const logoSrc = cfg.logoUrl || bee_default;
|
|
554
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
555
|
+
let overlayEl = null;
|
|
556
|
+
let locked = false;
|
|
557
|
+
let lockCount = 0;
|
|
558
|
+
let timeoutTimer = null;
|
|
559
|
+
let debounceTimer = null;
|
|
560
|
+
let currentMessage = cfg.message;
|
|
561
|
+
function emit(event, data = {}) {
|
|
562
|
+
const payload = { timestamp: Date.now(), ...data };
|
|
563
|
+
const handlers = listeners.get(event);
|
|
564
|
+
if (handlers) {
|
|
565
|
+
for (const fn of handlers) {
|
|
566
|
+
try {
|
|
567
|
+
fn(payload);
|
|
568
|
+
} catch (e) {
|
|
569
|
+
console.error("[ChatableX AgentLock] event handler error:", e);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function injectOverlay(message) {
|
|
575
|
+
if (typeof document === "undefined") return;
|
|
576
|
+
if (document.getElementById(OVERLAY_ID)) return;
|
|
577
|
+
const el = document.createElement("div");
|
|
578
|
+
el.id = OVERLAY_ID;
|
|
579
|
+
el.setAttribute("aria-hidden", "true");
|
|
580
|
+
el.style.cssText = [
|
|
581
|
+
"position:fixed",
|
|
582
|
+
"inset:0",
|
|
583
|
+
`z-index:2147483646`,
|
|
584
|
+
`background:rgba(255,255,255,${cfg.opacity})`,
|
|
585
|
+
"display:flex",
|
|
586
|
+
"flex-direction:column",
|
|
587
|
+
"align-items:center",
|
|
588
|
+
"justify-content:center",
|
|
589
|
+
"pointer-events:all",
|
|
590
|
+
"user-select:none",
|
|
591
|
+
"backdrop-filter:blur(1px)",
|
|
592
|
+
"-webkit-backdrop-filter:blur(1px)"
|
|
593
|
+
].join(";");
|
|
594
|
+
el.innerHTML = `
|
|
595
|
+
<img src="${logoSrc}" alt="" style="width:48px;height:48px;animation:__ctx_spin 1.5s linear infinite;" />
|
|
596
|
+
<p style="margin:12px 0 0;font:14px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#666;">${message}</p>
|
|
597
|
+
${cfg.allowCancel ? '<button id="__ctx_agent_lock_cancel__" style="margin-top:16px;background:none;border:none;color:#6366f1;font:13px -apple-system,BlinkMacSystemFont,sans-serif;cursor:pointer;text-decoration:underline;padding:4px 8px;">\u53D6\u6D88</button>' : ""}
|
|
598
|
+
`;
|
|
599
|
+
if (!document.getElementById("__ctx_agent_lock_style__")) {
|
|
600
|
+
const style = document.createElement("style");
|
|
601
|
+
style.id = "__ctx_agent_lock_style__";
|
|
602
|
+
style.textContent = "@keyframes __ctx_spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}";
|
|
603
|
+
document.head.appendChild(style);
|
|
604
|
+
}
|
|
605
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
606
|
+
el.addEventListener(evt, blockEvent, { capture: true, passive: false });
|
|
607
|
+
}
|
|
608
|
+
if (cfg.allowCancel) {
|
|
609
|
+
queueMicrotask(() => {
|
|
610
|
+
const btn = el.querySelector("#__ctx_agent_lock_cancel__");
|
|
611
|
+
if (btn) {
|
|
612
|
+
btn.addEventListener("click", (e) => {
|
|
613
|
+
e.stopPropagation();
|
|
614
|
+
handleCancel();
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
document.body.appendChild(el);
|
|
620
|
+
overlayEl = el;
|
|
621
|
+
}
|
|
622
|
+
function removeOverlay() {
|
|
623
|
+
if (overlayEl) {
|
|
624
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
625
|
+
overlayEl.removeEventListener(evt, blockEvent, { capture: true });
|
|
626
|
+
}
|
|
627
|
+
overlayEl.remove();
|
|
628
|
+
overlayEl = null;
|
|
629
|
+
}
|
|
630
|
+
const style = typeof document !== "undefined" ? document.getElementById("__ctx_agent_lock_style__") : null;
|
|
631
|
+
if (style) style.remove();
|
|
632
|
+
}
|
|
633
|
+
function startTimeout(ms, requestId) {
|
|
634
|
+
clearTimeoutTimer();
|
|
635
|
+
if (ms <= 0) return;
|
|
636
|
+
timeoutTimer = setTimeout(() => {
|
|
637
|
+
console.warn("[ChatableX] Agent lock timeout \u2014 auto-unlocking");
|
|
638
|
+
forceUnlock();
|
|
639
|
+
emit("timeout", { requestId });
|
|
640
|
+
}, ms);
|
|
641
|
+
}
|
|
642
|
+
function clearTimeoutTimer() {
|
|
643
|
+
if (timeoutTimer !== null) {
|
|
644
|
+
clearTimeout(timeoutTimer);
|
|
645
|
+
timeoutTimer = null;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function clearDebounceTimer() {
|
|
649
|
+
if (debounceTimer !== null) {
|
|
650
|
+
clearTimeout(debounceTimer);
|
|
651
|
+
debounceTimer = null;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function doLock(message, timeout, requestId) {
|
|
655
|
+
if (locked) return;
|
|
656
|
+
locked = true;
|
|
657
|
+
currentMessage = message;
|
|
658
|
+
if (cfg.mode === "overlay") {
|
|
659
|
+
injectOverlay(currentMessage);
|
|
660
|
+
}
|
|
661
|
+
startTimeout(timeout, requestId);
|
|
662
|
+
emit("lock", { requestId });
|
|
663
|
+
}
|
|
664
|
+
function forceUnlock(requestId) {
|
|
665
|
+
if (!locked) return;
|
|
666
|
+
locked = false;
|
|
667
|
+
lockCount = 0;
|
|
668
|
+
clearTimeoutTimer();
|
|
669
|
+
clearDebounceTimer();
|
|
670
|
+
if (cfg.mode === "overlay") {
|
|
671
|
+
removeOverlay();
|
|
672
|
+
}
|
|
673
|
+
emit("unlock", { requestId });
|
|
674
|
+
}
|
|
675
|
+
function handleCancel() {
|
|
676
|
+
const rid = void 0;
|
|
677
|
+
forceUnlock(rid);
|
|
678
|
+
emit("cancel", { requestId: rid });
|
|
679
|
+
}
|
|
680
|
+
function lock(opts) {
|
|
681
|
+
if (!cfg.enabled) return;
|
|
682
|
+
const msg = opts?.message ?? cfg.message;
|
|
683
|
+
const timeout = opts?.timeout ?? cfg.timeout;
|
|
684
|
+
doLock(msg, timeout);
|
|
685
|
+
}
|
|
686
|
+
function unlock() {
|
|
687
|
+
forceUnlock();
|
|
688
|
+
}
|
|
689
|
+
function isLocked() {
|
|
690
|
+
return locked;
|
|
691
|
+
}
|
|
692
|
+
function on(event, handler) {
|
|
693
|
+
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
694
|
+
listeners.get(event).add(handler);
|
|
695
|
+
return () => off(event, handler);
|
|
696
|
+
}
|
|
697
|
+
function off(event, handler) {
|
|
698
|
+
listeners.get(event)?.delete(handler);
|
|
699
|
+
}
|
|
700
|
+
function _autoLock(requestId) {
|
|
701
|
+
if (!cfg.enabled) return;
|
|
702
|
+
clearDebounceTimer();
|
|
703
|
+
lockCount++;
|
|
704
|
+
if (!locked) {
|
|
705
|
+
doLock(cfg.message, cfg.timeout, requestId);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function _autoUnlock(requestId) {
|
|
709
|
+
if (!cfg.enabled) return;
|
|
710
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
711
|
+
if (lockCount === 0) {
|
|
712
|
+
clearDebounceTimer();
|
|
713
|
+
debounceTimer = setTimeout(() => {
|
|
714
|
+
if (lockCount === 0) {
|
|
715
|
+
forceUnlock(requestId);
|
|
716
|
+
}
|
|
717
|
+
}, cfg.debounceUnlock);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function _destroy() {
|
|
721
|
+
forceUnlock();
|
|
722
|
+
listeners.clear();
|
|
723
|
+
}
|
|
724
|
+
return { lock, unlock, isLocked, on, off, _autoLock, _autoUnlock, _destroy };
|
|
725
|
+
}
|
|
726
|
+
|
|
510
727
|
// package.json
|
|
511
728
|
var package_default = {
|
|
512
729
|
name: "chatablex-web-sdk",
|
|
513
|
-
version: "1.0.
|
|
730
|
+
version: "1.0.34",
|
|
514
731
|
description: "ChatableX Web SDK for AI App WebUI development. Provides bridge communication with the ChatableX Flutter client.",
|
|
515
732
|
main: "dist/index.js",
|
|
516
733
|
module: "dist/index.mjs",
|
|
@@ -529,8 +746,8 @@ var package_default = {
|
|
|
529
746
|
"README.zh-CN.md"
|
|
530
747
|
],
|
|
531
748
|
scripts: {
|
|
532
|
-
build: "tsup
|
|
533
|
-
dev: "tsup
|
|
749
|
+
build: "tsup",
|
|
750
|
+
dev: "tsup --watch",
|
|
534
751
|
typecheck: "tsc --noEmit",
|
|
535
752
|
test: "vitest run",
|
|
536
753
|
"test:watch": "vitest",
|
|
@@ -600,7 +817,8 @@ var ChatableX = {
|
|
|
600
817
|
} catch {
|
|
601
818
|
if (debug) console.warn("[ChatableX] sdk_init handshake failed, continuing with defaults");
|
|
602
819
|
}
|
|
603
|
-
const
|
|
820
|
+
const agentLockModule = createAgentLockModule(bridge, config.agentLock);
|
|
821
|
+
const toolModule = createToolModule(bridge, config.appId, agentLockModule);
|
|
604
822
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
605
823
|
const authModule = createAuthModule(bridge);
|
|
606
824
|
const sdk = {
|
|
@@ -616,7 +834,8 @@ var ChatableX = {
|
|
|
616
834
|
appId: config.appId,
|
|
617
835
|
auth: authModule,
|
|
618
836
|
apiBaseUrl: config.apiBaseUrl
|
|
619
|
-
})
|
|
837
|
+
}),
|
|
838
|
+
agentLock: agentLockModule
|
|
620
839
|
};
|
|
621
840
|
window.ChatableX = sdk;
|
|
622
841
|
_instance = sdk;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chatablex-web-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"description": "ChatableX Web SDK for AI App WebUI development. Provides bridge communication with the ChatableX Flutter client.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"README.zh-CN.md"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "tsup
|
|
23
|
-
"dev": "tsup
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
25
|
"test": "vitest run",
|
|
26
26
|
"test:watch": "vitest",
|
|
Binary file
|
package/src/assets.d.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { createToolsModule } from './modules/tools';
|
|
|
28
28
|
import { createPlatformModule } from './modules/platform';
|
|
29
29
|
import { createAuthModule } from './modules/auth';
|
|
30
30
|
import { createCloudModule } from './modules/cloud';
|
|
31
|
+
import { createAgentLockModule } from './modules/agentLock';
|
|
31
32
|
import type { ChatableXSDK, ChatableXInitConfig, ToolInfo } from './types';
|
|
32
33
|
import pkg from '../package.json';
|
|
33
34
|
|
|
@@ -77,7 +78,9 @@ export const ChatableX = {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// 4. Create modules
|
|
80
|
-
const
|
|
81
|
+
const agentLockModule = createAgentLockModule(bridge, config.agentLock);
|
|
82
|
+
|
|
83
|
+
const toolModule = createToolModule(bridge, config.appId, agentLockModule);
|
|
81
84
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
82
85
|
|
|
83
86
|
const authModule = createAuthModule(bridge);
|
|
@@ -96,6 +99,7 @@ export const ChatableX = {
|
|
|
96
99
|
auth: authModule,
|
|
97
100
|
apiBaseUrl: config.apiBaseUrl,
|
|
98
101
|
}),
|
|
102
|
+
agentLock: agentLockModule,
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
// Expose on window for debugging / Flutter interop
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { Bridge } from '../bridge';
|
|
2
|
+
import type {
|
|
3
|
+
AgentLockConfig,
|
|
4
|
+
AgentLockEventType,
|
|
5
|
+
AgentLockEventData,
|
|
6
|
+
AgentLockEventHandler,
|
|
7
|
+
ChatableXAgentLock,
|
|
8
|
+
} from '../types';
|
|
9
|
+
import beeLogo from '../assets/bee.png';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CONFIG: Required<AgentLockConfig> = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
mode: 'overlay',
|
|
14
|
+
logoUrl: '',
|
|
15
|
+
message: 'Agent 正在操作,请稍候…',
|
|
16
|
+
allowCancel: true,
|
|
17
|
+
opacity: 0.3,
|
|
18
|
+
timeout: 30_000,
|
|
19
|
+
debounceUnlock: 200,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const OVERLAY_ID = '__chatablex_agent_lock_overlay__';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Blocked event types — we intercept these on the overlay to prevent user
|
|
26
|
+
* interaction from reaching the app underneath.
|
|
27
|
+
*/
|
|
28
|
+
const BLOCKED_EVENTS: string[] = [
|
|
29
|
+
'mousedown', 'mouseup', 'click', 'dblclick', 'contextmenu',
|
|
30
|
+
'keydown', 'keyup', 'keypress',
|
|
31
|
+
'touchstart', 'touchmove', 'touchend',
|
|
32
|
+
'wheel', 'scroll',
|
|
33
|
+
'pointerdown', 'pointerup',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const CANCEL_BTN_ID = '__ctx_agent_lock_cancel__';
|
|
37
|
+
|
|
38
|
+
function blockEvent(e: Event): void {
|
|
39
|
+
const target = e.target as HTMLElement | null;
|
|
40
|
+
if (target?.id === CANCEL_BTN_ID) return;
|
|
41
|
+
e.stopPropagation();
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AgentLockModule extends ChatableXAgentLock {
|
|
46
|
+
/**
|
|
47
|
+
* @internal — called by tool module to lock before dispatch and schedule
|
|
48
|
+
* unlock after result. Uses ref-counting to support consecutive tools.
|
|
49
|
+
*/
|
|
50
|
+
_autoLock(requestId: string): void;
|
|
51
|
+
/** @internal */
|
|
52
|
+
_autoUnlock(requestId: string): void;
|
|
53
|
+
/** @internal */
|
|
54
|
+
_destroy(): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createAgentLockModule(
|
|
58
|
+
_bridge: Bridge,
|
|
59
|
+
userConfig: AgentLockConfig = {},
|
|
60
|
+
): AgentLockModule {
|
|
61
|
+
const cfg: Required<AgentLockConfig> = { ...DEFAULT_CONFIG, ...userConfig };
|
|
62
|
+
const logoSrc = cfg.logoUrl || beeLogo;
|
|
63
|
+
|
|
64
|
+
const listeners = new Map<AgentLockEventType, Set<AgentLockEventHandler>>();
|
|
65
|
+
let overlayEl: HTMLDivElement | null = null;
|
|
66
|
+
let locked = false;
|
|
67
|
+
let lockCount = 0;
|
|
68
|
+
let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
69
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
70
|
+
let currentMessage = cfg.message;
|
|
71
|
+
|
|
72
|
+
function emit(event: AgentLockEventType, data: Partial<AgentLockEventData> = {}): void {
|
|
73
|
+
const payload: AgentLockEventData = { timestamp: Date.now(), ...data };
|
|
74
|
+
const handlers = listeners.get(event);
|
|
75
|
+
if (handlers) {
|
|
76
|
+
for (const fn of handlers) {
|
|
77
|
+
try { fn(payload); } catch (e) { console.error('[ChatableX AgentLock] event handler error:', e); }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function injectOverlay(message: string): void {
|
|
83
|
+
if (typeof document === 'undefined') return;
|
|
84
|
+
if (document.getElementById(OVERLAY_ID)) return;
|
|
85
|
+
|
|
86
|
+
const el = document.createElement('div');
|
|
87
|
+
el.id = OVERLAY_ID;
|
|
88
|
+
el.setAttribute('aria-hidden', 'true');
|
|
89
|
+
el.style.cssText = [
|
|
90
|
+
'position:fixed', 'inset:0', `z-index:2147483646`,
|
|
91
|
+
`background:rgba(255,255,255,${cfg.opacity})`,
|
|
92
|
+
'display:flex', 'flex-direction:column',
|
|
93
|
+
'align-items:center', 'justify-content:center',
|
|
94
|
+
'pointer-events:all', 'user-select:none',
|
|
95
|
+
'backdrop-filter:blur(1px)', '-webkit-backdrop-filter:blur(1px)',
|
|
96
|
+
].join(';');
|
|
97
|
+
|
|
98
|
+
el.innerHTML = `
|
|
99
|
+
<img src="${logoSrc}" alt="" style="width:48px;height:48px;animation:__ctx_spin 1.5s linear infinite;" />
|
|
100
|
+
<p style="margin:12px 0 0;font:14px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#666;">${message}</p>
|
|
101
|
+
${cfg.allowCancel ? '<button id="__ctx_agent_lock_cancel__" style="margin-top:16px;background:none;border:none;color:#6366f1;font:13px -apple-system,BlinkMacSystemFont,sans-serif;cursor:pointer;text-decoration:underline;padding:4px 8px;">取消</button>' : ''}
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
if (!document.getElementById('__ctx_agent_lock_style__')) {
|
|
105
|
+
const style = document.createElement('style');
|
|
106
|
+
style.id = '__ctx_agent_lock_style__';
|
|
107
|
+
style.textContent = '@keyframes __ctx_spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}';
|
|
108
|
+
document.head.appendChild(style);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
112
|
+
el.addEventListener(evt, blockEvent, { capture: true, passive: false });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (cfg.allowCancel) {
|
|
116
|
+
// Defer binding so the DOM is ready
|
|
117
|
+
queueMicrotask(() => {
|
|
118
|
+
const btn = el.querySelector('#__ctx_agent_lock_cancel__');
|
|
119
|
+
if (btn) {
|
|
120
|
+
btn.addEventListener('click', (e) => {
|
|
121
|
+
e.stopPropagation();
|
|
122
|
+
handleCancel();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
document.body.appendChild(el);
|
|
129
|
+
overlayEl = el;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function removeOverlay(): void {
|
|
133
|
+
if (overlayEl) {
|
|
134
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
135
|
+
overlayEl.removeEventListener(evt, blockEvent, { capture: true } as EventListenerOptions);
|
|
136
|
+
}
|
|
137
|
+
overlayEl.remove();
|
|
138
|
+
overlayEl = null;
|
|
139
|
+
}
|
|
140
|
+
const style = typeof document !== 'undefined' ? document.getElementById('__ctx_agent_lock_style__') : null;
|
|
141
|
+
if (style) style.remove();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function startTimeout(ms: number, requestId?: string): void {
|
|
145
|
+
clearTimeoutTimer();
|
|
146
|
+
if (ms <= 0) return;
|
|
147
|
+
timeoutTimer = setTimeout(() => {
|
|
148
|
+
console.warn('[ChatableX] Agent lock timeout — auto-unlocking');
|
|
149
|
+
forceUnlock();
|
|
150
|
+
emit('timeout', { requestId });
|
|
151
|
+
}, ms);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function clearTimeoutTimer(): void {
|
|
155
|
+
if (timeoutTimer !== null) {
|
|
156
|
+
clearTimeout(timeoutTimer);
|
|
157
|
+
timeoutTimer = null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function clearDebounceTimer(): void {
|
|
162
|
+
if (debounceTimer !== null) {
|
|
163
|
+
clearTimeout(debounceTimer);
|
|
164
|
+
debounceTimer = null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function doLock(message: string, timeout: number, requestId?: string): void {
|
|
169
|
+
if (locked) return;
|
|
170
|
+
locked = true;
|
|
171
|
+
currentMessage = message;
|
|
172
|
+
|
|
173
|
+
if (cfg.mode === 'overlay') {
|
|
174
|
+
injectOverlay(currentMessage);
|
|
175
|
+
}
|
|
176
|
+
startTimeout(timeout, requestId);
|
|
177
|
+
emit('lock', { requestId });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function forceUnlock(requestId?: string): void {
|
|
181
|
+
if (!locked) return;
|
|
182
|
+
locked = false;
|
|
183
|
+
lockCount = 0;
|
|
184
|
+
clearTimeoutTimer();
|
|
185
|
+
clearDebounceTimer();
|
|
186
|
+
if (cfg.mode === 'overlay') {
|
|
187
|
+
removeOverlay();
|
|
188
|
+
}
|
|
189
|
+
emit('unlock', { requestId });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function handleCancel(): void {
|
|
193
|
+
const rid = undefined; // auto mode tracks this externally
|
|
194
|
+
forceUnlock(rid);
|
|
195
|
+
emit('cancel', { requestId: rid });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Public API -----------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
function lock(opts?: { message?: string; timeout?: number }): void {
|
|
201
|
+
if (!cfg.enabled) return;
|
|
202
|
+
const msg = opts?.message ?? cfg.message;
|
|
203
|
+
const timeout = opts?.timeout ?? cfg.timeout;
|
|
204
|
+
doLock(msg, timeout);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function unlock(): void {
|
|
208
|
+
forceUnlock();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isLocked(): boolean {
|
|
212
|
+
return locked;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function on(event: AgentLockEventType, handler: AgentLockEventHandler): () => void {
|
|
216
|
+
if (!listeners.has(event)) listeners.set(event, new Set());
|
|
217
|
+
listeners.get(event)!.add(handler);
|
|
218
|
+
return () => off(event, handler);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function off(event: AgentLockEventType, handler: AgentLockEventHandler): void {
|
|
222
|
+
listeners.get(event)?.delete(handler);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Internal auto-mode API (called by tool module) ----------------------------
|
|
226
|
+
|
|
227
|
+
function _autoLock(requestId: string): void {
|
|
228
|
+
if (!cfg.enabled) return;
|
|
229
|
+
clearDebounceTimer();
|
|
230
|
+
lockCount++;
|
|
231
|
+
if (!locked) {
|
|
232
|
+
doLock(cfg.message, cfg.timeout, requestId);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function _autoUnlock(requestId: string): void {
|
|
237
|
+
if (!cfg.enabled) return;
|
|
238
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
239
|
+
if (lockCount === 0) {
|
|
240
|
+
clearDebounceTimer();
|
|
241
|
+
debounceTimer = setTimeout(() => {
|
|
242
|
+
if (lockCount === 0) {
|
|
243
|
+
forceUnlock(requestId);
|
|
244
|
+
}
|
|
245
|
+
}, cfg.debounceUnlock);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function _destroy(): void {
|
|
250
|
+
forceUnlock();
|
|
251
|
+
listeners.clear();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { lock, unlock, isLocked, on, off, _autoLock, _autoUnlock, _destroy };
|
|
255
|
+
}
|
package/src/modules/tool.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { Bridge } from '../bridge';
|
|
2
2
|
import type { ToolInfo, ToolExecuteHandler, ChatableXToolModule } from '../types';
|
|
3
|
+
import type { AgentLockModule } from './agentLock';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Creates the `sdk.tool` module — the primary interface for LLM-driven
|
|
6
7
|
* tool execution in a ChatableX WebUI app.
|
|
7
8
|
*/
|
|
8
|
-
export function createToolModule(
|
|
9
|
+
export function createToolModule(
|
|
10
|
+
bridge: Bridge,
|
|
11
|
+
appId: string,
|
|
12
|
+
agentLock?: AgentLockModule,
|
|
13
|
+
): ChatableXToolModule & { _setInfo(info: Partial<ToolInfo>): void } {
|
|
9
14
|
let _info: ToolInfo = { id: appId, name: appId, version: '1.0.0', description: '' };
|
|
10
15
|
let _handler: ToolExecuteHandler | null = null;
|
|
11
16
|
|
|
@@ -30,13 +35,19 @@ export function createToolModule(bridge: Bridge, appId: string): ChatableXToolMo
|
|
|
30
35
|
bridge.addEventListener('toolExecution', async (data) => {
|
|
31
36
|
const params = data as Record<string, unknown>;
|
|
32
37
|
const requestId = params._requestId as string | undefined;
|
|
38
|
+
|
|
39
|
+
if (requestId && agentLock) agentLock._autoLock(requestId);
|
|
40
|
+
|
|
33
41
|
const result = await dispatch(params);
|
|
42
|
+
|
|
34
43
|
if (requestId && window.ChatableXBridge) {
|
|
35
44
|
window.ChatableXBridge.postMessage(JSON.stringify({
|
|
36
45
|
method: 'tool.executeResult',
|
|
37
46
|
params: { _requestId: requestId, ...result },
|
|
38
47
|
}));
|
|
39
48
|
}
|
|
49
|
+
|
|
50
|
+
if (requestId && agentLock) agentLock._autoUnlock(requestId);
|
|
40
51
|
});
|
|
41
52
|
|
|
42
53
|
return {
|
package/src/types.ts
CHANGED
|
@@ -169,6 +169,8 @@ export interface ChatableXInitConfig {
|
|
|
169
169
|
* calls reject with a clear error.
|
|
170
170
|
*/
|
|
171
171
|
apiBaseUrl?: string;
|
|
172
|
+
/** Agent lock configuration — blocks user input during tool execution. */
|
|
173
|
+
agentLock?: AgentLockConfig;
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
// ---------------------------------------------------------------------------
|
|
@@ -332,6 +334,54 @@ export interface ChatableXCloud {
|
|
|
332
334
|
usage(): Promise<CloudUsage>;
|
|
333
335
|
}
|
|
334
336
|
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// Agent Lock
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
export interface AgentLockConfig {
|
|
342
|
+
/** Enable the agent lock feature (default: true). */
|
|
343
|
+
enabled?: boolean;
|
|
344
|
+
/**
|
|
345
|
+
* `"overlay"` — SDK renders a built-in transparent overlay (default).
|
|
346
|
+
* `"events-only"` — SDK only emits lock/unlock events; no overlay injected.
|
|
347
|
+
*/
|
|
348
|
+
mode?: 'overlay' | 'events-only';
|
|
349
|
+
/** URL of the logo displayed in the overlay centre. Defaults to built-in Chatablex SVG. */
|
|
350
|
+
logoUrl?: string;
|
|
351
|
+
/** Message shown below the logo (default: "Agent 正在操作,请稍候…"). */
|
|
352
|
+
message?: string;
|
|
353
|
+
/** Show a cancel button on the overlay (default: true). */
|
|
354
|
+
allowCancel?: boolean;
|
|
355
|
+
/** Overlay background opacity, 0–1 (default: 0.3). */
|
|
356
|
+
opacity?: number;
|
|
357
|
+
/** Auto-unlock timeout in ms (default: 30000). 0 disables. */
|
|
358
|
+
timeout?: number;
|
|
359
|
+
/** Delay before actually removing the overlay after unlock, to avoid flicker between consecutive tools (default: 200ms). */
|
|
360
|
+
debounceUnlock?: number;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export type AgentLockEventType = 'lock' | 'unlock' | 'cancel' | 'timeout';
|
|
364
|
+
|
|
365
|
+
export interface AgentLockEventData {
|
|
366
|
+
requestId?: string;
|
|
367
|
+
timestamp: number;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export type AgentLockEventHandler = (data: AgentLockEventData) => void;
|
|
371
|
+
|
|
372
|
+
export interface ChatableXAgentLock {
|
|
373
|
+
/** Manually lock user interaction with an optional custom message / timeout. */
|
|
374
|
+
lock(opts?: { message?: string; timeout?: number }): void;
|
|
375
|
+
/** Manually unlock. Safe to call when already unlocked. */
|
|
376
|
+
unlock(): void;
|
|
377
|
+
/** Whether the overlay is currently active. */
|
|
378
|
+
isLocked(): boolean;
|
|
379
|
+
/** Subscribe to lock lifecycle events. Returns an unsubscribe function. */
|
|
380
|
+
on(event: AgentLockEventType, handler: AgentLockEventHandler): () => void;
|
|
381
|
+
/** Remove a previously registered handler. */
|
|
382
|
+
off(event: AgentLockEventType, handler: AgentLockEventHandler): void;
|
|
383
|
+
}
|
|
384
|
+
|
|
335
385
|
export interface ChatableXSDK {
|
|
336
386
|
ai: ChatableXAI;
|
|
337
387
|
tools: ChatableXTools;
|
|
@@ -342,6 +392,7 @@ export interface ChatableXSDK {
|
|
|
342
392
|
platform: ChatableXPlatform;
|
|
343
393
|
auth: ChatableXAuth;
|
|
344
394
|
cloud: ChatableXCloud;
|
|
395
|
+
agentLock: ChatableXAgentLock;
|
|
345
396
|
}
|
|
346
397
|
|
|
347
398
|
// ---------------------------------------------------------------------------
|