@modelnex/sdk 0.5.16 → 0.5.17
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.js +149 -83
- package/dist/index.mjs +149 -83
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2626,7 +2626,6 @@ function isTourEligible(tour, userProfile) {
|
|
|
2626
2626
|
|
|
2627
2627
|
// src/hooks/useTourPlayback.ts
|
|
2628
2628
|
var import_react12 = require("react");
|
|
2629
|
-
var import_socket2 = require("socket.io-client");
|
|
2630
2629
|
|
|
2631
2630
|
// src/utils/retryLookup.ts
|
|
2632
2631
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -2649,6 +2648,76 @@ async function retryLookup({
|
|
|
2649
2648
|
}
|
|
2650
2649
|
}
|
|
2651
2650
|
|
|
2651
|
+
// src/utils/tour-socket-pool.ts
|
|
2652
|
+
var import_socket2 = require("socket.io-client");
|
|
2653
|
+
function isSocketWritable(socket) {
|
|
2654
|
+
if (!socket?.connected) return false;
|
|
2655
|
+
const engine = socket.io?.engine;
|
|
2656
|
+
if (!engine) return true;
|
|
2657
|
+
if (typeof engine.readyState === "string" && engine.readyState !== "open") return false;
|
|
2658
|
+
if (engine.transport && "writable" in engine.transport && engine.transport.writable === false) return false;
|
|
2659
|
+
return true;
|
|
2660
|
+
}
|
|
2661
|
+
function emitSocketEvent(socket, event, payload) {
|
|
2662
|
+
if (!isSocketWritable(socket)) return false;
|
|
2663
|
+
socket.emit(event, payload);
|
|
2664
|
+
return true;
|
|
2665
|
+
}
|
|
2666
|
+
function createTourSocketPool({
|
|
2667
|
+
createSocket = (serverUrl) => (0, import_socket2.io)(serverUrl, {
|
|
2668
|
+
path: "/socket.io",
|
|
2669
|
+
transports: resolveSocketIoTransports(serverUrl, "polling-first"),
|
|
2670
|
+
autoConnect: true,
|
|
2671
|
+
reconnection: true,
|
|
2672
|
+
reconnectionAttempts: 10,
|
|
2673
|
+
reconnectionDelay: 1e3
|
|
2674
|
+
}),
|
|
2675
|
+
releaseDelayMs = 2500,
|
|
2676
|
+
scheduleRelease = (callback, delayMs) => setTimeout(callback, delayMs),
|
|
2677
|
+
cancelRelease = (timer) => clearTimeout(timer)
|
|
2678
|
+
} = {}) {
|
|
2679
|
+
const pooledSockets = /* @__PURE__ */ new Map();
|
|
2680
|
+
return {
|
|
2681
|
+
acquire(serverUrl) {
|
|
2682
|
+
const pooled = pooledSockets.get(serverUrl);
|
|
2683
|
+
if (pooled) {
|
|
2684
|
+
if (pooled.releaseTimer) {
|
|
2685
|
+
cancelRelease(pooled.releaseTimer);
|
|
2686
|
+
pooled.releaseTimer = null;
|
|
2687
|
+
}
|
|
2688
|
+
pooled.leases += 1;
|
|
2689
|
+
return pooled.socket;
|
|
2690
|
+
}
|
|
2691
|
+
const socket = createSocket(serverUrl);
|
|
2692
|
+
pooledSockets.set(serverUrl, {
|
|
2693
|
+
socket,
|
|
2694
|
+
leases: 1,
|
|
2695
|
+
releaseTimer: null
|
|
2696
|
+
});
|
|
2697
|
+
return socket;
|
|
2698
|
+
},
|
|
2699
|
+
release(serverUrl, socket) {
|
|
2700
|
+
const pooled = pooledSockets.get(serverUrl);
|
|
2701
|
+
if (!pooled || pooled.socket !== socket) return;
|
|
2702
|
+
pooled.leases = Math.max(0, pooled.leases - 1);
|
|
2703
|
+
if (pooled.leases > 0) return;
|
|
2704
|
+
pooled.releaseTimer = scheduleRelease(() => {
|
|
2705
|
+
const latest = pooledSockets.get(serverUrl);
|
|
2706
|
+
if (!latest || latest !== pooled || latest.leases > 0) return;
|
|
2707
|
+
latest.socket.disconnect();
|
|
2708
|
+
pooledSockets.delete(serverUrl);
|
|
2709
|
+
}, releaseDelayMs);
|
|
2710
|
+
},
|
|
2711
|
+
// Test-only introspection
|
|
2712
|
+
getSnapshot(serverUrl) {
|
|
2713
|
+
const pooled = pooledSockets.get(serverUrl);
|
|
2714
|
+
if (!pooled) return null;
|
|
2715
|
+
return { leases: pooled.leases, socket: pooled.socket };
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
var tourSocketPool = createTourSocketPool();
|
|
2720
|
+
|
|
2652
2721
|
// src/hooks/useTourPlayback.ts
|
|
2653
2722
|
function resolveElement(step) {
|
|
2654
2723
|
const el = step.element;
|
|
@@ -3185,11 +3254,13 @@ function useTourPlayback({
|
|
|
3185
3254
|
const socketRef = (0, import_react12.useRef)(null);
|
|
3186
3255
|
const socketIdRef = (0, import_react12.useRef)(socketId);
|
|
3187
3256
|
const commandUrlRef = (0, import_react12.useRef)(commandUrl);
|
|
3257
|
+
const websiteIdRef = (0, import_react12.useRef)(websiteId);
|
|
3188
3258
|
const onStepChangeRef = (0, import_react12.useRef)(onStepChange);
|
|
3189
3259
|
const isActiveRef = (0, import_react12.useRef)(false);
|
|
3190
3260
|
const activeCommandBatchIdRef = (0, import_react12.useRef)(null);
|
|
3191
3261
|
socketIdRef.current = socketId;
|
|
3192
3262
|
commandUrlRef.current = commandUrl;
|
|
3263
|
+
websiteIdRef.current = websiteId;
|
|
3193
3264
|
onStepChangeRef.current = onStepChange;
|
|
3194
3265
|
isActiveRef.current = isActive;
|
|
3195
3266
|
reviewModeRef.current = isReviewMode;
|
|
@@ -3200,25 +3271,17 @@ function useTourPlayback({
|
|
|
3200
3271
|
(0, import_react12.useEffect)(() => {
|
|
3201
3272
|
if (disabled) return;
|
|
3202
3273
|
if (typeof window === "undefined") return;
|
|
3203
|
-
|
|
3204
|
-
const socket = (0, import_socket2.io)(serverUrl, {
|
|
3205
|
-
path: "/socket.io",
|
|
3206
|
-
transports: resolveSocketIoTransports(serverUrl, "polling-first"),
|
|
3207
|
-
autoConnect: true,
|
|
3208
|
-
reconnection: true,
|
|
3209
|
-
reconnectionAttempts: 10,
|
|
3210
|
-
reconnectionDelay: 1e3
|
|
3211
|
-
});
|
|
3212
|
-
createdSocket = socket;
|
|
3274
|
+
const socket = tourSocketPool.acquire(serverUrl);
|
|
3213
3275
|
socketRef.current = socket;
|
|
3214
|
-
|
|
3276
|
+
const handleConnect = () => {
|
|
3215
3277
|
console.log("[TourClient] Connected to tour agent server:", socket.id);
|
|
3216
3278
|
const profile = userProfileRef.current;
|
|
3217
|
-
|
|
3218
|
-
|
|
3279
|
+
const currentWebsiteId = websiteIdRef.current;
|
|
3280
|
+
if (currentWebsiteId && profile?.userId) {
|
|
3281
|
+
emitSocketEvent(socket, "tour:init", { websiteId: currentWebsiteId, userId: profile.userId, userType: profile.type });
|
|
3219
3282
|
}
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3283
|
+
};
|
|
3284
|
+
const handleServerState = (payload) => {
|
|
3222
3285
|
if (typeof payload?.runId === "number") {
|
|
3223
3286
|
runIdRef.current = payload.runId;
|
|
3224
3287
|
}
|
|
@@ -3226,8 +3289,8 @@ function useTourPlayback({
|
|
|
3226
3289
|
turnIdRef.current = payload.turnId ?? null;
|
|
3227
3290
|
}
|
|
3228
3291
|
setServerState(payload);
|
|
3229
|
-
}
|
|
3230
|
-
|
|
3292
|
+
};
|
|
3293
|
+
const handleCommandCancel = (payload) => {
|
|
3231
3294
|
console.log("[TourClient] Received command_cancel:", payload);
|
|
3232
3295
|
if (payload.commandBatchId && activeCommandBatchIdRef.current === payload.commandBatchId) {
|
|
3233
3296
|
activeCommandBatchIdRef.current = null;
|
|
@@ -3238,12 +3301,11 @@ function useTourPlayback({
|
|
|
3238
3301
|
window.speechSynthesis.cancel();
|
|
3239
3302
|
}
|
|
3240
3303
|
}
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3304
|
+
};
|
|
3305
|
+
const handleCommand = async (payload) => {
|
|
3243
3306
|
const emitIfOpen = (ev, data) => {
|
|
3244
3307
|
if (socketRef.current !== socket) return;
|
|
3245
|
-
|
|
3246
|
-
socket.emit(ev, data);
|
|
3308
|
+
emitSocketEvent(socket, ev, data);
|
|
3247
3309
|
};
|
|
3248
3310
|
console.log("[TourClient] Received command batch:", payload.stepIndex, payload.commands);
|
|
3249
3311
|
runCleanup(pendingManualWaitCleanupRef.current);
|
|
@@ -3736,8 +3798,8 @@ function useTourPlayback({
|
|
|
3736
3798
|
turnId: turnIdRef.current
|
|
3737
3799
|
});
|
|
3738
3800
|
clearCommandBatchId();
|
|
3739
|
-
}
|
|
3740
|
-
|
|
3801
|
+
};
|
|
3802
|
+
const handleTourStart = async (tourData) => {
|
|
3741
3803
|
if (isActiveRef.current) return;
|
|
3742
3804
|
runIdRef.current = typeof tourData.runId === "number" ? tourData.runId : runIdRef.current;
|
|
3743
3805
|
const tour = tourData.tourContext ?? tourRef.current;
|
|
@@ -3770,8 +3832,8 @@ function useTourPlayback({
|
|
|
3770
3832
|
try {
|
|
3771
3833
|
const { generateMinifiedAOM: generateMinifiedAOM2 } = await Promise.resolve().then(() => (init_aom(), aom_exports));
|
|
3772
3834
|
const aom = generateMinifiedAOM2();
|
|
3773
|
-
if (socketRef.current === socket
|
|
3774
|
-
socket
|
|
3835
|
+
if (socketRef.current === socket) {
|
|
3836
|
+
emitSocketEvent(socket, "tour:sync_dom", {
|
|
3775
3837
|
url: window.location.pathname + window.location.search + window.location.hash,
|
|
3776
3838
|
aom: aom.nodes,
|
|
3777
3839
|
domSummary: captureDomSummary()
|
|
@@ -3780,8 +3842,8 @@ function useTourPlayback({
|
|
|
3780
3842
|
} catch (e) {
|
|
3781
3843
|
console.warn("[TourClient] Initial DOM sync failed:", e);
|
|
3782
3844
|
}
|
|
3783
|
-
}
|
|
3784
|
-
|
|
3845
|
+
};
|
|
3846
|
+
const handleTourUpdate = (payload) => {
|
|
3785
3847
|
const updatedTour = payload?.tourContext;
|
|
3786
3848
|
if (!updatedTour?.id || updatedTour.id !== tourRef.current?.id) {
|
|
3787
3849
|
return;
|
|
@@ -3798,12 +3860,12 @@ function useTourPlayback({
|
|
|
3798
3860
|
onStepChangeRef.current?.(clampedStepIndex, nextTotal, updatedTour);
|
|
3799
3861
|
}
|
|
3800
3862
|
}
|
|
3801
|
-
}
|
|
3802
|
-
|
|
3863
|
+
};
|
|
3864
|
+
const handleTourEndEvent = () => {
|
|
3803
3865
|
setServerState((prev) => prev ? { ...prev, isActive: false, phase: "completed" } : prev);
|
|
3804
3866
|
handleTourEnd();
|
|
3805
|
-
}
|
|
3806
|
-
|
|
3867
|
+
};
|
|
3868
|
+
const handleDebugLog = (entry) => {
|
|
3807
3869
|
const isDev = devModeRef.current || process.env.NODE_ENV === "development" || typeof window !== "undefined" && window.MODELNEX_DEBUG;
|
|
3808
3870
|
if (isDev) {
|
|
3809
3871
|
console.log(`%c[ModelNex Tour] ${entry.type}`, "color: #3b82f6; font-weight: bold", entry);
|
|
@@ -3813,29 +3875,40 @@ function useTourPlayback({
|
|
|
3813
3875
|
}));
|
|
3814
3876
|
}
|
|
3815
3877
|
}
|
|
3816
|
-
}
|
|
3878
|
+
};
|
|
3879
|
+
socket.on("connect", handleConnect);
|
|
3880
|
+
socket.on("tour:server_state", handleServerState);
|
|
3881
|
+
socket.on("tour:command_cancel", handleCommandCancel);
|
|
3882
|
+
socket.on("tour:command", handleCommand);
|
|
3883
|
+
socket.on("tour:start", handleTourStart);
|
|
3884
|
+
socket.on("tour:update", handleTourUpdate);
|
|
3885
|
+
socket.on("tour:end", handleTourEndEvent);
|
|
3886
|
+
socket.on("tour:debug_log", handleDebugLog);
|
|
3817
3887
|
console.log("[ModelNex SDK] Tour playback initialized. Debug logs enabled:", devModeRef.current || process.env.NODE_ENV === "development");
|
|
3818
3888
|
return () => {
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3889
|
+
socket.off("connect", handleConnect);
|
|
3890
|
+
socket.off("tour:server_state", handleServerState);
|
|
3891
|
+
socket.off("tour:command_cancel", handleCommandCancel);
|
|
3892
|
+
socket.off("tour:command", handleCommand);
|
|
3893
|
+
socket.off("tour:start", handleTourStart);
|
|
3894
|
+
socket.off("tour:update", handleTourUpdate);
|
|
3895
|
+
socket.off("tour:end", handleTourEndEvent);
|
|
3896
|
+
socket.off("tour:debug_log", handleDebugLog);
|
|
3897
|
+
const toClose = socket;
|
|
3824
3898
|
socketRef.current = null;
|
|
3825
3899
|
setServerState(null);
|
|
3826
3900
|
runIdRef.current = null;
|
|
3827
3901
|
turnIdRef.current = null;
|
|
3902
|
+
tourSocketPool.release(serverUrl, toClose);
|
|
3828
3903
|
};
|
|
3829
|
-
}, [serverUrl,
|
|
3904
|
+
}, [serverUrl, disabled]);
|
|
3830
3905
|
(0, import_react12.useEffect)(() => {
|
|
3831
3906
|
if (disabled) return;
|
|
3832
3907
|
const s = socketRef.current;
|
|
3833
3908
|
const profile = userProfile;
|
|
3834
|
-
if (!
|
|
3909
|
+
if (!websiteId || !profile?.userId) return;
|
|
3835
3910
|
const timer = setTimeout(() => {
|
|
3836
|
-
|
|
3837
|
-
s.emit("tour:init", { websiteId, userId: profile.userId, userType: profile.type });
|
|
3838
|
-
}
|
|
3911
|
+
emitSocketEvent(s, "tour:init", { websiteId, userId: profile.userId, userType: profile.type });
|
|
3839
3912
|
}, 150);
|
|
3840
3913
|
return () => clearTimeout(timer);
|
|
3841
3914
|
}, [disabled, websiteId, userProfile?.userId, userProfile?.type]);
|
|
@@ -3845,8 +3918,8 @@ function useTourPlayback({
|
|
|
3845
3918
|
}
|
|
3846
3919
|
}, [showCaptions, isReviewMode]);
|
|
3847
3920
|
(0, import_react12.useEffect)(() => {
|
|
3848
|
-
if (!
|
|
3849
|
-
socketRef.current
|
|
3921
|
+
if (!isActiveRef.current) return;
|
|
3922
|
+
emitSocketEvent(socketRef.current, "tour:client_state", {
|
|
3850
3923
|
runId: runIdRef.current,
|
|
3851
3924
|
turnId: turnIdRef.current,
|
|
3852
3925
|
commandBatchId: activeCommandBatchIdRef.current,
|
|
@@ -3859,19 +3932,17 @@ function useTourPlayback({
|
|
|
3859
3932
|
});
|
|
3860
3933
|
}, [isActive, playbackState, voice.isListening, voice.isSpeaking]);
|
|
3861
3934
|
const syncAOM = (0, import_react12.useCallback)(async () => {
|
|
3862
|
-
if (!
|
|
3935
|
+
if (!isActiveRef.current) return;
|
|
3863
3936
|
const { generateMinifiedAOM: generateMinifiedAOM2 } = await Promise.resolve().then(() => (init_aom(), aom_exports));
|
|
3864
3937
|
const aom = generateMinifiedAOM2();
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
});
|
|
3871
|
-
}
|
|
3938
|
+
emitSocketEvent(socketRef.current, "tour:sync_dom", {
|
|
3939
|
+
url: window.location.pathname + window.location.search + window.location.hash,
|
|
3940
|
+
aom: aom.nodes,
|
|
3941
|
+
domSummary: captureDomSummary()
|
|
3942
|
+
});
|
|
3872
3943
|
}, []);
|
|
3873
3944
|
const interruptExecution = (0, import_react12.useCallback)((transcript) => {
|
|
3874
|
-
if (!socketRef.current
|
|
3945
|
+
if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
|
|
3875
3946
|
if (!commandInFlightRef.current && !voice.isSpeaking) return false;
|
|
3876
3947
|
interruptedForQuestionRef.current = true;
|
|
3877
3948
|
activeExecutionTokenRef.current += 1;
|
|
@@ -3879,17 +3950,16 @@ function useTourPlayback({
|
|
|
3879
3950
|
removeHighlight();
|
|
3880
3951
|
removeCaption();
|
|
3881
3952
|
voice.stopSpeaking();
|
|
3882
|
-
if (socketRef.current
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
});
|
|
3953
|
+
if (emitSocketEvent(socketRef.current, "tour:action_result", {
|
|
3954
|
+
success: true,
|
|
3955
|
+
interrupted: true,
|
|
3956
|
+
transcript,
|
|
3957
|
+
commandBatchId: activeCommandBatchIdRef.current,
|
|
3958
|
+
runId: runIdRef.current,
|
|
3959
|
+
turnId: turnIdRef.current
|
|
3960
|
+
})) {
|
|
3891
3961
|
activeCommandBatchIdRef.current = null;
|
|
3892
|
-
socketRef.current
|
|
3962
|
+
emitSocketEvent(socketRef.current, "tour:user_input", {
|
|
3893
3963
|
transcript,
|
|
3894
3964
|
interrupted: true,
|
|
3895
3965
|
runId: runIdRef.current,
|
|
@@ -3911,9 +3981,7 @@ function useTourPlayback({
|
|
|
3911
3981
|
removeCaption();
|
|
3912
3982
|
voice.stopSpeaking();
|
|
3913
3983
|
voice.stopListening();
|
|
3914
|
-
|
|
3915
|
-
socketRef.current.emit("tour:abort");
|
|
3916
|
-
}
|
|
3984
|
+
emitSocketEvent(socketRef.current, "tour:abort");
|
|
3917
3985
|
if (reviewModeRef.current && tourRef.current?.id && previewRunIdRef.current) {
|
|
3918
3986
|
void logPreviewEvent(serverUrl, toursApiBaseRef.current, tourRef.current.id, previewRunIdRef.current, websiteId, {
|
|
3919
3987
|
stepOrder: stepIndexRef.current,
|
|
@@ -3985,7 +4053,7 @@ function useTourPlayback({
|
|
|
3985
4053
|
await new Promise((r) => setTimeout(r, 200));
|
|
3986
4054
|
retries++;
|
|
3987
4055
|
}
|
|
3988
|
-
if (!socketRef.current
|
|
4056
|
+
if (!isSocketWritable(socketRef.current)) {
|
|
3989
4057
|
console.warn("[TourClient] Cannot run tour, socket not connected.");
|
|
3990
4058
|
return;
|
|
3991
4059
|
}
|
|
@@ -4013,13 +4081,11 @@ function useTourPlayback({
|
|
|
4013
4081
|
previewRunIdRef.current = null;
|
|
4014
4082
|
}
|
|
4015
4083
|
tourRef.current = tour;
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
});
|
|
4022
|
-
}
|
|
4084
|
+
emitSocketEvent(socketRef.current, "tour:request_start", {
|
|
4085
|
+
tourId: tour.id,
|
|
4086
|
+
previewRunId: previewRunIdRef.current,
|
|
4087
|
+
tourContext: tour
|
|
4088
|
+
});
|
|
4023
4089
|
}, [serverUrl, websiteId]);
|
|
4024
4090
|
(0, import_react12.useEffect)(() => {
|
|
4025
4091
|
if (!enableAutoDiscovery) return;
|
|
@@ -4163,8 +4229,8 @@ function useTourPlayback({
|
|
|
4163
4229
|
const revisionVersion = Number(response?.revision?.versionNumber);
|
|
4164
4230
|
const appliedMessage = Number.isFinite(revisionVersion) && revisionVersion > 0 ? `Applied to the draft as version ${revisionVersion}.` : "Correction applied to the draft.";
|
|
4165
4231
|
setReviewStatusMessage(apply ? appliedMessage : "Correction saved for review.");
|
|
4166
|
-
if (apply && playbackState === "paused" &&
|
|
4167
|
-
socketRef.current
|
|
4232
|
+
if (apply && playbackState === "paused" && isActiveRef.current) {
|
|
4233
|
+
emitSocketEvent(socketRef.current, "tour:resume");
|
|
4168
4234
|
setPlaybackState("executing");
|
|
4169
4235
|
}
|
|
4170
4236
|
} catch (err) {
|
|
@@ -4188,14 +4254,14 @@ function useTourPlayback({
|
|
|
4188
4254
|
stopTour();
|
|
4189
4255
|
}, [stopTour]);
|
|
4190
4256
|
const pauseTour = (0, import_react12.useCallback)(() => {
|
|
4191
|
-
if (socketRef.current
|
|
4192
|
-
socketRef.current
|
|
4257
|
+
if (isSocketWritable(socketRef.current) && isActiveRef.current) {
|
|
4258
|
+
emitSocketEvent(socketRef.current, "tour:pause");
|
|
4193
4259
|
setPlaybackState("paused");
|
|
4194
4260
|
}
|
|
4195
4261
|
}, []);
|
|
4196
4262
|
const resumeTour = (0, import_react12.useCallback)(() => {
|
|
4197
|
-
if (socketRef.current
|
|
4198
|
-
socketRef.current
|
|
4263
|
+
if (isSocketWritable(socketRef.current) && isActiveRef.current) {
|
|
4264
|
+
emitSocketEvent(socketRef.current, "tour:resume");
|
|
4199
4265
|
setPlaybackState("executing");
|
|
4200
4266
|
}
|
|
4201
4267
|
}, []);
|
|
@@ -4217,9 +4283,9 @@ function useTourPlayback({
|
|
|
4217
4283
|
if (voiceInputResolveRef.current) {
|
|
4218
4284
|
console.log("[TourAgent] Resolving loop waiting_voice with text:", text);
|
|
4219
4285
|
voiceInputResolveRef.current(text);
|
|
4220
|
-
} else if (socketRef.current
|
|
4286
|
+
} else if (isSocketWritable(socketRef.current)) {
|
|
4221
4287
|
console.log("[TourAgent] Forwarding ambient voice to server:", text);
|
|
4222
|
-
socketRef.current
|
|
4288
|
+
emitSocketEvent(socketRef.current, "tour:user_input", {
|
|
4223
4289
|
transcript: text,
|
|
4224
4290
|
runId: runIdRef.current,
|
|
4225
4291
|
turnId: turnIdRef.current
|
package/dist/index.mjs
CHANGED
|
@@ -2416,7 +2416,6 @@ function isTourEligible(tour, userProfile) {
|
|
|
2416
2416
|
|
|
2417
2417
|
// src/hooks/useTourPlayback.ts
|
|
2418
2418
|
import { useState as useState7, useRef as useRef8, useCallback as useCallback7, useEffect as useEffect11, useContext as useContext4 } from "react";
|
|
2419
|
-
import { io as io2 } from "socket.io-client";
|
|
2420
2419
|
|
|
2421
2420
|
// src/utils/retryLookup.ts
|
|
2422
2421
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -2439,6 +2438,76 @@ async function retryLookup({
|
|
|
2439
2438
|
}
|
|
2440
2439
|
}
|
|
2441
2440
|
|
|
2441
|
+
// src/utils/tour-socket-pool.ts
|
|
2442
|
+
import { io as io2 } from "socket.io-client";
|
|
2443
|
+
function isSocketWritable(socket) {
|
|
2444
|
+
if (!socket?.connected) return false;
|
|
2445
|
+
const engine = socket.io?.engine;
|
|
2446
|
+
if (!engine) return true;
|
|
2447
|
+
if (typeof engine.readyState === "string" && engine.readyState !== "open") return false;
|
|
2448
|
+
if (engine.transport && "writable" in engine.transport && engine.transport.writable === false) return false;
|
|
2449
|
+
return true;
|
|
2450
|
+
}
|
|
2451
|
+
function emitSocketEvent(socket, event, payload) {
|
|
2452
|
+
if (!isSocketWritable(socket)) return false;
|
|
2453
|
+
socket.emit(event, payload);
|
|
2454
|
+
return true;
|
|
2455
|
+
}
|
|
2456
|
+
function createTourSocketPool({
|
|
2457
|
+
createSocket = (serverUrl) => io2(serverUrl, {
|
|
2458
|
+
path: "/socket.io",
|
|
2459
|
+
transports: resolveSocketIoTransports(serverUrl, "polling-first"),
|
|
2460
|
+
autoConnect: true,
|
|
2461
|
+
reconnection: true,
|
|
2462
|
+
reconnectionAttempts: 10,
|
|
2463
|
+
reconnectionDelay: 1e3
|
|
2464
|
+
}),
|
|
2465
|
+
releaseDelayMs = 2500,
|
|
2466
|
+
scheduleRelease = (callback, delayMs) => setTimeout(callback, delayMs),
|
|
2467
|
+
cancelRelease = (timer) => clearTimeout(timer)
|
|
2468
|
+
} = {}) {
|
|
2469
|
+
const pooledSockets = /* @__PURE__ */ new Map();
|
|
2470
|
+
return {
|
|
2471
|
+
acquire(serverUrl) {
|
|
2472
|
+
const pooled = pooledSockets.get(serverUrl);
|
|
2473
|
+
if (pooled) {
|
|
2474
|
+
if (pooled.releaseTimer) {
|
|
2475
|
+
cancelRelease(pooled.releaseTimer);
|
|
2476
|
+
pooled.releaseTimer = null;
|
|
2477
|
+
}
|
|
2478
|
+
pooled.leases += 1;
|
|
2479
|
+
return pooled.socket;
|
|
2480
|
+
}
|
|
2481
|
+
const socket = createSocket(serverUrl);
|
|
2482
|
+
pooledSockets.set(serverUrl, {
|
|
2483
|
+
socket,
|
|
2484
|
+
leases: 1,
|
|
2485
|
+
releaseTimer: null
|
|
2486
|
+
});
|
|
2487
|
+
return socket;
|
|
2488
|
+
},
|
|
2489
|
+
release(serverUrl, socket) {
|
|
2490
|
+
const pooled = pooledSockets.get(serverUrl);
|
|
2491
|
+
if (!pooled || pooled.socket !== socket) return;
|
|
2492
|
+
pooled.leases = Math.max(0, pooled.leases - 1);
|
|
2493
|
+
if (pooled.leases > 0) return;
|
|
2494
|
+
pooled.releaseTimer = scheduleRelease(() => {
|
|
2495
|
+
const latest = pooledSockets.get(serverUrl);
|
|
2496
|
+
if (!latest || latest !== pooled || latest.leases > 0) return;
|
|
2497
|
+
latest.socket.disconnect();
|
|
2498
|
+
pooledSockets.delete(serverUrl);
|
|
2499
|
+
}, releaseDelayMs);
|
|
2500
|
+
},
|
|
2501
|
+
// Test-only introspection
|
|
2502
|
+
getSnapshot(serverUrl) {
|
|
2503
|
+
const pooled = pooledSockets.get(serverUrl);
|
|
2504
|
+
if (!pooled) return null;
|
|
2505
|
+
return { leases: pooled.leases, socket: pooled.socket };
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
var tourSocketPool = createTourSocketPool();
|
|
2510
|
+
|
|
2442
2511
|
// src/hooks/useTourPlayback.ts
|
|
2443
2512
|
function resolveElement(step) {
|
|
2444
2513
|
const el = step.element;
|
|
@@ -2975,11 +3044,13 @@ function useTourPlayback({
|
|
|
2975
3044
|
const socketRef = useRef8(null);
|
|
2976
3045
|
const socketIdRef = useRef8(socketId);
|
|
2977
3046
|
const commandUrlRef = useRef8(commandUrl);
|
|
3047
|
+
const websiteIdRef = useRef8(websiteId);
|
|
2978
3048
|
const onStepChangeRef = useRef8(onStepChange);
|
|
2979
3049
|
const isActiveRef = useRef8(false);
|
|
2980
3050
|
const activeCommandBatchIdRef = useRef8(null);
|
|
2981
3051
|
socketIdRef.current = socketId;
|
|
2982
3052
|
commandUrlRef.current = commandUrl;
|
|
3053
|
+
websiteIdRef.current = websiteId;
|
|
2983
3054
|
onStepChangeRef.current = onStepChange;
|
|
2984
3055
|
isActiveRef.current = isActive;
|
|
2985
3056
|
reviewModeRef.current = isReviewMode;
|
|
@@ -2990,25 +3061,17 @@ function useTourPlayback({
|
|
|
2990
3061
|
useEffect11(() => {
|
|
2991
3062
|
if (disabled) return;
|
|
2992
3063
|
if (typeof window === "undefined") return;
|
|
2993
|
-
|
|
2994
|
-
const socket = io2(serverUrl, {
|
|
2995
|
-
path: "/socket.io",
|
|
2996
|
-
transports: resolveSocketIoTransports(serverUrl, "polling-first"),
|
|
2997
|
-
autoConnect: true,
|
|
2998
|
-
reconnection: true,
|
|
2999
|
-
reconnectionAttempts: 10,
|
|
3000
|
-
reconnectionDelay: 1e3
|
|
3001
|
-
});
|
|
3002
|
-
createdSocket = socket;
|
|
3064
|
+
const socket = tourSocketPool.acquire(serverUrl);
|
|
3003
3065
|
socketRef.current = socket;
|
|
3004
|
-
|
|
3066
|
+
const handleConnect = () => {
|
|
3005
3067
|
console.log("[TourClient] Connected to tour agent server:", socket.id);
|
|
3006
3068
|
const profile = userProfileRef.current;
|
|
3007
|
-
|
|
3008
|
-
|
|
3069
|
+
const currentWebsiteId = websiteIdRef.current;
|
|
3070
|
+
if (currentWebsiteId && profile?.userId) {
|
|
3071
|
+
emitSocketEvent(socket, "tour:init", { websiteId: currentWebsiteId, userId: profile.userId, userType: profile.type });
|
|
3009
3072
|
}
|
|
3010
|
-
}
|
|
3011
|
-
|
|
3073
|
+
};
|
|
3074
|
+
const handleServerState = (payload) => {
|
|
3012
3075
|
if (typeof payload?.runId === "number") {
|
|
3013
3076
|
runIdRef.current = payload.runId;
|
|
3014
3077
|
}
|
|
@@ -3016,8 +3079,8 @@ function useTourPlayback({
|
|
|
3016
3079
|
turnIdRef.current = payload.turnId ?? null;
|
|
3017
3080
|
}
|
|
3018
3081
|
setServerState(payload);
|
|
3019
|
-
}
|
|
3020
|
-
|
|
3082
|
+
};
|
|
3083
|
+
const handleCommandCancel = (payload) => {
|
|
3021
3084
|
console.log("[TourClient] Received command_cancel:", payload);
|
|
3022
3085
|
if (payload.commandBatchId && activeCommandBatchIdRef.current === payload.commandBatchId) {
|
|
3023
3086
|
activeCommandBatchIdRef.current = null;
|
|
@@ -3028,12 +3091,11 @@ function useTourPlayback({
|
|
|
3028
3091
|
window.speechSynthesis.cancel();
|
|
3029
3092
|
}
|
|
3030
3093
|
}
|
|
3031
|
-
}
|
|
3032
|
-
|
|
3094
|
+
};
|
|
3095
|
+
const handleCommand = async (payload) => {
|
|
3033
3096
|
const emitIfOpen = (ev, data) => {
|
|
3034
3097
|
if (socketRef.current !== socket) return;
|
|
3035
|
-
|
|
3036
|
-
socket.emit(ev, data);
|
|
3098
|
+
emitSocketEvent(socket, ev, data);
|
|
3037
3099
|
};
|
|
3038
3100
|
console.log("[TourClient] Received command batch:", payload.stepIndex, payload.commands);
|
|
3039
3101
|
runCleanup(pendingManualWaitCleanupRef.current);
|
|
@@ -3526,8 +3588,8 @@ function useTourPlayback({
|
|
|
3526
3588
|
turnId: turnIdRef.current
|
|
3527
3589
|
});
|
|
3528
3590
|
clearCommandBatchId();
|
|
3529
|
-
}
|
|
3530
|
-
|
|
3591
|
+
};
|
|
3592
|
+
const handleTourStart = async (tourData) => {
|
|
3531
3593
|
if (isActiveRef.current) return;
|
|
3532
3594
|
runIdRef.current = typeof tourData.runId === "number" ? tourData.runId : runIdRef.current;
|
|
3533
3595
|
const tour = tourData.tourContext ?? tourRef.current;
|
|
@@ -3560,8 +3622,8 @@ function useTourPlayback({
|
|
|
3560
3622
|
try {
|
|
3561
3623
|
const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
|
|
3562
3624
|
const aom = generateMinifiedAOM2();
|
|
3563
|
-
if (socketRef.current === socket
|
|
3564
|
-
socket
|
|
3625
|
+
if (socketRef.current === socket) {
|
|
3626
|
+
emitSocketEvent(socket, "tour:sync_dom", {
|
|
3565
3627
|
url: window.location.pathname + window.location.search + window.location.hash,
|
|
3566
3628
|
aom: aom.nodes,
|
|
3567
3629
|
domSummary: captureDomSummary()
|
|
@@ -3570,8 +3632,8 @@ function useTourPlayback({
|
|
|
3570
3632
|
} catch (e) {
|
|
3571
3633
|
console.warn("[TourClient] Initial DOM sync failed:", e);
|
|
3572
3634
|
}
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3635
|
+
};
|
|
3636
|
+
const handleTourUpdate = (payload) => {
|
|
3575
3637
|
const updatedTour = payload?.tourContext;
|
|
3576
3638
|
if (!updatedTour?.id || updatedTour.id !== tourRef.current?.id) {
|
|
3577
3639
|
return;
|
|
@@ -3588,12 +3650,12 @@ function useTourPlayback({
|
|
|
3588
3650
|
onStepChangeRef.current?.(clampedStepIndex, nextTotal, updatedTour);
|
|
3589
3651
|
}
|
|
3590
3652
|
}
|
|
3591
|
-
}
|
|
3592
|
-
|
|
3653
|
+
};
|
|
3654
|
+
const handleTourEndEvent = () => {
|
|
3593
3655
|
setServerState((prev) => prev ? { ...prev, isActive: false, phase: "completed" } : prev);
|
|
3594
3656
|
handleTourEnd();
|
|
3595
|
-
}
|
|
3596
|
-
|
|
3657
|
+
};
|
|
3658
|
+
const handleDebugLog = (entry) => {
|
|
3597
3659
|
const isDev = devModeRef.current || process.env.NODE_ENV === "development" || typeof window !== "undefined" && window.MODELNEX_DEBUG;
|
|
3598
3660
|
if (isDev) {
|
|
3599
3661
|
console.log(`%c[ModelNex Tour] ${entry.type}`, "color: #3b82f6; font-weight: bold", entry);
|
|
@@ -3603,29 +3665,40 @@ function useTourPlayback({
|
|
|
3603
3665
|
}));
|
|
3604
3666
|
}
|
|
3605
3667
|
}
|
|
3606
|
-
}
|
|
3668
|
+
};
|
|
3669
|
+
socket.on("connect", handleConnect);
|
|
3670
|
+
socket.on("tour:server_state", handleServerState);
|
|
3671
|
+
socket.on("tour:command_cancel", handleCommandCancel);
|
|
3672
|
+
socket.on("tour:command", handleCommand);
|
|
3673
|
+
socket.on("tour:start", handleTourStart);
|
|
3674
|
+
socket.on("tour:update", handleTourUpdate);
|
|
3675
|
+
socket.on("tour:end", handleTourEndEvent);
|
|
3676
|
+
socket.on("tour:debug_log", handleDebugLog);
|
|
3607
3677
|
console.log("[ModelNex SDK] Tour playback initialized. Debug logs enabled:", devModeRef.current || process.env.NODE_ENV === "development");
|
|
3608
3678
|
return () => {
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3679
|
+
socket.off("connect", handleConnect);
|
|
3680
|
+
socket.off("tour:server_state", handleServerState);
|
|
3681
|
+
socket.off("tour:command_cancel", handleCommandCancel);
|
|
3682
|
+
socket.off("tour:command", handleCommand);
|
|
3683
|
+
socket.off("tour:start", handleTourStart);
|
|
3684
|
+
socket.off("tour:update", handleTourUpdate);
|
|
3685
|
+
socket.off("tour:end", handleTourEndEvent);
|
|
3686
|
+
socket.off("tour:debug_log", handleDebugLog);
|
|
3687
|
+
const toClose = socket;
|
|
3614
3688
|
socketRef.current = null;
|
|
3615
3689
|
setServerState(null);
|
|
3616
3690
|
runIdRef.current = null;
|
|
3617
3691
|
turnIdRef.current = null;
|
|
3692
|
+
tourSocketPool.release(serverUrl, toClose);
|
|
3618
3693
|
};
|
|
3619
|
-
}, [serverUrl,
|
|
3694
|
+
}, [serverUrl, disabled]);
|
|
3620
3695
|
useEffect11(() => {
|
|
3621
3696
|
if (disabled) return;
|
|
3622
3697
|
const s = socketRef.current;
|
|
3623
3698
|
const profile = userProfile;
|
|
3624
|
-
if (!
|
|
3699
|
+
if (!websiteId || !profile?.userId) return;
|
|
3625
3700
|
const timer = setTimeout(() => {
|
|
3626
|
-
|
|
3627
|
-
s.emit("tour:init", { websiteId, userId: profile.userId, userType: profile.type });
|
|
3628
|
-
}
|
|
3701
|
+
emitSocketEvent(s, "tour:init", { websiteId, userId: profile.userId, userType: profile.type });
|
|
3629
3702
|
}, 150);
|
|
3630
3703
|
return () => clearTimeout(timer);
|
|
3631
3704
|
}, [disabled, websiteId, userProfile?.userId, userProfile?.type]);
|
|
@@ -3635,8 +3708,8 @@ function useTourPlayback({
|
|
|
3635
3708
|
}
|
|
3636
3709
|
}, [showCaptions, isReviewMode]);
|
|
3637
3710
|
useEffect11(() => {
|
|
3638
|
-
if (!
|
|
3639
|
-
socketRef.current
|
|
3711
|
+
if (!isActiveRef.current) return;
|
|
3712
|
+
emitSocketEvent(socketRef.current, "tour:client_state", {
|
|
3640
3713
|
runId: runIdRef.current,
|
|
3641
3714
|
turnId: turnIdRef.current,
|
|
3642
3715
|
commandBatchId: activeCommandBatchIdRef.current,
|
|
@@ -3649,19 +3722,17 @@ function useTourPlayback({
|
|
|
3649
3722
|
});
|
|
3650
3723
|
}, [isActive, playbackState, voice.isListening, voice.isSpeaking]);
|
|
3651
3724
|
const syncAOM = useCallback7(async () => {
|
|
3652
|
-
if (!
|
|
3725
|
+
if (!isActiveRef.current) return;
|
|
3653
3726
|
const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
|
|
3654
3727
|
const aom = generateMinifiedAOM2();
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
});
|
|
3661
|
-
}
|
|
3728
|
+
emitSocketEvent(socketRef.current, "tour:sync_dom", {
|
|
3729
|
+
url: window.location.pathname + window.location.search + window.location.hash,
|
|
3730
|
+
aom: aom.nodes,
|
|
3731
|
+
domSummary: captureDomSummary()
|
|
3732
|
+
});
|
|
3662
3733
|
}, []);
|
|
3663
3734
|
const interruptExecution = useCallback7((transcript) => {
|
|
3664
|
-
if (!socketRef.current
|
|
3735
|
+
if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
|
|
3665
3736
|
if (!commandInFlightRef.current && !voice.isSpeaking) return false;
|
|
3666
3737
|
interruptedForQuestionRef.current = true;
|
|
3667
3738
|
activeExecutionTokenRef.current += 1;
|
|
@@ -3669,17 +3740,16 @@ function useTourPlayback({
|
|
|
3669
3740
|
removeHighlight();
|
|
3670
3741
|
removeCaption();
|
|
3671
3742
|
voice.stopSpeaking();
|
|
3672
|
-
if (socketRef.current
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
});
|
|
3743
|
+
if (emitSocketEvent(socketRef.current, "tour:action_result", {
|
|
3744
|
+
success: true,
|
|
3745
|
+
interrupted: true,
|
|
3746
|
+
transcript,
|
|
3747
|
+
commandBatchId: activeCommandBatchIdRef.current,
|
|
3748
|
+
runId: runIdRef.current,
|
|
3749
|
+
turnId: turnIdRef.current
|
|
3750
|
+
})) {
|
|
3681
3751
|
activeCommandBatchIdRef.current = null;
|
|
3682
|
-
socketRef.current
|
|
3752
|
+
emitSocketEvent(socketRef.current, "tour:user_input", {
|
|
3683
3753
|
transcript,
|
|
3684
3754
|
interrupted: true,
|
|
3685
3755
|
runId: runIdRef.current,
|
|
@@ -3701,9 +3771,7 @@ function useTourPlayback({
|
|
|
3701
3771
|
removeCaption();
|
|
3702
3772
|
voice.stopSpeaking();
|
|
3703
3773
|
voice.stopListening();
|
|
3704
|
-
|
|
3705
|
-
socketRef.current.emit("tour:abort");
|
|
3706
|
-
}
|
|
3774
|
+
emitSocketEvent(socketRef.current, "tour:abort");
|
|
3707
3775
|
if (reviewModeRef.current && tourRef.current?.id && previewRunIdRef.current) {
|
|
3708
3776
|
void logPreviewEvent(serverUrl, toursApiBaseRef.current, tourRef.current.id, previewRunIdRef.current, websiteId, {
|
|
3709
3777
|
stepOrder: stepIndexRef.current,
|
|
@@ -3775,7 +3843,7 @@ function useTourPlayback({
|
|
|
3775
3843
|
await new Promise((r) => setTimeout(r, 200));
|
|
3776
3844
|
retries++;
|
|
3777
3845
|
}
|
|
3778
|
-
if (!socketRef.current
|
|
3846
|
+
if (!isSocketWritable(socketRef.current)) {
|
|
3779
3847
|
console.warn("[TourClient] Cannot run tour, socket not connected.");
|
|
3780
3848
|
return;
|
|
3781
3849
|
}
|
|
@@ -3803,13 +3871,11 @@ function useTourPlayback({
|
|
|
3803
3871
|
previewRunIdRef.current = null;
|
|
3804
3872
|
}
|
|
3805
3873
|
tourRef.current = tour;
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
});
|
|
3812
|
-
}
|
|
3874
|
+
emitSocketEvent(socketRef.current, "tour:request_start", {
|
|
3875
|
+
tourId: tour.id,
|
|
3876
|
+
previewRunId: previewRunIdRef.current,
|
|
3877
|
+
tourContext: tour
|
|
3878
|
+
});
|
|
3813
3879
|
}, [serverUrl, websiteId]);
|
|
3814
3880
|
useEffect11(() => {
|
|
3815
3881
|
if (!enableAutoDiscovery) return;
|
|
@@ -3953,8 +4019,8 @@ function useTourPlayback({
|
|
|
3953
4019
|
const revisionVersion = Number(response?.revision?.versionNumber);
|
|
3954
4020
|
const appliedMessage = Number.isFinite(revisionVersion) && revisionVersion > 0 ? `Applied to the draft as version ${revisionVersion}.` : "Correction applied to the draft.";
|
|
3955
4021
|
setReviewStatusMessage(apply ? appliedMessage : "Correction saved for review.");
|
|
3956
|
-
if (apply && playbackState === "paused" &&
|
|
3957
|
-
socketRef.current
|
|
4022
|
+
if (apply && playbackState === "paused" && isActiveRef.current) {
|
|
4023
|
+
emitSocketEvent(socketRef.current, "tour:resume");
|
|
3958
4024
|
setPlaybackState("executing");
|
|
3959
4025
|
}
|
|
3960
4026
|
} catch (err) {
|
|
@@ -3978,14 +4044,14 @@ function useTourPlayback({
|
|
|
3978
4044
|
stopTour();
|
|
3979
4045
|
}, [stopTour]);
|
|
3980
4046
|
const pauseTour = useCallback7(() => {
|
|
3981
|
-
if (socketRef.current
|
|
3982
|
-
socketRef.current
|
|
4047
|
+
if (isSocketWritable(socketRef.current) && isActiveRef.current) {
|
|
4048
|
+
emitSocketEvent(socketRef.current, "tour:pause");
|
|
3983
4049
|
setPlaybackState("paused");
|
|
3984
4050
|
}
|
|
3985
4051
|
}, []);
|
|
3986
4052
|
const resumeTour = useCallback7(() => {
|
|
3987
|
-
if (socketRef.current
|
|
3988
|
-
socketRef.current
|
|
4053
|
+
if (isSocketWritable(socketRef.current) && isActiveRef.current) {
|
|
4054
|
+
emitSocketEvent(socketRef.current, "tour:resume");
|
|
3989
4055
|
setPlaybackState("executing");
|
|
3990
4056
|
}
|
|
3991
4057
|
}, []);
|
|
@@ -4007,9 +4073,9 @@ function useTourPlayback({
|
|
|
4007
4073
|
if (voiceInputResolveRef.current) {
|
|
4008
4074
|
console.log("[TourAgent] Resolving loop waiting_voice with text:", text);
|
|
4009
4075
|
voiceInputResolveRef.current(text);
|
|
4010
|
-
} else if (socketRef.current
|
|
4076
|
+
} else if (isSocketWritable(socketRef.current)) {
|
|
4011
4077
|
console.log("[TourAgent] Forwarding ambient voice to server:", text);
|
|
4012
|
-
socketRef.current
|
|
4078
|
+
emitSocketEvent(socketRef.current, "tour:user_input", {
|
|
4013
4079
|
transcript: text,
|
|
4014
4080
|
runId: runIdRef.current,
|
|
4015
4081
|
turnId: turnIdRef.current
|