callix-dialer-widget 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
6
6
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
7
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
8
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
- import React, { useState, useMemo, useRef, useEffect, useCallback, forwardRef, useLayoutEffect, useId, createContext, useImperativeHandle, StrictMode, useContext } from "react";
9
+ import React, { createContext, useMemo, useSyncExternalStore, useRef, useCallback, useContext, useEffect, useState, forwardRef, useLayoutEffect, useId, useImperativeHandle, StrictMode } from "react";
10
10
  import require$$0$2, { createPortal } from "react-dom";
11
11
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
12
12
  function getAugmentedNamespace(n) {
@@ -43725,6 +43725,790 @@ function requireClientSdkReact() {
43725
43725
  return clientSdkReact;
43726
43726
  }
43727
43727
  var clientSdkReactExports = requireClientSdkReact();
43728
+ class EngineSignal {
43729
+ constructor(value) {
43730
+ this.value = value;
43731
+ this.listeners = /* @__PURE__ */ new Set();
43732
+ }
43733
+ get() {
43734
+ return this.value;
43735
+ }
43736
+ set(value) {
43737
+ if (JSON.stringify(value) === JSON.stringify(this.value)) return;
43738
+ this.value = value;
43739
+ for (const listener of this.listeners) listener(value);
43740
+ }
43741
+ subscribe(fn) {
43742
+ this.listeners.add(fn);
43743
+ return () => this.listeners.delete(fn);
43744
+ }
43745
+ }
43746
+ const INITIAL_TRANSCRIPT = { segments: [], status: null, insight: null, interims: 0 };
43747
+ const MAX_SEGMENTS = 300;
43748
+ const RECONNECT_MIN_MS = 500;
43749
+ const RECONNECT_MAX_MS = 5e3;
43750
+ function engineWsUrl(baseUrl) {
43751
+ const url = new URL(baseUrl, typeof window !== "undefined" ? window.location.href : "http://localhost");
43752
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
43753
+ url.pathname = `${url.pathname.replace(/\/+$/, "")}/ws`;
43754
+ url.search = "";
43755
+ url.hash = "";
43756
+ return url.toString();
43757
+ }
43758
+ class EngineClient {
43759
+ constructor(options) {
43760
+ this.options = options;
43761
+ this.userSignal = new EngineSignal(void 0);
43762
+ this.listeners = /* @__PURE__ */ new Set();
43763
+ this.sessionLostListeners = /* @__PURE__ */ new Set();
43764
+ this.viewWaiters = [];
43765
+ this.ws = null;
43766
+ this.reconnectDelay = RECONNECT_MIN_MS;
43767
+ this.reconnectTimer = null;
43768
+ this.closed = false;
43769
+ this.iceServers = [{ urls: "stun:stun.l.google.com:19302" }];
43770
+ this.pc = null;
43771
+ this.connectionId = null;
43772
+ this.remoteSet = false;
43773
+ this.pendingCandidates = [];
43774
+ this.micStream = null;
43775
+ this.audioSeq = 0;
43776
+ this.unlockListener = null;
43777
+ this.agentId = options.agentId;
43778
+ this.session = options.session ?? null;
43779
+ this.log = options.log ?? (() => void 0);
43780
+ this.audio = options.audioElement !== void 0 ? options.audioElement : createAudioElement();
43781
+ this.state = {
43782
+ wsState: "idle",
43783
+ tabId: null,
43784
+ view: null,
43785
+ engaged: false,
43786
+ pcState: "idle",
43787
+ needsUnlock: false,
43788
+ micError: null,
43789
+ lastError: null,
43790
+ inputMuted: false,
43791
+ outputMuted: false,
43792
+ outputVolume: 100,
43793
+ transcript: INITIAL_TRANSCRIPT,
43794
+ sessionLost: null
43795
+ };
43796
+ }
43797
+ // ------------------------------------------------------------------ store
43798
+ getState() {
43799
+ return this.state;
43800
+ }
43801
+ subscribe(listener) {
43802
+ this.listeners.add(listener);
43803
+ return () => {
43804
+ this.listeners.delete(listener);
43805
+ };
43806
+ }
43807
+ setState(patch) {
43808
+ this.state = { ...this.state, ...patch };
43809
+ for (const listener of this.listeners) listener();
43810
+ }
43811
+ get currentCall() {
43812
+ var _a2, _b;
43813
+ return ((_b = (_a2 = this.state.view) == null ? void 0 : _a2.snapshot) == null ? void 0 : _b.leg.call) ?? null;
43814
+ }
43815
+ // ------------------------------------------------------------------ conexão
43816
+ connect() {
43817
+ if (this.closed) this.closed = false;
43818
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) return;
43819
+ const Impl = this.options.WebSocketImpl ?? WebSocket;
43820
+ this.setState({ wsState: "connecting" });
43821
+ const ws = new Impl(engineWsUrl(this.options.url));
43822
+ this.ws = ws;
43823
+ ws.onopen = () => {
43824
+ if (this.ws !== ws) return;
43825
+ this.reconnectDelay = RECONNECT_MIN_MS;
43826
+ this.setState({ wsState: "open" });
43827
+ const hello = { type: "hello", role: "tab", agentId: this.agentId, token: this.options.token, label: this.options.label ?? "Discador", ...this.session ? { session: this.session } : {} };
43828
+ this.send(hello);
43829
+ this.log("conectado ao host do motor");
43830
+ };
43831
+ ws.onmessage = (event) => {
43832
+ if (this.ws !== ws) return;
43833
+ let message;
43834
+ try {
43835
+ message = JSON.parse(String(event.data));
43836
+ } catch {
43837
+ return;
43838
+ }
43839
+ this.handle(message);
43840
+ };
43841
+ ws.onclose = () => {
43842
+ if (this.ws !== ws) return;
43843
+ this.ws = null;
43844
+ const wasEngaged = this.state.engaged;
43845
+ this.setState({ wsState: "closed", tabId: null, engaged: false });
43846
+ if (wasEngaged) this.stopAudio();
43847
+ if (this.closed) return;
43848
+ this.log(`conexão com o motor caiu; nova tentativa em ${this.reconnectDelay} ms`);
43849
+ this.reconnectTimer = setTimeout(() => {
43850
+ this.reconnectTimer = null;
43851
+ this.connect();
43852
+ }, this.reconnectDelay);
43853
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, RECONNECT_MAX_MS);
43854
+ };
43855
+ ws.onerror = () => void 0;
43856
+ }
43857
+ /** fecha de vez (sem reconectar) e solta o áudio */
43858
+ close() {
43859
+ this.closed = true;
43860
+ if (this.reconnectTimer) {
43861
+ clearTimeout(this.reconnectTimer);
43862
+ this.reconnectTimer = null;
43863
+ }
43864
+ this.stopAudio();
43865
+ const ws = this.ws;
43866
+ this.ws = null;
43867
+ if (ws) {
43868
+ ws.onclose = null;
43869
+ ws.close();
43870
+ }
43871
+ for (const waiter of this.viewWaiters.splice(0)) clearTimeout(waiter.timer);
43872
+ this.setState({ wsState: "closed", tabId: null, engaged: false });
43873
+ }
43874
+ /** a view atual, ou a próxima que o host mandar (welcome/view) */
43875
+ waitForView(timeoutMs = 15e3) {
43876
+ if (this.state.view && this.state.tabId) return Promise.resolve(this.state.view);
43877
+ return new Promise((resolve, reject) => {
43878
+ const timer = setTimeout(() => {
43879
+ const index = this.viewWaiters.findIndex((w) => w.timer === timer);
43880
+ if (index >= 0) this.viewWaiters.splice(index, 1);
43881
+ reject(new Error("o motor não respondeu"));
43882
+ }, timeoutMs);
43883
+ this.viewWaiters.push({ resolve, timer });
43884
+ });
43885
+ }
43886
+ send(message) {
43887
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(message));
43888
+ }
43889
+ handle(message) {
43890
+ switch (message.type) {
43891
+ case "welcome": {
43892
+ if (Array.isArray(message.iceServers) && message.iceServers.length > 0) this.iceServers = message.iceServers;
43893
+ this.setState({ tabId: message.tabId });
43894
+ this.applyView(message.view);
43895
+ this.log(`sou a aba ${message.tabId}`);
43896
+ if (this.options.autoEngage !== false && message.view.engagedTabId !== message.tabId) this.engage();
43897
+ break;
43898
+ }
43899
+ case "view":
43900
+ this.applyView(message.view);
43901
+ break;
43902
+ case "engage":
43903
+ this.setState({ engaged: message.engaged });
43904
+ if (message.engaged) void this.startAudio();
43905
+ else this.stopAudio();
43906
+ break;
43907
+ case "signal":
43908
+ void this.onSignal(message.payload);
43909
+ break;
43910
+ case "error":
43911
+ this.setState({ lastError: message.message });
43912
+ this.log(`erro do host: ${message.message}`);
43913
+ break;
43914
+ case "transcript":
43915
+ this.pushSegment(message.segment);
43916
+ break;
43917
+ case "transcript-sync":
43918
+ this.setState({ transcript: { segments: message.segments ?? [], status: message.status ?? null, insight: message.insight ?? null, interims: 0 } });
43919
+ break;
43920
+ case "insight": {
43921
+ const current = this.state.transcript.insight;
43922
+ if (!current || message.insight.revision >= current.revision || message.insight.callId !== current.callId) {
43923
+ this.setState({ transcript: { ...this.state.transcript, insight: message.insight } });
43924
+ }
43925
+ break;
43926
+ }
43927
+ case "transcript-status":
43928
+ this.onTranscriptStatus(message.status);
43929
+ break;
43930
+ }
43931
+ }
43932
+ applyView(view) {
43933
+ var _a2, _b;
43934
+ const previous = this.state.view;
43935
+ const lost = view.sessionLost ?? null;
43936
+ const newlyLost = Boolean(lost) && !(previous == null ? void 0 : previous.sessionLost);
43937
+ if (newlyLost) {
43938
+ this.session = null;
43939
+ this.log(`o Callix derrubou a sessão: ${(lost == null ? void 0 : lost.message) ?? ""}`);
43940
+ }
43941
+ const user = (_a2 = view.snapshot) == null ? void 0 : _a2.leg.user;
43942
+ this.userSignal.set(user ? { ...user, sessionId: 0 } : void 0);
43943
+ const call = (_b = view.snapshot) == null ? void 0 : _b.leg.call;
43944
+ const patch = { view, sessionLost: lost ? { originator: lost.originator, message: lost.message } : null };
43945
+ if (call && typeof call.muted === "boolean" && call.muted !== this.state.inputMuted && call.state !== "ended") patch.inputMuted = call.muted;
43946
+ this.setState(patch);
43947
+ for (const waiter of this.viewWaiters.splice(0)) {
43948
+ clearTimeout(waiter.timer);
43949
+ waiter.resolve(view);
43950
+ }
43951
+ if (newlyLost && lost) for (const listener of this.sessionLostListeners) listener(lost);
43952
+ }
43953
+ onSessionLost(listener) {
43954
+ this.sessionLostListeners.add(listener);
43955
+ return () => {
43956
+ this.sessionLostListeners.delete(listener);
43957
+ };
43958
+ }
43959
+ // ------------------------------------------------------------------ sessão e comandos
43960
+ /** modo sdk: o widget fez login no back-end e entrega a sessão ao host */
43961
+ setSession(session) {
43962
+ this.session = session;
43963
+ this.send({ type: "session", session });
43964
+ }
43965
+ engage() {
43966
+ this.send({ type: "engage" });
43967
+ }
43968
+ release() {
43969
+ this.send({ type: "release" });
43970
+ }
43971
+ command(command) {
43972
+ this.send({ type: "command", command });
43973
+ this.log(`comando: ${command.name}${command.phone ? ` ${command.phone}` : ""}`);
43974
+ }
43975
+ // ------------------------------------------------------------------ áudio
43976
+ setInputMuted(muted) {
43977
+ if (this.micStream) for (const track of this.micStream.getAudioTracks()) track.enabled = !muted;
43978
+ this.setState({ inputMuted: muted });
43979
+ if (this.currentCall) this.command({ name: muted ? "mute" : "unmute" });
43980
+ }
43981
+ setOutputMuted(muted) {
43982
+ if (this.audio) this.audio.muted = muted;
43983
+ this.setState({ outputMuted: muted });
43984
+ }
43985
+ setOutputVolume(volume) {
43986
+ const clamped = Math.max(0, Math.min(100, Math.round(volume)));
43987
+ if (this.audio) this.audio.volume = clamped / 100;
43988
+ this.setState({ outputVolume: clamped });
43989
+ }
43990
+ async setOutputDevice(deviceId) {
43991
+ const audio = this.audio;
43992
+ if (!(audio == null ? void 0 : audio.setSinkId)) throw new Error("este navegador não permite escolher a saída de som");
43993
+ await audio.setSinkId(deviceId);
43994
+ }
43995
+ /** depois de um toque do agente: tenta tocar o som do motor que o navegador segurou */
43996
+ async unlockAudio() {
43997
+ if (!this.audio || !this.audio.srcObject) return;
43998
+ try {
43999
+ await this.audio.play();
44000
+ this.setState({ needsUnlock: false });
44001
+ } catch {
44002
+ this.setState({ needsUnlock: true });
44003
+ }
44004
+ }
44005
+ async startAudio() {
44006
+ const seq = ++this.audioSeq;
44007
+ this.stopAudio();
44008
+ const mediaDevices = this.options.mediaDevices === void 0 ? typeof navigator !== "undefined" ? navigator.mediaDevices : null : this.options.mediaDevices;
44009
+ if (!(mediaDevices == null ? void 0 : mediaDevices.getUserMedia)) {
44010
+ this.setState({ micError: "Este navegador não permite usar o microfone pela página." });
44011
+ return;
44012
+ }
44013
+ let stream;
44014
+ try {
44015
+ stream = await mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } });
44016
+ } catch (error2) {
44017
+ const message = `Microfone indisponível: ${error2.message}`;
44018
+ this.setState({ micError: message });
44019
+ this.log(message);
44020
+ return;
44021
+ }
44022
+ if (seq !== this.audioSeq || !this.state.engaged) {
44023
+ for (const track of stream.getTracks()) track.stop();
44024
+ return;
44025
+ }
44026
+ this.micStream = stream;
44027
+ for (const track of stream.getAudioTracks()) track.enabled = !this.state.inputMuted;
44028
+ this.setState({ micError: null });
44029
+ const Impl = this.options.PeerConnectionImpl ?? RTCPeerConnection;
44030
+ const connection = new Impl({ iceServers: this.iceServers });
44031
+ this.pc = connection;
44032
+ const connectionId = Math.random().toString(36).slice(2, 10);
44033
+ this.connectionId = connectionId;
44034
+ this.remoteSet = false;
44035
+ this.pendingCandidates = [];
44036
+ for (const track of stream.getAudioTracks()) connection.addTrack(track, stream);
44037
+ connection.onicecandidate = (event) => {
44038
+ this.send({ type: "signal", payload: { connectionId, candidate: event.candidate ? event.candidate.toJSON() : null } });
44039
+ };
44040
+ connection.ontrack = (event) => {
44041
+ const remote = event.streams[0] ?? new MediaStream([event.track]);
44042
+ if (this.audio) {
44043
+ this.audio.srcObject = remote;
44044
+ this.audio.muted = this.state.outputMuted;
44045
+ this.audio.volume = this.state.outputVolume / 100;
44046
+ this.audio.play().then(() => this.setState({ needsUnlock: false })).catch(() => {
44047
+ this.setState({ needsUnlock: true });
44048
+ this.armUnlock();
44049
+ });
44050
+ }
44051
+ this.log("áudio do motor chegou");
44052
+ };
44053
+ connection.onconnectionstatechange = () => {
44054
+ if (this.pc !== connection) return;
44055
+ this.setState({ pcState: connection.connectionState });
44056
+ this.log(`WebRTC: ${connection.connectionState}`);
44057
+ if (connection.connectionState === "failed" && this.state.engaged) {
44058
+ this.log("conexão de áudio com o motor falhou; renegociando");
44059
+ void this.startAudio();
44060
+ }
44061
+ };
44062
+ const offer = await connection.createOffer();
44063
+ if (this.pc !== connection) return;
44064
+ await connection.setLocalDescription(offer);
44065
+ this.send({ type: "signal", payload: { connectionId, description: { type: offer.type, sdp: offer.sdp } } });
44066
+ this.setState({ pcState: connection.connectionState });
44067
+ }
44068
+ stopAudio() {
44069
+ if (this.pc) {
44070
+ this.pc.onconnectionstatechange = null;
44071
+ this.pc.onicecandidate = null;
44072
+ this.pc.ontrack = null;
44073
+ this.pc.close();
44074
+ this.pc = null;
44075
+ }
44076
+ this.connectionId = null;
44077
+ if (this.micStream) {
44078
+ for (const track of this.micStream.getTracks()) track.stop();
44079
+ this.micStream = null;
44080
+ }
44081
+ if (this.audio) this.audio.srcObject = null;
44082
+ if (this.state.pcState !== "idle") this.setState({ pcState: "idle" });
44083
+ }
44084
+ async onSignal(payload) {
44085
+ const connection = this.pc;
44086
+ if (!connection) return;
44087
+ if (payload.connectionId && payload.connectionId !== this.connectionId) return;
44088
+ try {
44089
+ if (payload.description) {
44090
+ await connection.setRemoteDescription(payload.description);
44091
+ this.remoteSet = true;
44092
+ for (const candidate of this.pendingCandidates.splice(0)) await connection.addIceCandidate(candidate).catch(() => void 0);
44093
+ }
44094
+ if (payload.candidate !== void 0) {
44095
+ const candidate = payload.candidate ?? void 0;
44096
+ if (!this.remoteSet && candidate) this.pendingCandidates.push(candidate);
44097
+ else await connection.addIceCandidate(candidate).catch(() => void 0);
44098
+ }
44099
+ } catch (error2) {
44100
+ this.log(`sinalização: ${error2.message}`);
44101
+ }
44102
+ }
44103
+ /** o próximo toque/tecla na página libera o som (política de autoplay) */
44104
+ armUnlock() {
44105
+ if (this.unlockListener || typeof document === "undefined") return;
44106
+ const listener = () => {
44107
+ document.removeEventListener("pointerdown", listener, true);
44108
+ document.removeEventListener("keydown", listener, true);
44109
+ this.unlockListener = null;
44110
+ void this.unlockAudio();
44111
+ };
44112
+ this.unlockListener = listener;
44113
+ document.addEventListener("pointerdown", listener, true);
44114
+ document.addEventListener("keydown", listener, true);
44115
+ }
44116
+ // ------------------------------------------------------------------ transcrição
44117
+ pushSegment(segment) {
44118
+ const current = this.state.transcript;
44119
+ let segments = current.segments;
44120
+ let insight = current.insight;
44121
+ let interims = current.interims;
44122
+ if (segments.length && segments[0].callId !== segment.callId) {
44123
+ segments = [];
44124
+ insight = null;
44125
+ interims = 0;
44126
+ }
44127
+ const index = segments.findIndex((s) => s.id === segment.id);
44128
+ const next = index >= 0 ? segments.map((s, i2) => i2 === index ? segment : s) : [...segments, segment];
44129
+ if (next.length > MAX_SEGMENTS) next.shift();
44130
+ if (segment.interim) interims += 1;
44131
+ this.setState({ transcript: { ...current, segments: next, insight, interims } });
44132
+ }
44133
+ onTranscriptStatus(status) {
44134
+ const current = this.state.transcript;
44135
+ if (!current.status || current.status.recordingId === status.recordingId || status.live === "running") {
44136
+ const fresh = current.status && current.status.recordingId !== status.recordingId;
44137
+ this.setState({ transcript: { ...current, status, ...fresh ? { segments: [], insight: null, interims: 0 } : {} } });
44138
+ }
44139
+ }
44140
+ }
44141
+ function createAudioElement() {
44142
+ if (typeof document === "undefined") return null;
44143
+ const audio = document.createElement("audio");
44144
+ audio.autoplay = true;
44145
+ audio.setAttribute("playsinline", "true");
44146
+ audio.style.display = "none";
44147
+ audio.dataset.callixEngineAudio = "true";
44148
+ document.body.appendChild(audio);
44149
+ return audio;
44150
+ }
44151
+ const EngineClientContext = createContext(null);
44152
+ function EngineClientProvider({ client: client2, children: children2 }) {
44153
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(EngineClientContext.Provider, { value: client2, children: children2 });
44154
+ }
44155
+ function useEngineClient() {
44156
+ return useContext(EngineClientContext);
44157
+ }
44158
+ function useEngineState(client2) {
44159
+ return useSyncExternalStore(client2.subscribe.bind(client2), client2.getState.bind(client2), client2.getState.bind(client2));
44160
+ }
44161
+ const ENGINE_BREAKS = [{ id: 0, name: "Pausa" }];
44162
+ function toCallQualification(q) {
44163
+ return { id: q.id, name: q.name, isSystemQualification: false, systemQualificationType: 0 };
44164
+ }
44165
+ function qualificationsFromView(view, kind) {
44166
+ var _a2;
44167
+ return (((_a2 = view == null ? void 0 : view.snapshot) == null ? void 0 : _a2.leg.qualifications) ?? []).filter((q) => q.kind === kind).map(toCallQualification);
44168
+ }
44169
+ class EngineCall {
44170
+ constructor(client2, snapshot) {
44171
+ this.client = client2;
44172
+ this.listeners = /* @__PURE__ */ new Map();
44173
+ this.snapshot = snapshot;
44174
+ }
44175
+ get id() {
44176
+ return this.snapshot.id;
44177
+ }
44178
+ get direction() {
44179
+ return this.snapshot.direction;
44180
+ }
44181
+ get data() {
44182
+ var _a2;
44183
+ return {
44184
+ destinationNumber: (_a2 = this.snapshot.contact) == null ? void 0 : _a2.phone,
44185
+ campaignContactPhoneId: this.snapshot.campaignContactPhoneId,
44186
+ answeredAt: this.snapshot.answeredAt ? new Date(this.snapshot.answeredAt) : void 0
44187
+ };
44188
+ }
44189
+ get callerNumber() {
44190
+ var _a2;
44191
+ return (_a2 = this.snapshot.contact) == null ? void 0 : _a2.phone;
44192
+ }
44193
+ get peerDisplayName() {
44194
+ var _a2;
44195
+ return (_a2 = this.snapshot.contact) == null ? void 0 : _a2.name;
44196
+ }
44197
+ get isEstablished() {
44198
+ return this.snapshot.state === "active";
44199
+ }
44200
+ get isEnded() {
44201
+ return this.snapshot.state === "ended";
44202
+ }
44203
+ /** segundos desde o atendimento (ou desde o início, enquanto toca) */
44204
+ get currentDurationSeconds() {
44205
+ const from = this.snapshot.answeredAt ?? this.snapshot.startedAt;
44206
+ return Math.max(0, Math.floor((Date.now() - from) / 1e3));
44207
+ }
44208
+ answer() {
44209
+ this.client.command({ name: "answer" });
44210
+ }
44211
+ reject() {
44212
+ this.client.command({ name: "reject" });
44213
+ }
44214
+ hangup() {
44215
+ this.client.command({ name: "hangup" });
44216
+ }
44217
+ isOnHold() {
44218
+ return { local: false, remote: false };
44219
+ }
44220
+ async hold() {
44221
+ }
44222
+ async unhold() {
44223
+ }
44224
+ isMuted() {
44225
+ return this.client.getState().inputMuted;
44226
+ }
44227
+ mute() {
44228
+ this.client.setInputMuted(true);
44229
+ }
44230
+ unmute() {
44231
+ this.client.setInputMuted(false);
44232
+ }
44233
+ sendDtmf(tones) {
44234
+ this.client.command({ name: "dtmf", tones });
44235
+ }
44236
+ on(event, listener) {
44237
+ let set = this.listeners.get(event);
44238
+ if (!set) {
44239
+ set = /* @__PURE__ */ new Set();
44240
+ this.listeners.set(event, set);
44241
+ }
44242
+ set.add(listener);
44243
+ return this;
44244
+ }
44245
+ once(event, listener) {
44246
+ const wrapped = (...args) => {
44247
+ this.off(event, wrapped);
44248
+ listener(...args);
44249
+ };
44250
+ return this.on(event, wrapped);
44251
+ }
44252
+ off(event, listener) {
44253
+ var _a2;
44254
+ (_a2 = this.listeners.get(event)) == null ? void 0 : _a2.delete(listener);
44255
+ return this;
44256
+ }
44257
+ toCallData() {
44258
+ var _a2;
44259
+ return {
44260
+ id: this.snapshot.id,
44261
+ direction: this.snapshot.direction,
44262
+ destinationNumber: ((_a2 = this.snapshot.contact) == null ? void 0 : _a2.phone) ?? "",
44263
+ answeredAt: this.snapshot.answeredAt ? new Date(this.snapshot.answeredAt).toISOString() : void 0,
44264
+ campaignContactPhoneId: this.snapshot.campaignContactPhoneId,
44265
+ isMuted: this.snapshot.muted
44266
+ };
44267
+ }
44268
+ }
44269
+ function operatorStateFromView(view, call) {
44270
+ var _a2, _b, _c;
44271
+ if (!view) return void 0;
44272
+ if (((_a2 = view.sessionLost) == null ? void 0 : _a2.originator) === "anotherDevice") return { state: "startupError", error: "userIsAlreadyConnectedOnAnotherDevice" };
44273
+ const leg = (_b = view.snapshot) == null ? void 0 : _b.leg;
44274
+ if (!leg) {
44275
+ if (view.engine === "starting") return { state: "starting" };
44276
+ if (view.engine === "running") return { state: "starting" };
44277
+ return { state: "offline" };
44278
+ }
44279
+ const asCall = call ?? void 0;
44280
+ switch (leg.status) {
44281
+ case "starting":
44282
+ return { state: "starting" };
44283
+ case "available":
44284
+ return { state: "idle" };
44285
+ case "ringing":
44286
+ return { state: "callRinging", call: asCall };
44287
+ case "dialing":
44288
+ return ((_c = leg.call) == null ? void 0 : _c.state) === "ringing" ? { state: "manualCallRinging", call: asCall } : { state: "manualCallSetup", call: asCall };
44289
+ case "inCall":
44290
+ return { state: "callInProgress", call: asCall };
44291
+ case "afterCall":
44292
+ return { state: "afterCall" };
44293
+ case "onBreak":
44294
+ return { state: "onBreak", break: ENGINE_BREAKS[0] };
44295
+ case "offline":
44296
+ return { state: "offline" };
44297
+ case "error":
44298
+ return { state: "startupError", error: "unknownError" };
44299
+ }
44300
+ }
44301
+ function callInfoFromView(view) {
44302
+ var _a2;
44303
+ const leg = (_a2 = view == null ? void 0 : view.snapshot) == null ? void 0 : _a2.leg;
44304
+ const call = leg == null ? void 0 : leg.call;
44305
+ if (!leg || !call) return void 0;
44306
+ const successQualifications = qualificationsFromView(view, "success");
44307
+ const discardQualifications = qualificationsFromView(view, "discard");
44308
+ const contact = call.contact;
44309
+ if (contact == null ? void 0 : contact.campaign) {
44310
+ const info = {
44311
+ campaign: {
44312
+ id: 0,
44313
+ name: contact.campaign,
44314
+ type: 0,
44315
+ requireCallComments: false,
44316
+ enableTransfer: false,
44317
+ enableScheduledCall: false,
44318
+ successQualifications,
44319
+ discardQualifications,
44320
+ previewDuration: "",
44321
+ showCampaignModelOnService: false,
44322
+ skipCallQualification: false
44323
+ },
44324
+ campaignList: { id: 0, name: "" },
44325
+ campaignContact: { id: call.campaignContactPhoneId ?? 0, label: contact.name, phoneNumber: contact.phone }
44326
+ };
44327
+ return { type: "campaign", info };
44328
+ }
44329
+ return { type: "manual", info: { successQualifications, discardQualifications } };
44330
+ }
44331
+ function useEngineCall(client2) {
44332
+ var _a2, _b;
44333
+ const state = useEngineState(client2);
44334
+ const snapshot = ((_b = (_a2 = state.view) == null ? void 0 : _a2.snapshot) == null ? void 0 : _b.leg.call) ?? null;
44335
+ const ref = useRef(null);
44336
+ if (!snapshot || snapshot.state === "ended") {
44337
+ ref.current = null;
44338
+ } else if (!ref.current || ref.current.id !== snapshot.id) {
44339
+ ref.current = new EngineCall(client2, snapshot);
44340
+ } else {
44341
+ ref.current.snapshot = snapshot;
44342
+ }
44343
+ return ref.current;
44344
+ }
44345
+ function useEngineOperatorState(client2) {
44346
+ const state = useEngineState(client2);
44347
+ const call = useEngineCall(client2);
44348
+ return useMemo(() => operatorStateFromView(state.view, call), [state.view, call]);
44349
+ }
44350
+ function useEngineCurrentCall(client2) {
44351
+ const call = useEngineCall(client2);
44352
+ return call ?? void 0;
44353
+ }
44354
+ function useEngineCurrentCallInfo(client2) {
44355
+ var _a2, _b, _c, _d, _e;
44356
+ const state = useEngineState(client2);
44357
+ const view = state.view;
44358
+ const key = JSON.stringify([(_b = (_a2 = view == null ? void 0 : view.snapshot) == null ? void 0 : _a2.leg.call) == null ? void 0 : _b.id, (_d = (_c = view == null ? void 0 : view.snapshot) == null ? void 0 : _c.leg.call) == null ? void 0 : _d.contact, (_e = view == null ? void 0 : view.snapshot) == null ? void 0 : _e.leg.qualifications]);
44359
+ return useMemo(() => callInfoFromView(view), [key]);
44360
+ }
44361
+ function useEngineQualifications(client2, kind) {
44362
+ var _a2, _b;
44363
+ const state = useEngineState(client2);
44364
+ const raw = ((_b = (_a2 = state.view) == null ? void 0 : _a2.snapshot) == null ? void 0 : _b.leg.qualifications) ?? [];
44365
+ const key = JSON.stringify(raw);
44366
+ return useMemo(() => qualificationsFromView(state.view, kind), [key, kind]);
44367
+ }
44368
+ function useEngineControls(client2) {
44369
+ return useMemo(
44370
+ () => ({
44371
+ becomeAvailable: () => {
44372
+ var _a2, _b;
44373
+ const status = (_b = (_a2 = client2.getState().view) == null ? void 0 : _a2.snapshot) == null ? void 0 : _b.leg.status;
44374
+ if (status === "onBreak") client2.command({ name: "resume" });
44375
+ },
44376
+ goOffline: () => void 0,
44377
+ enterOnBreak: () => client2.command({ name: "pause" }),
44378
+ makeManualCall: (phoneNumber) => client2.command({ name: "dial", phone: phoneNumber }),
44379
+ finishAfterCall: (data) => {
44380
+ if (typeof data.qualificationId === "number") client2.command({ name: "qualify", qualificationId: data.qualificationId, result: data.result === "failure" ? "discard" : "success" });
44381
+ else client2.command({ name: "finish" });
44382
+ }
44383
+ }),
44384
+ [client2]
44385
+ );
44386
+ }
44387
+ function useEngineCurrentCallControls(client2) {
44388
+ return useMemo(
44389
+ () => ({
44390
+ answer: () => client2.command({ name: "answer" }),
44391
+ reject: () => client2.command({ name: "reject" }),
44392
+ hangup: () => client2.command({ name: "hangup" }),
44393
+ isOnHold: () => ({ local: false, remote: false }),
44394
+ hold: async () => void 0,
44395
+ unhold: async () => void 0,
44396
+ isMuted: () => client2.getState().inputMuted,
44397
+ mute: () => client2.setInputMuted(true),
44398
+ unmute: () => client2.setInputMuted(false)
44399
+ }),
44400
+ [client2]
44401
+ );
44402
+ }
44403
+ function useEngineAudioInputMuted(client2) {
44404
+ const state = useEngineState(client2);
44405
+ const set = useCallback((muted) => client2.setInputMuted(muted), [client2]);
44406
+ return [state.inputMuted, set];
44407
+ }
44408
+ function useEngineAudioOutputMuted(client2) {
44409
+ const state = useEngineState(client2);
44410
+ const set = useCallback((muted) => client2.setOutputMuted(muted), [client2]);
44411
+ return [state.outputMuted, set];
44412
+ }
44413
+ function useEngineAudioOutputVolume(client2) {
44414
+ const state = useEngineState(client2);
44415
+ const set = useCallback((volume) => client2.setOutputVolume(volume), [client2]);
44416
+ return [state.outputVolume, set];
44417
+ }
44418
+ function useEngineEventListener(client2, event, listener) {
44419
+ const ref = useRef(listener);
44420
+ ref.current = listener;
44421
+ useEffect(() => {
44422
+ return client2.onSessionLost((lost) => {
44423
+ ref.current({ originator: lost.originator });
44424
+ });
44425
+ }, [client2, event]);
44426
+ }
44427
+ const operators = /* @__PURE__ */ new WeakMap();
44428
+ function engineOperator(client2) {
44429
+ let operator = operators.get(client2);
44430
+ if (!operator) {
44431
+ const breaks = new EngineSignal(ENGINE_BREAKS);
44432
+ operator = {
44433
+ breaks,
44434
+ user: client2.userSignal,
44435
+ audioOutput: {
44436
+ setAudioOutputDevice: (id) => void client2.setOutputDevice(id).catch(() => void 0),
44437
+ setVolume: (volume) => client2.setOutputVolume(volume),
44438
+ setMute: (muted) => client2.setOutputMuted(muted)
44439
+ },
44440
+ stop: () => void 0,
44441
+ goOffline: () => void 0,
44442
+ becomeAvailable: () => {
44443
+ var _a2, _b;
44444
+ if (((_b = (_a2 = client2.getState().view) == null ? void 0 : _a2.snapshot) == null ? void 0 : _b.leg.status) === "onBreak") client2.command({ name: "resume" });
44445
+ },
44446
+ enterOnBreak: () => client2.command({ name: "pause" }),
44447
+ makeManualCall: (phoneNumber) => client2.command({ name: "dial", phone: phoneNumber }),
44448
+ finishAfterCall: (data) => {
44449
+ if (typeof data.qualificationId === "number") client2.command({ name: "qualify", qualificationId: data.qualificationId, result: data.result === "failure" ? "discard" : "success" });
44450
+ else client2.command({ name: "finish" });
44451
+ }
44452
+ };
44453
+ operators.set(client2, operator);
44454
+ }
44455
+ return operator;
44456
+ }
44457
+ function useEngineOperator(client2) {
44458
+ return useMemo(() => engineOperator(client2), [client2]);
44459
+ }
44460
+ function useEngineTranscript(client2) {
44461
+ return useEngineState(client2).transcript;
44462
+ }
44463
+ function useCallOperator() {
44464
+ const engine = useContext(EngineClientContext);
44465
+ return engine ? useEngineOperator(engine) : clientSdkReactExports.useCallOperator();
44466
+ }
44467
+ function useCallOperatorState() {
44468
+ const engine = useContext(EngineClientContext);
44469
+ return engine ? useEngineOperatorState(engine) : clientSdkReactExports.useCallOperatorState();
44470
+ }
44471
+ function useCallOperatorControls() {
44472
+ const engine = useContext(EngineClientContext);
44473
+ return engine ? useEngineControls(engine) : clientSdkReactExports.useCallOperatorControls();
44474
+ }
44475
+ function useCallOperatorCurrentCall() {
44476
+ const engine = useContext(EngineClientContext);
44477
+ return engine ? useEngineCurrentCall(engine) : clientSdkReactExports.useCallOperatorCurrentCall();
44478
+ }
44479
+ function useCallOperatorCurrentCallInfo() {
44480
+ const engine = useContext(EngineClientContext);
44481
+ return engine ? useEngineCurrentCallInfo(engine) : clientSdkReactExports.useCallOperatorCurrentCallInfo();
44482
+ }
44483
+ function useCallOperatorSuccessQualifications() {
44484
+ const engine = useContext(EngineClientContext);
44485
+ return engine ? useEngineQualifications(engine, "success") : clientSdkReactExports.useCallOperatorSuccessQualifications();
44486
+ }
44487
+ function useCallOperatorDiscardQualifications() {
44488
+ const engine = useContext(EngineClientContext);
44489
+ return engine ? useEngineQualifications(engine, "discard") : clientSdkReactExports.useCallOperatorDiscardQualifications();
44490
+ }
44491
+ function useCallOperatorCurrentCallControls() {
44492
+ const engine = useContext(EngineClientContext);
44493
+ return engine ? useEngineCurrentCallControls(engine) : clientSdkReactExports.useCallOperatorCurrentCallControls();
44494
+ }
44495
+ function useCallOperatorAudioInputMuted() {
44496
+ const engine = useContext(EngineClientContext);
44497
+ return engine ? useEngineAudioInputMuted(engine) : clientSdkReactExports.useCallOperatorAudioInputMuted();
44498
+ }
44499
+ function useCallOperatorAudioOutputMuted() {
44500
+ const engine = useContext(EngineClientContext);
44501
+ return engine ? useEngineAudioOutputMuted(engine) : clientSdkReactExports.useCallOperatorAudioOutputMuted();
44502
+ }
44503
+ function useCallOperatorAudioOutputVolume() {
44504
+ const engine = useContext(EngineClientContext);
44505
+ return engine ? useEngineAudioOutputVolume(engine) : clientSdkReactExports.useCallOperatorAudioOutputVolume();
44506
+ }
44507
+ function useCallOperatorEventListener(event, listener) {
44508
+ const engine = useContext(EngineClientContext);
44509
+ if (engine) useEngineEventListener(engine, event, listener);
44510
+ else clientSdkReactExports.useCallOperatorEventListener(event, listener);
44511
+ }
43728
44512
  var DefaultContext = {
43729
44513
  color: void 0,
43730
44514
  size: void 0,
@@ -48745,8 +49529,8 @@ function formatPhoneValue(raw) {
48745
49529
  }
48746
49530
  function CallInfoDisplay() {
48747
49531
  var _a2;
48748
- const callInfo = clientSdkReactExports.useCallOperatorCurrentCallInfo();
48749
- const currentCall = clientSdkReactExports.useCallOperatorCurrentCall();
49532
+ const callInfo = useCallOperatorCurrentCallInfo();
49533
+ const currentCall = useCallOperatorCurrentCall();
48750
49534
  const campaignContactPhoneId = ((_a2 = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _a2.campaignContactPhoneId) ?? null;
48751
49535
  if ((callInfo == null ? void 0 : callInfo.type) === "campaign") {
48752
49536
  return /* @__PURE__ */ jsxRuntimeExports.jsx(CampaignCallInfoDisplay, { campaignCallInfo: callInfo.info, campaignContactPhoneId });
@@ -48892,7 +49676,7 @@ function useSignal(signal) {
48892
49676
  return value;
48893
49677
  }
48894
49678
  function useOperatorBreak() {
48895
- const { enterOnBreak, becomeAvailable } = clientSdkReactExports.useCallOperatorControls();
49679
+ const { enterOnBreak, becomeAvailable } = useCallOperatorControls();
48896
49680
  return {
48897
49681
  // Use the SDK's native method
48898
49682
  goOnBreak: enterOnBreak,
@@ -52189,19 +52973,19 @@ function AuthenticatedDialer({
52189
52973
  const isMiniMode = variant === "min";
52190
52974
  const { skipManualQualification } = useDialerConfig();
52191
52975
  console.log("[FloatingDialer/AuthenticatedDialer] 🔧 skipManualQualification:", skipManualQualification);
52192
- const operator = clientSdkReactExports.useCallOperator();
52193
- const operatorState = clientSdkReactExports.useCallOperatorState();
52194
- const currentCall = clientSdkReactExports.useCallOperatorCurrentCall();
52195
- const currentCallInfo = clientSdkReactExports.useCallOperatorCurrentCallInfo();
52196
- const rawSuccessQualifications = clientSdkReactExports.useCallOperatorSuccessQualifications();
52197
- const rawDiscardQualifications = clientSdkReactExports.useCallOperatorDiscardQualifications();
52976
+ const operator = useCallOperator();
52977
+ const operatorState = useCallOperatorState();
52978
+ const currentCall = useCallOperatorCurrentCall();
52979
+ const currentCallInfo = useCallOperatorCurrentCallInfo();
52980
+ const rawSuccessQualifications = useCallOperatorSuccessQualifications();
52981
+ const rawDiscardQualifications = useCallOperatorDiscardQualifications();
52198
52982
  const successQualifications = useMemo(() => rawSuccessQualifications ?? [], [rawSuccessQualifications]);
52199
52983
  const discardQualifications = useMemo(() => rawDiscardQualifications ?? [], [rawDiscardQualifications]);
52200
- const { makeManualCall, becomeAvailable, finishAfterCall } = clientSdkReactExports.useCallOperatorControls();
52201
- const { hangup, answer, reject } = clientSdkReactExports.useCallOperatorCurrentCallControls();
52202
- const [inputMuted, setInputMuted] = clientSdkReactExports.useCallOperatorAudioInputMuted();
52203
- const [outputMuted, setOutputMuted] = clientSdkReactExports.useCallOperatorAudioOutputMuted();
52204
- const [outputVolume, setOutputVolume] = clientSdkReactExports.useCallOperatorAudioOutputVolume();
52984
+ const { makeManualCall, becomeAvailable, finishAfterCall } = useCallOperatorControls();
52985
+ const { hangup, answer, reject } = useCallOperatorCurrentCallControls();
52986
+ const [inputMuted, setInputMuted] = useCallOperatorAudioInputMuted();
52987
+ const [outputMuted, setOutputMuted] = useCallOperatorAudioOutputMuted();
52988
+ const [outputVolume, setOutputVolume] = useCallOperatorAudioOutputVolume();
52205
52989
  useEffect(() => {
52206
52990
  console.log("[FloatingDialer] Estado do operador mudou:", {
52207
52991
  state: operatorState == null ? void 0 : operatorState.state,
@@ -52234,7 +53018,7 @@ function AuthenticatedDialer({
52234
53018
  const visibleInputRef = useRef(null);
52235
53019
  const [callError, setCallError] = useState(null);
52236
53020
  const [availabilityError, setAvailabilityError] = useState(null);
52237
- clientSdkReactExports.useCallOperatorEventListener("userSessionDropped", (data) => {
53021
+ useCallOperatorEventListener("userSessionDropped", (data) => {
52238
53022
  const notice = describeSessionDrop(data == null ? void 0 : data.originator);
52239
53023
  setAvailabilityError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
52240
53024
  });
@@ -56837,9 +57621,69 @@ function DialerNoticeBanner({ notice, onDismiss, onRecheck, onHeightChange }) {
56837
57621
  dismissible && onDismiss && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "dialer-notice-close", onClick: onDismiss, "aria-label": "Fechar aviso", children: /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-xmark", "aria-hidden": "true" }) })
56838
57622
  ] });
56839
57623
  }
57624
+ const EMOTION_LABEL = {
57625
+ raiva: "raiva",
57626
+ irritacao: "irritação",
57627
+ frustracao: "frustração",
57628
+ desconfianca: "desconfiança",
57629
+ impaciencia: "impaciência",
57630
+ confusao: "confusão",
57631
+ nervosismo: "nervosismo",
57632
+ surpresa: "surpresa",
57633
+ desanimo: "desânimo",
57634
+ cansaco: "cansaço",
57635
+ interesse: "interesse",
57636
+ entusiasmo: "entusiasmo",
57637
+ satisfacao: "satisfação",
57638
+ alivio: "alívio",
57639
+ neutro: "neutro"
57640
+ };
57641
+ const STANCE_LABEL = {
57642
+ aberto: "cliente aberto",
57643
+ neutro: "cliente neutro",
57644
+ resistente: "cliente resistente",
57645
+ hostil: "cliente hostil",
57646
+ indefinido: "lendo a conversa"
57647
+ };
57648
+ const SPEAKER_LABEL = { agent: "Você", customer: "Cliente", mixed: "" };
57649
+ function topEmotions(segment, max2 = 2) {
57650
+ return (segment.emotions ?? []).filter((e) => e.label !== "neutro" && e.intensity >= 0.3).slice(0, max2);
57651
+ }
57652
+ function LiveCaptionPanel({ maxLines = 4 }) {
57653
+ const client2 = useEngineClient();
57654
+ if (!client2) return null;
57655
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(LiveCaptionPanelInner, { client: client2, maxLines });
57656
+ }
57657
+ function LiveCaptionPanelInner({ client: client2, maxLines }) {
57658
+ const { segments, insight, status } = useEngineTranscript(client2);
57659
+ const listening = (status == null ? void 0 : status.live) === "running";
57660
+ if (segments.length === 0 && !insight && !listening) return null;
57661
+ const rows = segments.slice(-maxLines);
57662
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "live-captions", id: "liveCaptions", "aria-live": "polite", children: [
57663
+ insight && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `live-insight stance-${insight.customer.stance}`, id: "liveInsight", title: insight.summary, children: [
57664
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "live-insight-stance", children: STANCE_LABEL[insight.customer.stance] ?? insight.customer.stance }),
57665
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "live-insight-interest", children: [
57666
+ "interesse ",
57667
+ Math.round(insight.customer.interest),
57668
+ "%"
57669
+ ] }),
57670
+ insight.nextMove && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "live-insight-next", children: insight.nextMove })
57671
+ ] }),
57672
+ rows.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "live-caption-list", children: rows.map((segment) => /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: `live-caption ${segment.speaker}${segment.interim ? " interim" : ""}${segment.refined ? " refined" : ""}`, children: [
57673
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "live-caption-speaker", children: SPEAKER_LABEL[segment.speaker] }),
57674
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "live-caption-text", children: segment.text }),
57675
+ topEmotions(segment).map((emotion) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: `live-caption-emotion${emotion.source === "context" ? " ctx" : ""}`, title: emotion.evidence ?? "", children: [
57676
+ EMOTION_LABEL[emotion.label] ?? emotion.label,
57677
+ " ",
57678
+ Math.round(emotion.intensity * 100),
57679
+ "%"
57680
+ ] }, emotion.label))
57681
+ ] }, segment.id)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "live-caption-waiting", children: "Ouvindo a conversa…" })
57682
+ ] });
57683
+ }
56840
57684
  function useDialerNotices(options = {}) {
56841
- const operatorState = clientSdkReactExports.useCallOperatorState();
56842
- const currentCall = clientSdkReactExports.useCallOperatorCurrentCall();
57685
+ const operatorState = useCallOperatorState();
57686
+ const currentCall = useCallOperatorCurrentCall();
56843
57687
  const [deviceIssue, setDeviceIssue] = useState(null);
56844
57688
  const [sessionDrop, setSessionDrop] = useState(null);
56845
57689
  const [callFailure, setCallFailure] = useState(null);
@@ -56869,7 +57713,7 @@ function useDialerNotices(options = {}) {
56869
57713
  (_a3 = mediaDevices == null ? void 0 : mediaDevices.removeEventListener) == null ? void 0 : _a3.call(mediaDevices, "devicechange", handler);
56870
57714
  };
56871
57715
  }, [options.checkDevices]);
