@eshal-bot/chat-widget 0.2.34 → 0.2.36
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/{chat-widget-MarkdownRenderer-Ck05zMtk.esm.js → chat-widget-MarkdownRenderer-BJPjyfmH.esm.js} +6 -6
- package/dist/chat-widget-index-CudNa4lx.esm.js +20 -0
- package/dist/chat-widget-markdown-chunk.js +361 -12
- package/dist/chat-widget-markdown-chunk.min.js +1 -1
- package/dist/chat-widget.esm.js +1 -1
- package/dist/chat-widget.js +370 -232
- package/dist/chat-widget.min.js +6 -6
- package/dist/chat-widget.umd.js +8 -8
- package/package.json +1 -1
- package/dist/chat-widget-index-DsG4QrRk.esm.js +0 -20
|
@@ -88734,6 +88734,360 @@
|
|
|
88734
88734
|
};
|
|
88735
88735
|
}
|
|
88736
88736
|
|
|
88737
|
+
// Kept in its own tiny module (not A2uiBlock.jsx): MessageList imports it
|
|
88738
|
+
// eagerly, and A2uiBlock/MarkdownRenderer are lazy-loaded to keep charts and
|
|
88739
|
+
// react-markdown out of the widget's initial bundle.
|
|
88740
|
+
|
|
88741
|
+
// 2026-09-26 (P1, double-booking risk): the delivered user turn that
|
|
88742
|
+
// followed the message carrying a card, derived from conversation history by
|
|
88743
|
+
// MessageList. `undefined` = not answered yet. One-shot cards (confirmation,
|
|
88744
|
+
// form) render locked once answered, so a reload cannot re-arm an answered
|
|
88745
|
+
// "Confirm booking". Context rather than a prop so it does not have to be
|
|
88746
|
+
// threaded through MessageBubble -> StreamingMarkdown -> MarkdownRenderer.
|
|
88747
|
+
const A2uiAnsweredContext = /*#__PURE__*/React.createContext(undefined);
|
|
88748
|
+
|
|
88749
|
+
const VerificationContext = /*#__PURE__*/React.createContext(null);
|
|
88750
|
+
const BRAND_BLUE$1 = '#2563eb';
|
|
88751
|
+
function fill(text, values) {
|
|
88752
|
+
return Object.entries(values).reduce((s, [k, v]) => s.replace(`{${k}}`, String(v)), String(text));
|
|
88753
|
+
}
|
|
88754
|
+
function boxStyle(isDark) {
|
|
88755
|
+
return {
|
|
88756
|
+
border: `1px solid ${BRAND_BLUE$1}`,
|
|
88757
|
+
borderRadius: 10,
|
|
88758
|
+
padding: 12,
|
|
88759
|
+
marginTop: 8,
|
|
88760
|
+
background: isDark ? '#111827' : '#ffffff',
|
|
88761
|
+
color: isDark ? '#f3f4f6' : '#111827',
|
|
88762
|
+
display: 'grid',
|
|
88763
|
+
gap: 8,
|
|
88764
|
+
fontSize: 13
|
|
88765
|
+
};
|
|
88766
|
+
}
|
|
88767
|
+
function inputStyle(isDark) {
|
|
88768
|
+
return {
|
|
88769
|
+
width: '100%',
|
|
88770
|
+
boxSizing: 'border-box',
|
|
88771
|
+
padding: '7px 10px',
|
|
88772
|
+
borderRadius: 6,
|
|
88773
|
+
border: `1px solid ${isDark ? '#374151' : '#d1d5db'}`,
|
|
88774
|
+
background: isDark ? '#1f2937' : '#f9fafb',
|
|
88775
|
+
color: 'inherit',
|
|
88776
|
+
fontSize: 14
|
|
88777
|
+
};
|
|
88778
|
+
}
|
|
88779
|
+
function buttonStyle(disabled) {
|
|
88780
|
+
return {
|
|
88781
|
+
padding: '7px 12px',
|
|
88782
|
+
borderRadius: 6,
|
|
88783
|
+
border: 'none',
|
|
88784
|
+
background: BRAND_BLUE$1,
|
|
88785
|
+
color: '#ffffff',
|
|
88786
|
+
fontWeight: 600,
|
|
88787
|
+
fontSize: 13,
|
|
88788
|
+
cursor: disabled ? 'default' : 'pointer',
|
|
88789
|
+
opacity: disabled ? 0.5 : 1,
|
|
88790
|
+
justifySelf: 'start'
|
|
88791
|
+
};
|
|
88792
|
+
}
|
|
88793
|
+
const linkStyle = {
|
|
88794
|
+
background: 'none',
|
|
88795
|
+
border: 'none',
|
|
88796
|
+
padding: 0,
|
|
88797
|
+
color: BRAND_BLUE$1,
|
|
88798
|
+
fontSize: 12,
|
|
88799
|
+
cursor: 'pointer',
|
|
88800
|
+
justifySelf: 'start'
|
|
88801
|
+
};
|
|
88802
|
+
|
|
88803
|
+
/**
|
|
88804
|
+
* The verification code card, rendered from an `eshal-ui` fence of type
|
|
88805
|
+
* "verification" (spec: { mode: 'email'|'sms', tool_call_id?, invocation_id? }).
|
|
88806
|
+
* The address and the code go straight to main-backend through the context;
|
|
88807
|
+
* the code is never sent as a chat message. When the code checks out, the
|
|
88808
|
+
* card sends one resume message carrying `verificationComplete` so the held
|
|
88809
|
+
* tool can run.
|
|
88810
|
+
*/
|
|
88811
|
+
function VerifyCard({
|
|
88812
|
+
spec,
|
|
88813
|
+
isDark,
|
|
88814
|
+
onButtonClick
|
|
88815
|
+
}) {
|
|
88816
|
+
const ctx = React.useContext(VerificationContext);
|
|
88817
|
+
const mode = spec?.mode === 'sms' ? 'sms' : 'email';
|
|
88818
|
+
const t = ctx?.t ?? (k => k);
|
|
88819
|
+
const [step, setStep] = React.useState('address'); // address | code | done
|
|
88820
|
+
const [address, setAddress] = React.useState(() => (mode === 'sms' ? ctx?.prefillPhone : ctx?.prefillEmail) || '');
|
|
88821
|
+
const [code, setCode] = React.useState('');
|
|
88822
|
+
const [masked, setMasked] = React.useState('');
|
|
88823
|
+
const [busy, setBusy] = React.useState(false);
|
|
88824
|
+
const [error, setError] = React.useState(null);
|
|
88825
|
+
const [resendIn, setResendIn] = React.useState(0);
|
|
88826
|
+
const [cancelled, setCancelled] = React.useState(false);
|
|
88827
|
+
const resumedRef = React.useRef(false);
|
|
88828
|
+
const pendingKey = React.useRef(Symbol('verify-card'));
|
|
88829
|
+
// A card the conversation already moved past (a later visitor turn exists,
|
|
88830
|
+
// e.g. after a reload) is not waiting for anything.
|
|
88831
|
+
const answered = React.useContext(A2uiAnsweredContext) !== undefined;
|
|
88832
|
+
const waiting = Boolean(ctx) && !answered && !cancelled && step !== 'done';
|
|
88833
|
+
|
|
88834
|
+
// While this card waits for its code, the chat input is blocked so the code
|
|
88835
|
+
// is entered here and never typed into the conversation.
|
|
88836
|
+
React.useEffect(() => {
|
|
88837
|
+
if (!waiting || !ctx?.setPending) return undefined;
|
|
88838
|
+
const key = pendingKey.current;
|
|
88839
|
+
ctx.setPending(key, true);
|
|
88840
|
+
return () => ctx.setPending(key, false);
|
|
88841
|
+
}, [waiting, ctx]);
|
|
88842
|
+
|
|
88843
|
+
// SMS only: ask for the delivery report a few times while waiting for the
|
|
88844
|
+
// code, so a number that cannot receive it is said rather than waited on.
|
|
88845
|
+
React.useEffect(() => {
|
|
88846
|
+
if (mode !== 'sms' || step !== 'code' || !ctx?.status) return undefined;
|
|
88847
|
+
let tries = 0;
|
|
88848
|
+
let stopped = false;
|
|
88849
|
+
const id = setInterval(async () => {
|
|
88850
|
+
tries += 1;
|
|
88851
|
+
if (stopped || tries > 12) {
|
|
88852
|
+
clearInterval(id);
|
|
88853
|
+
return;
|
|
88854
|
+
}
|
|
88855
|
+
try {
|
|
88856
|
+
const res = await ctx.status();
|
|
88857
|
+
const delivery = res?.body?.delivery;
|
|
88858
|
+
if (delivery === 'failed') {
|
|
88859
|
+
setError(t('verifyNotDelivered'));
|
|
88860
|
+
setResendIn(0);
|
|
88861
|
+
clearInterval(id);
|
|
88862
|
+
} else if (delivery === 'delivered' || delivery === null) {
|
|
88863
|
+
clearInterval(id);
|
|
88864
|
+
}
|
|
88865
|
+
} catch {
|
|
88866
|
+
// A missed check only means no early warning.
|
|
88867
|
+
}
|
|
88868
|
+
}, 5000);
|
|
88869
|
+
return () => {
|
|
88870
|
+
stopped = true;
|
|
88871
|
+
clearInterval(id);
|
|
88872
|
+
};
|
|
88873
|
+
}, [mode, step, ctx, t]);
|
|
88874
|
+
React.useEffect(() => {
|
|
88875
|
+
if (resendIn <= 0) return undefined;
|
|
88876
|
+
const id = setTimeout(() => setResendIn(s => s - 1), 1000);
|
|
88877
|
+
return () => clearTimeout(id);
|
|
88878
|
+
}, [resendIn]);
|
|
88879
|
+
if (!ctx) return null;
|
|
88880
|
+
if (cancelled || answered && step !== 'done') {
|
|
88881
|
+
return /*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
88882
|
+
style: {
|
|
88883
|
+
...boxStyle(isDark),
|
|
88884
|
+
borderColor: isDark ? '#374151' : '#d1d5db'
|
|
88885
|
+
},
|
|
88886
|
+
"data-testid": "verify-card",
|
|
88887
|
+
children: /*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
88888
|
+
style: {
|
|
88889
|
+
fontSize: 12,
|
|
88890
|
+
opacity: 0.7
|
|
88891
|
+
},
|
|
88892
|
+
children: cancelled ? t('verifyCancelled') : t('verifyTitle')
|
|
88893
|
+
})
|
|
88894
|
+
});
|
|
88895
|
+
}
|
|
88896
|
+
const send = async () => {
|
|
88897
|
+
if (busy || !address.trim()) return;
|
|
88898
|
+
setBusy(true);
|
|
88899
|
+
setError(null);
|
|
88900
|
+
try {
|
|
88901
|
+
const res = await ctx.start({
|
|
88902
|
+
method: mode,
|
|
88903
|
+
...(mode === 'sms' ? {
|
|
88904
|
+
phone: address.trim()
|
|
88905
|
+
} : {
|
|
88906
|
+
email: address.trim()
|
|
88907
|
+
})
|
|
88908
|
+
});
|
|
88909
|
+
if (res.ok) {
|
|
88910
|
+
setMasked(res.body?.masked || address.trim());
|
|
88911
|
+
setResendIn(Number(res.body?.resend_after_s) || 60);
|
|
88912
|
+
setCode('');
|
|
88913
|
+
setStep('code');
|
|
88914
|
+
} else {
|
|
88915
|
+
setError(res.body?.error || t('verifyGenericError'));
|
|
88916
|
+
if (res.body?.resendAfterS) setResendIn(Number(res.body.resendAfterS));
|
|
88917
|
+
}
|
|
88918
|
+
} catch {
|
|
88919
|
+
setError(t('verifyGenericError'));
|
|
88920
|
+
} finally {
|
|
88921
|
+
setBusy(false);
|
|
88922
|
+
}
|
|
88923
|
+
};
|
|
88924
|
+
const confirm = async () => {
|
|
88925
|
+
const digits = code.replace(/\D/g, '');
|
|
88926
|
+
if (busy || digits.length !== 6) return;
|
|
88927
|
+
setBusy(true);
|
|
88928
|
+
setError(null);
|
|
88929
|
+
try {
|
|
88930
|
+
const res = await ctx.verify({
|
|
88931
|
+
code: digits
|
|
88932
|
+
});
|
|
88933
|
+
if (res.ok) {
|
|
88934
|
+
setMasked(res.body?.masked || masked);
|
|
88935
|
+
setStep('done');
|
|
88936
|
+
if (!resumedRef.current && onButtonClick) {
|
|
88937
|
+
resumedRef.current = true;
|
|
88938
|
+
const text = t('verifyResumeMessage');
|
|
88939
|
+
onButtonClick(text, text, {
|
|
88940
|
+
verificationComplete: {
|
|
88941
|
+
...(spec?.tool_call_id ? {
|
|
88942
|
+
tool_call_id: String(spec.tool_call_id)
|
|
88943
|
+
} : {}),
|
|
88944
|
+
...(spec?.invocation_id ? {
|
|
88945
|
+
invocation_id: String(spec.invocation_id)
|
|
88946
|
+
} : {})
|
|
88947
|
+
}
|
|
88948
|
+
});
|
|
88949
|
+
}
|
|
88950
|
+
} else if (res.body?.code === 'wrong_code' && typeof res.body?.triesLeft === 'number') {
|
|
88951
|
+
setError(fill(t('verifyTriesLeft'), {
|
|
88952
|
+
count: res.body.triesLeft
|
|
88953
|
+
}));
|
|
88954
|
+
setCode('');
|
|
88955
|
+
} else {
|
|
88956
|
+
setError(res.body?.error || t('verifyGenericError'));
|
|
88957
|
+
if (res.body?.code === 'expired' || res.body?.code === 'locked') setStep('address');
|
|
88958
|
+
}
|
|
88959
|
+
} catch {
|
|
88960
|
+
setError(t('verifyGenericError'));
|
|
88961
|
+
} finally {
|
|
88962
|
+
setBusy(false);
|
|
88963
|
+
}
|
|
88964
|
+
};
|
|
88965
|
+
if (step === 'done') {
|
|
88966
|
+
return /*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
88967
|
+
style: boxStyle(isDark),
|
|
88968
|
+
"data-testid": "verify-card",
|
|
88969
|
+
children: /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
|
|
88970
|
+
style: {
|
|
88971
|
+
color: '#13703a',
|
|
88972
|
+
fontWeight: 600
|
|
88973
|
+
},
|
|
88974
|
+
children: ["\u2713 ", fill(t('verifyDone'), {
|
|
88975
|
+
target: masked
|
|
88976
|
+
})]
|
|
88977
|
+
})
|
|
88978
|
+
});
|
|
88979
|
+
}
|
|
88980
|
+
return /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
|
|
88981
|
+
style: boxStyle(isDark),
|
|
88982
|
+
"data-testid": "verify-card",
|
|
88983
|
+
children: [/*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
88984
|
+
style: {
|
|
88985
|
+
fontWeight: 600
|
|
88986
|
+
},
|
|
88987
|
+
children: t('verifyTitle')
|
|
88988
|
+
}), step === 'address' ? /*#__PURE__*/jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
|
|
88989
|
+
children: [/*#__PURE__*/jsxRuntimeExports.jsxs("label", {
|
|
88990
|
+
style: {
|
|
88991
|
+
display: 'grid',
|
|
88992
|
+
gap: 4
|
|
88993
|
+
},
|
|
88994
|
+
children: [/*#__PURE__*/jsxRuntimeExports.jsx("span", {
|
|
88995
|
+
style: {
|
|
88996
|
+
fontSize: 12,
|
|
88997
|
+
opacity: 0.8
|
|
88998
|
+
},
|
|
88999
|
+
children: mode === 'sms' ? t('verifyPhoneLabel') : t('verifyEmailLabel')
|
|
89000
|
+
}), /*#__PURE__*/jsxRuntimeExports.jsx("input", {
|
|
89001
|
+
style: inputStyle(isDark),
|
|
89002
|
+
type: mode === 'sms' ? 'tel' : 'email',
|
|
89003
|
+
autoComplete: mode === 'sms' ? 'tel' : 'email',
|
|
89004
|
+
inputMode: mode === 'sms' ? 'tel' : 'email',
|
|
89005
|
+
placeholder: mode === 'sms' ? '+971 50 123 4567' : 'name@example.com',
|
|
89006
|
+
value: address,
|
|
89007
|
+
onChange: e => setAddress(e.target.value),
|
|
89008
|
+
onKeyDown: e => {
|
|
89009
|
+
if (e.key === 'Enter') send();
|
|
89010
|
+
}
|
|
89011
|
+
})]
|
|
89012
|
+
}), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
|
|
89013
|
+
type: "button",
|
|
89014
|
+
style: buttonStyle(busy || !address.trim() || resendIn > 0),
|
|
89015
|
+
disabled: busy || !address.trim() || resendIn > 0,
|
|
89016
|
+
onClick: send,
|
|
89017
|
+
children: busy ? t('verifySending') : resendIn > 0 ? fill(t('verifyResendIn'), {
|
|
89018
|
+
seconds: resendIn
|
|
89019
|
+
}) : t('verifySendCode')
|
|
89020
|
+
})]
|
|
89021
|
+
}) : /*#__PURE__*/jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
|
|
89022
|
+
children: [/*#__PURE__*/jsxRuntimeExports.jsx("span", {
|
|
89023
|
+
children: fill(t('verifyEnterCode'), {
|
|
89024
|
+
target: masked
|
|
89025
|
+
})
|
|
89026
|
+
}), /*#__PURE__*/jsxRuntimeExports.jsx("input", {
|
|
89027
|
+
style: {
|
|
89028
|
+
...inputStyle(isDark),
|
|
89029
|
+
letterSpacing: '0.3em',
|
|
89030
|
+
fontFamily: 'monospace',
|
|
89031
|
+
fontSize: 18
|
|
89032
|
+
},
|
|
89033
|
+
"aria-label": t('verifyCodeLabel'),
|
|
89034
|
+
inputMode: "numeric",
|
|
89035
|
+
autoComplete: "one-time-code",
|
|
89036
|
+
maxLength: 6,
|
|
89037
|
+
value: code,
|
|
89038
|
+
onChange: e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6)),
|
|
89039
|
+
onKeyDown: e => {
|
|
89040
|
+
if (e.key === 'Enter') confirm();
|
|
89041
|
+
}
|
|
89042
|
+
}), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
|
|
89043
|
+
type: "button",
|
|
89044
|
+
style: buttonStyle(busy || code.length !== 6),
|
|
89045
|
+
disabled: busy || code.length !== 6,
|
|
89046
|
+
onClick: confirm,
|
|
89047
|
+
children: t('verifyConfirm')
|
|
89048
|
+
}), resendIn > 0 ? /*#__PURE__*/jsxRuntimeExports.jsx("span", {
|
|
89049
|
+
style: {
|
|
89050
|
+
fontSize: 12,
|
|
89051
|
+
opacity: 0.7
|
|
89052
|
+
},
|
|
89053
|
+
children: fill(t('verifyResendIn'), {
|
|
89054
|
+
seconds: resendIn
|
|
89055
|
+
})
|
|
89056
|
+
}) : /*#__PURE__*/jsxRuntimeExports.jsx("button", {
|
|
89057
|
+
type: "button",
|
|
89058
|
+
style: linkStyle,
|
|
89059
|
+
onClick: send,
|
|
89060
|
+
disabled: busy,
|
|
89061
|
+
children: t('verifyResend')
|
|
89062
|
+
}), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
|
|
89063
|
+
type: "button",
|
|
89064
|
+
style: linkStyle,
|
|
89065
|
+
onClick: () => {
|
|
89066
|
+
setStep('address');
|
|
89067
|
+
setError(null);
|
|
89068
|
+
},
|
|
89069
|
+
children: t('verifyDifferent')
|
|
89070
|
+
})]
|
|
89071
|
+
}), error && /*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
89072
|
+
role: "alert",
|
|
89073
|
+
style: {
|
|
89074
|
+
color: '#b42318',
|
|
89075
|
+
fontSize: 12
|
|
89076
|
+
},
|
|
89077
|
+
children: error
|
|
89078
|
+
}), /*#__PURE__*/jsxRuntimeExports.jsx("button", {
|
|
89079
|
+
type: "button",
|
|
89080
|
+
style: {
|
|
89081
|
+
...linkStyle,
|
|
89082
|
+
color: isDark ? '#9ca3af' : '#6b7280'
|
|
89083
|
+
},
|
|
89084
|
+
onClick: () => setCancelled(true),
|
|
89085
|
+
disabled: busy,
|
|
89086
|
+
children: t('verifyCancel')
|
|
89087
|
+
})]
|
|
89088
|
+
});
|
|
89089
|
+
}
|
|
89090
|
+
|
|
88737
89091
|
/**
|
|
88738
89092
|
* Table-cell and label formatting shared by the three A2UI renderers - the
|
|
88739
89093
|
* agent-sdk, the widget and the frontend Testing Lab (EV-4798 MERN-19). Each
|
|
@@ -88875,18 +89229,6 @@
|
|
|
88875
89229
|
}, key++) : seg.value);
|
|
88876
89230
|
}
|
|
88877
89231
|
|
|
88878
|
-
// Kept in its own tiny module (not A2uiBlock.jsx): MessageList imports it
|
|
88879
|
-
// eagerly, and A2uiBlock/MarkdownRenderer are lazy-loaded to keep charts and
|
|
88880
|
-
// react-markdown out of the widget's initial bundle.
|
|
88881
|
-
|
|
88882
|
-
// 2026-09-26 (P1, double-booking risk): the delivered user turn that
|
|
88883
|
-
// followed the message carrying a card, derived from conversation history by
|
|
88884
|
-
// MessageList. `undefined` = not answered yet. One-shot cards (confirmation,
|
|
88885
|
-
// form) render locked once answered, so a reload cannot re-arm an answered
|
|
88886
|
-
// "Confirm booking". Context rather than a prop so it does not have to be
|
|
88887
|
-
// threaded through MessageBubble -> StreamingMarkdown -> MarkdownRenderer.
|
|
88888
|
-
const A2uiAnsweredContext = /*#__PURE__*/React.createContext(undefined);
|
|
88889
|
-
|
|
88890
89232
|
/**
|
|
88891
89233
|
* A2uiBlock - render `eshal-ui` / `eshal-chart` / `chart` fence JSON as a
|
|
88892
89234
|
* real visual block inside the embedded chat widget.
|
|
@@ -89026,6 +89368,13 @@
|
|
|
89026
89368
|
isDark: isDark,
|
|
89027
89369
|
onButtonClick: onButtonClick
|
|
89028
89370
|
});
|
|
89371
|
+
// Verification code card: the runtime holds a personal tool until the
|
|
89372
|
+
// visitor proves their email or phone. The code never travels as chat.
|
|
89373
|
+
if (t === 'verification') return /*#__PURE__*/jsxRuntimeExports.jsx(VerifyCard, {
|
|
89374
|
+
spec: spec,
|
|
89375
|
+
isDark: isDark,
|
|
89376
|
+
onButtonClick: onButtonClick
|
|
89377
|
+
});
|
|
89029
89378
|
// Cross-industry primitives (14-08-2026) - mirrors
|
|
89030
89379
|
// packages/frontend/src/components/charts/A2uiBlock.tsx's menu/profile/
|
|
89031
89380
|
// timeline/rating/comparison/gallery additions.
|