56872
- clientSdkReactExports.useCallOperatorEventListener("userSessionDropped", (data) => {
57716
+ useCallOperatorEventListener("userSessionDropped", (data) => {
56873
57717
  setSessionDrop((data == null ? void 0 : data.originator) ?? "unknown");
56874
57718
  });
56875
57719
  useEffect(() => {
@@ -57762,21 +58606,21 @@ function AuthenticatedMobileDialer({
57762
58606
  const [speakerDeviceId, setSpeakerDeviceId] = useState(null);
57763
58607
  const [defaultDeviceId, setDefaultDeviceId] = useState("default");
57764
58608
  const [speakerSupported, setSpeakerSupported] = useState(null);
57765
- const operator = clientSdkReactExports.useCallOperator();
57766
- const operatorState = clientSdkReactExports.useCallOperatorState();
57767
- const currentCall = clientSdkReactExports.useCallOperatorCurrentCall();
57768
- const { hangup, answer, reject } = clientSdkReactExports.useCallOperatorCurrentCallControls();
57769
- const callInfo = clientSdkReactExports.useCallOperatorCurrentCallInfo();
57770
- const rawSuccessQualifications = clientSdkReactExports.useCallOperatorSuccessQualifications();
57771
- const rawDiscardQualifications = clientSdkReactExports.useCallOperatorDiscardQualifications();
58609
+ const operator = useCallOperator();
58610
+ const operatorState = useCallOperatorState();
58611
+ const currentCall = useCallOperatorCurrentCall();
58612
+ const { hangup, answer, reject } = useCallOperatorCurrentCallControls();
58613
+ const callInfo = useCallOperatorCurrentCallInfo();
58614
+ const rawSuccessQualifications = useCallOperatorSuccessQualifications();
58615
+ const rawDiscardQualifications = useCallOperatorDiscardQualifications();
57772
58616
  const successQualifications = useMemo(() => rawSuccessQualifications ?? [], [rawSuccessQualifications]);
57773
58617
  const discardQualifications = useMemo(() => rawDiscardQualifications ?? [], [rawDiscardQualifications]);
57774
- const { makeManualCall, finishAfterCall } = clientSdkReactExports.useCallOperatorControls();
58618
+ const { makeManualCall, finishAfterCall } = useCallOperatorControls();
57775
58619
  const { goOnBreak, goOffBreak } = useOperatorBreak();
57776
58620
  const breaks = useSignal(operator == null ? void 0 : operator.breaks) || [];
57777
58621
  const userData = useSignal(operator == null ? void 0 : operator.user);
57778
58622
  const isOnBreak = (operatorState == null ? void 0 : operatorState.state) === "onBreak";
57779
- const [inputMuted, setInputMuted] = clientSdkReactExports.useCallOperatorAudioInputMuted();
58623
+ const [inputMuted, setInputMuted] = useCallOperatorAudioInputMuted();
57780
58624
  const haptic = useHaptic();
57781
58625
  const dtmf = useDTMF({ enabled: true });
57782
58626
  const keySequenceRef = useRef("");
@@ -58595,6 +59439,7 @@ function AuthenticatedMobileDialer({
58595
59439
  ] })
58596
59440
  ] }) })
58597
59441
  ] }),
59442
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LiveCaptionPanel, {}),
58598
59443
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "call-controls", children: [
58599
59444
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "control-row", children: [
58600
59445
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -59049,14 +59894,14 @@ function AuthenticatedMiniDialer({
59049
59894
  }) {
59050
59895
  var _a2;
59051
59896
  const { skipManualQualification } = useDialerConfig();
59052
- const operatorState = clientSdkReactExports.useCallOperatorState();
59053
- const currentCallInfo = clientSdkReactExports.useCallOperatorCurrentCallInfo();
59054
- const currentCall = clientSdkReactExports.useCallOperatorCurrentCall();
59055
- const rawSuccessQualifications = clientSdkReactExports.useCallOperatorSuccessQualifications();
59056
- const rawDiscardQualifications = clientSdkReactExports.useCallOperatorDiscardQualifications();
59897
+ const operatorState = useCallOperatorState();
59898
+ const currentCallInfo = useCallOperatorCurrentCallInfo();
59899
+ const currentCall = useCallOperatorCurrentCall();
59900
+ const rawSuccessQualifications = useCallOperatorSuccessQualifications();
59901
+ const rawDiscardQualifications = useCallOperatorDiscardQualifications();
59057
59902
  const successQualifications = useMemo(() => rawSuccessQualifications ?? [], [rawSuccessQualifications]);
59058
59903
  const discardQualifications = useMemo(() => rawDiscardQualifications ?? [], [rawDiscardQualifications]);
59059
- const { finishAfterCall } = clientSdkReactExports.useCallOperatorControls();
59904
+ const { finishAfterCall } = useCallOperatorControls();
59060
59905
  const [isClientInfoModalOpen, setIsClientInfoModalOpen] = useState(false);
59061
59906
  const [isQualificationModalOpen, setIsQualificationModalOpen] = useState(false);
59062
59907
  const [callDurationSeconds, setCallDurationSeconds] = useState(0);
@@ -59350,8 +60195,8 @@ function buildHeaders(sessionToken) {
59350
60195
  }
59351
60196
  const delay = (ms2) => new Promise((resolve) => setTimeout(resolve, ms2));
59352
60197
  function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask }) {
59353
- const operatorState = clientSdkReactExports.useCallOperatorState();
59354
- const { makeManualCall } = clientSdkReactExports.useCallOperatorControls();
60198
+ const operatorState = useCallOperatorState();
60199
+ const { makeManualCall } = useCallOperatorControls();
59355
60200
  const stateRef = useRef(operatorState == null ? void 0 : operatorState.state);
59356
60201
  stateRef.current = operatorState == null ? void 0 : operatorState.state;
59357
60202
  const makeManualCallRef = useRef(makeManualCall);
@@ -59553,7 +60398,7 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
59553
60398
  }, [enabled, pollIntervalMs, sessionToken]);
59554
60399
  return null;
59555
60400
  }
59556
- const WIDGET_VERSION = "2.5.1";
60401
+ const WIDGET_VERSION = "2.6.0";
59557
60402
  let currentSipWebSocket = null;
59558
60403
  function sendSipUnregisterIfPossible(ws) {
59559
60404
  if (ws.readyState !== WebSocket.OPEN) return;
@@ -59680,8 +60525,8 @@ function useDialerConfig() {
59680
60525
  }
59681
60526
  const SdkCleanupManager = forwardRef(
59682
60527
  function SdkCleanupManager2({ children: children2 }, ref) {
59683
- const operator = clientSdkReactExports.useCallOperator();
59684
- const controls = clientSdkReactExports.useCallOperatorControls();
60528
+ const operator = useCallOperator();
60529
+ const controls = useCallOperatorControls();
59685
60530
  useImperativeHandle(ref, () => ({
59686
60531
  goOffline: () => {
59687
60532
  console.log("[Callix Dialer] 📴 Chamando goOffline() para enviar SIP UNREGISTER...");
@@ -59717,6 +60562,10 @@ const SdkCleanupManager = forwardRef(
59717
60562
  );
59718
60563
  function deriveOptionsFromElement(element) {
59719
60564
  const result2 = {};
60565
+ const datasetEngineUrl = element.getAttribute("data-callix-engine-url") ?? element.dataset.callixEngineUrl;
60566
+ if (datasetEngineUrl && datasetEngineUrl.trim()) result2.engineUrl = datasetEngineUrl.trim();
60567
+ const datasetEngineToken = element.getAttribute("data-callix-engine-token") ?? element.dataset.callixEngineToken;
60568
+ if (datasetEngineToken && datasetEngineToken.trim()) result2.engineToken = datasetEngineToken.trim();
59720
60569
  const datasetMode = element.getAttribute("data-callix-mode") || element.dataset.callixMode || element.dataset.mode;
59721
60570
  const datasetUsername = element.getAttribute("data-callix-username") || element.dataset.callixUsername || element.dataset.username;
59722
60571
  const classList = Array.from(element.classList);
@@ -59808,6 +60657,12 @@ class CallixDialerWidget {
59808
60657
  if (this.options.clickToCall === void 0 && derived.clickToCall !== void 0) {
59809
60658
  this.options.clickToCall = derived.clickToCall;
59810
60659
  }
60660
+ if (!this.options.engineUrl && derived.engineUrl) {
60661
+ this.options.engineUrl = derived.engineUrl;
60662
+ }
60663
+ if (!this.options.engineToken && derived.engineToken) {
60664
+ this.options.engineToken = derived.engineToken;
60665
+ }
59811
60666
  this.root = clientExports.createRoot(this.container);
59812
60667
  this.root.render(
59813
60668
  /* @__PURE__ */ jsxRuntimeExports.jsx(StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(DialerWrapper, { options: this.options, containerEl: this.container }) })
@@ -59840,6 +60695,20 @@ const getStorageKeys = (username) => ({
59840
60695
  SKIP_MANUAL_QUALIFICATION: `callix_dialer_${username}_skip_manual_qualification`
59841
60696
  });
59842
60697
  const LAST_USERNAME_KEY = "callix_dialer_last_username";
60698
+ async function fetchSkipQualification(domain, token) {
60699
+ var _a2, _b;
60700
+ try {
60701
+ const response = await fetch(`https://${domain}/sdk/v1/client/user-service/config`, {
60702
+ headers: { Authorization: `Bearer ${token}` },
60703
+ credentials: "omit"
60704
+ });
60705
+ if (!response.ok) return null;
60706
+ const data = await response.json();
60707
+ return ((_b = (_a2 = data == null ? void 0 : data.service) == null ? void 0 : _a2.outboundOptions) == null ? void 0 : _b.skipQualification) === true;
60708
+ } catch {
60709
+ return null;
60710
+ }
60711
+ }
59843
60712
  function DialerWrapper({ options, containerEl }) {
59844
60713
  const [username, setUsername] = useState(null);
59845
60714
  const [isAuthenticated, setIsAuthenticated] = useState(false);
@@ -59866,8 +60735,49 @@ function DialerWrapper({ options, containerEl }) {
59866
60735
  useEffect(() => {
59867
60736
  currentSessionRef.current.token = sessionToken;
59868
60737
  }, [sessionToken]);
60738
+ const engineMode = Boolean(options.engineUrl);
60739
+ const engineModeRef = useRef(engineMode);
60740
+ engineModeRef.current = engineMode;
60741
+ const engineRef = useRef(null);
60742
+ const ensureEngine = useCallback(
60743
+ (user) => {
60744
+ const current = engineRef.current;
60745
+ if (current && current.agentId === user) {
60746
+ current.connect();
60747
+ return current;
60748
+ }
60749
+ current == null ? void 0 : current.close();
60750
+ const client2 = new EngineClient({
60751
+ url: options.engineUrl ?? "",
60752
+ token: options.engineToken ?? "",
60753
+ agentId: user,
60754
+ label: "Discador",
60755
+ log: (message) => console.log("[Callix Dialer/motor]", message)
60756
+ });
60757
+ client2.onSessionLost((lost) => {
60758
+ const notice = describeSessionDrop(lost.originator);
60759
+ setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
60760
+ setIsAuthenticated(false);
60761
+ setSessionToken(null);
60762
+ autoLoginAttemptedRef.current = true;
60763
+ });
60764
+ client2.connect();
60765
+ engineRef.current = client2;
60766
+ return client2;
60767
+ },
60768
+ [options.engineUrl, options.engineToken]
60769
+ );
60770
+ useEffect(
60771
+ () => () => {
60772
+ var _a2;
60773
+ (_a2 = engineRef.current) == null ? void 0 : _a2.close();
60774
+ engineRef.current = null;
60775
+ },
60776
+ []
60777
+ );
59869
60778
  useEffect(() => {
59870
60779
  const handleBeforeUnload = () => {
60780
+ if (engineModeRef.current) return;
59871
60781
  const { apiUrl, token } = currentSessionRef.current;
59872
60782
  if (token) {
59873
60783
  const data = JSON.stringify({ logout: true });
@@ -59932,6 +60842,77 @@ function DialerWrapper({ options, containerEl }) {
59932
60842
  setError(null);
59933
60843
  setLoginStatus("Preparando login...");
59934
60844
  console.log("[Callix Dialer] 🚀 handleLogin iniciado para usuário:", user);
60845
+ if (options.engineUrl) {
60846
+ const engineApiUrl = options.apiUrl || "/api/user-sdk-session";
60847
+ try {
60848
+ setLoginStatus("Conectando ao motor...");
60849
+ const client2 = ensureEngine(user);
60850
+ const view = await client2.waitForView(15e3);
60851
+ const domain = options.domain || new URL(engineApiUrl, window.location.href).host;
60852
+ const engineHasSession = (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
60853
+ let token = null;
60854
+ if (engineHasSession) {
60855
+ try {
60856
+ token = localStorage.getItem(getStorageKeys(user).SESSION_TOKEN);
60857
+ } catch {
60858
+ token = null;
60859
+ }
60860
+ console.log("[Callix Dialer] motor já tem a sessão do agente; entrando sem novo login");
60861
+ } else {
60862
+ setLoginStatus("Autenticando no Callix...");
60863
+ const response = await fetch(engineApiUrl, {
60864
+ method: "POST",
60865
+ headers: { "Content-Type": "application/json" },
60866
+ body: JSON.stringify({ username: user }),
60867
+ credentials: "same-origin"
60868
+ });
60869
+ if (!response.ok) {
60870
+ const errorData = await response.json().catch(() => ({ error: "Erro desconhecido" }));
60871
+ throw new Error(errorData.error || `Falha na autenticação: ${response.status}`);
60872
+ }
60873
+ const data = await response.json();
60874
+ token = data.userSessionToken;
60875
+ client2.setSession({ domain, userSessionToken: token });
60876
+ setLoginStatus("Subindo o motor...");
60877
+ }
60878
+ if (token) {
60879
+ const skip = await fetchSkipQualification(domain, token);
60880
+ if (skip !== null) {
60881
+ setSkipManualQualification(skip);
60882
+ try {
60883
+ localStorage.setItem(getStorageKeys(user).SKIP_MANUAL_QUALIFICATION, String(skip));
60884
+ } catch {
60885
+ }
60886
+ }
60887
+ }
60888
+ try {
60889
+ localStorage.setItem(LAST_USERNAME_KEY, user);
60890
+ if (token) {
60891
+ const STORAGE_KEYS = getStorageKeys(user);
60892
+ localStorage.setItem(STORAGE_KEYS.SESSION_TOKEN, token);
60893
+ localStorage.setItem(STORAGE_KEYS.DOMAIN, domain);
60894
+ localStorage.setItem(STORAGE_KEYS.TIMESTAMP, Date.now().toString());
60895
+ }
60896
+ } catch {
60897
+ }
60898
+ setSessionToken(token);
60899
+ setCallixDomain(domain);
60900
+ setUsername(user);
60901
+ setIsAuthenticated(true);
60902
+ setError(null);
60903
+ setProviderKey(Date.now());
60904
+ console.log("[Callix Dialer] Conectado ao motor como", user);
60905
+ } catch (err) {
60906
+ console.error("[Callix Dialer] Erro ao entrar pelo motor:", err);
60907
+ setError(err instanceof Error ? err.message : "Erro ao conectar ao motor");
60908
+ setIsAuthenticated(false);
60909
+ setSessionToken(null);
60910
+ } finally {
60911
+ setIsLoading(false);
60912
+ setLoginStatus(null);
60913
+ }
60914
+ return;
60915
+ }
59935
60916
  const apiUrl = options.apiUrl || "/api/user-sdk-session";
59936
60917
  let previousToken;
59937
60918
  if (typeof window !== "undefined") {
@@ -60078,7 +61059,7 @@ function DialerWrapper({ options, containerEl }) {
60078
61059
  setIsLoading(false);
60079
61060
  setLoginStatus(null);
60080
61061
  }
60081
- }, [options.apiUrl, options.domain, isAuthenticated, sessionToken]);
61062
+ }, [options.apiUrl, options.domain, options.engineUrl, isAuthenticated, sessionToken, ensureEngine]);
60082
61063
  useEffect(() => {
60083
61064
  if (lastPresetRef.current !== presetUsername) {
60084
61065
  lastPresetRef.current = presetUsername;
@@ -60102,8 +61083,11 @@ function DialerWrapper({ options, containerEl }) {
60102
61083
  void handleLogin(username);
60103
61084
  }, [autoLogin, presetUsername, username, isAuthenticated, isLoading, isRestoringSession, error2, handleLogin]);
60104
61085
  const handleLogout = useCallback(async () => {
61086
+ var _a2;
60105
61087
  const currentUsername = username;
60106
- if (cleanupRef.current) {
61088
+ if (options.engineUrl) {
61089
+ (_a2 = engineRef.current) == null ? void 0 : _a2.command({ name: "logoff" });
61090
+ } else if (cleanupRef.current) {
60107
61091
  console.log("[Callix Dialer] 📴 Logout: Chamando goOffline()...");
60108
61092
  cleanupRef.current.goOffline();
60109
61093
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -60130,7 +61114,7 @@ function DialerWrapper({ options, containerEl }) {
60130
61114
  console.warn("[Callix Dialer] Não foi possível limpar sessão:", err);
60131
61115
  }
60132
61116
  }
60133
- }, [username, presetUsername]);
61117
+ }, [username, presetUsername, options.engineUrl]);
60134
61118
  const pendingAutoLogin = autoLogin && Boolean(username) && !isAuthenticated && !error2 && !autoLoginAttemptedRef.current;
60135
61119
  const loginConfirmation = useMemo(() => {
60136
61120
  if (isAuthenticated || isLoading || isRestoringSession || isDisconnecting || !username || pendingAutoLogin) {
@@ -60169,7 +61153,7 @@ function DialerWrapper({ options, containerEl }) {
60169
61153
  }
60170
61154
  ) });
60171
61155
  }
60172
- if (!isAuthenticated || !sessionToken || !callixDomain) {
61156
+ if (!isAuthenticated || !engineMode && (!sessionToken || !callixDomain)) {
60173
61157
  return /* @__PURE__ */ jsxRuntimeExports.jsx(DialerConfigContext.Provider, { value: { skipManualQualification }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
60174
61158
  DialerComponent,
60175
61159
  {
@@ -60189,6 +61173,38 @@ function DialerWrapper({ options, containerEl }) {
60189
61173
  }
60190
61174
  ) });
60191
61175
  }
61176
+ if (engineMode && engineRef.current) {
61177
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(DialerConfigContext.Provider, { value: { skipManualQualification }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(EngineClientProvider, { client: engineRef.current, children: [
61178
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
61179
+ DialerComponent,
61180
+ {
61181
+ isAuthenticated,
61182
+ username,
61183
+ onLogin: handleLogin,
61184
+ onLogout: handleLogout,
61185
+ isLoadingSession: isLoading,
61186
+ sessionError: error2,
61187
+ sessionStatus: loginStatus,
61188
+ variant: uiVariant,
61189
+ portalTarget,
61190
+ density: options.density,
61191
+ mode: dialerMode
61192
+ }
61193
+ ),
61194
+ sessionToken && /* @__PURE__ */ jsxRuntimeExports.jsx(
61195
+ ClickToCallAgent,
61196
+ {
61197
+ enabled: clickToCall.enabled,
61198
+ pollIntervalMs: clickToCall.pollIntervalMs,
61199
+ sessionToken,
61200
+ onTask: clickToCall.onTask
61201
+ }
61202
+ )
61203
+ ] }, providerKey) });
61204
+ }
61205
+ if (!sessionToken || !callixDomain) {
61206
+ return null;
61207
+ }
60192
61208
  return /* @__PURE__ */ jsxRuntimeExports.jsx(DialerConfigContext.Provider, { value: { skipManualQualification }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
60193
61209
  clientSdkReactExports.CallixClientProvider,
60194
61210
  {