wealth-alpha-chat-widget 1.0.1
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/README.md +139 -0
- package/dist/index.cjs +1110 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +388 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +219 -0
- package/dist/index.d.ts +219 -0
- package/dist/index.mjs +1079 -0
- package/dist/index.mjs.map +1 -0
- package/docs/BACKEND_CHAT_WIDGET.md +357 -0
- package/docs/DEPLOY.md +283 -0
- package/docs/PUBLISH.md +202 -0
- package/docs/SETUP.md +180 -0
- package/package.json +77 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/session.ts","../src/api/chatApi.ts","../src/hooks/useAuth.ts","../src/hooks/useChat.ts","../src/hooks/useChip.ts","../src/hooks/useSession.ts","../src/styles/chat.module.css","../src/components/AuthGate.tsx","../src/components/Chip.tsx","../src/components/ChipRow.tsx","../src/utils/markdown.ts","../src/components/MessageBubble.tsx","../src/components/TypingIndicator.tsx","../src/components/ChatBody.tsx","../src/utils/time.ts","../src/components/ChatHeader.tsx","../src/components/ChatInput.tsx","../src/components/FloatingButton.tsx","../src/components/WealthChat.tsx"],"names":["fallback","useRef","useEffect","useCallback","useState","root","floatingButton","positionRight","positionLeft","widget","header","headerTitle","headerMeta","headerActions","iconButton","body","bubble","markdown","bubbleBot","bubbleUser","chipRow","chip","chipActive","chipDisabled","typing","typingDot","input","inputBox","sendButton","authGate","authGateIcon","authGateTitle","authGateText","authGateButton","authGateDisclaimer","authGateDisclaimerTitle","errorBanner","jsxs","jsx"],"mappings":";;;;;;;AAEO,IAAM,WAAA,GAAc;AACpB,IAAM,2BAAA,GAA8B;AACpC,IAAM,WAAA,GAAc,GAAA;AAI3B,IAAM,mBAAA,GAAsB,CAAC,OAAA,EAAS,cAAA,EAAgB,cAAc,KAAK,CAAA;AAEzE,IAAM,YAAY,MAChB,OAAO,WAAW,WAAA,IAAe,OAAO,OAAO,YAAA,KAAiB,WAAA;AAElE,SAAS,gBAAgB,KAAA,EAAuB;AAC9C,EAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAM,GAAG,CAAA;AACzD,EAAA,MAAM,SAAS,MAAA,GAAS,KAAA,CAAM,OAAO,MAAA,CAAO,MAAA,GAAS,KAAK,CAAC,CAAA;AAC3D,EAAA,OAAO,KAAK,MAAM,CAAA;AACpB;AAEA,SAAS,UAAU,KAAA,EAA+C;AAChE,EAAA,IAAI;AACF,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAC7B,IAAA,IAAI,MAAM,MAAA,KAAW,CAAA,IAAK,CAAC,KAAA,CAAM,CAAC,GAAG,OAAO,IAAA;AAC5C,IAAA,OAAO,KAAK,KAAA,CAAM,eAAA,CAAgB,KAAA,CAAM,CAAC,CAAC,CAAC,CAAA;AAAA,EAC7C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,aAAa,KAAA,EAA8B;AAClD,EAAA,MAAM,OAAA,GAAU,UAAU,KAAK,CAAA;AAC/B,EAAA,MAAM,GAAA,GAAM,UAAU,KAAK,CAAA;AAC3B,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,EAAU,OAAO,IAAA;AACpC,EAAA,OAAO,GAAA,GAAM,GAAA;AACf;AAEA,SAAS,UAAU,KAAA,EAAuB;AACxC,EAAA,MAAM,OAAA,GAAU,UAAU,KAAK,CAAA;AAC/B,EAAA,IAAI,CAAC,SAAS,OAAO,EAAA;AACrB,EAAA,MAAM,EAAA,GAAK,QAAQ,IAAI,CAAA,IAAK,QAAQ,KAAK,CAAA,IAAK,QAAQ,SAAS,CAAA;AAC/D,EAAA,OAAO,EAAA,IAAM,IAAA,GAAO,MAAA,CAAO,EAAE,CAAA,GAAI,EAAA;AACnC;AAEA,SAAS,iBAAA,GAA2D;AAClE,EAAA,IAAI,CAAC,SAAA,EAAU,EAAG,OAAO,IAAA;AACzB,EAAA,KAAA,MAAW,OAAO,mBAAA,EAAqB;AACrC,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA;AAC7C,IAAA,IAAI,KAAA,IAAS,MAAM,MAAA,GAAS,EAAA,SAAW,EAAE,KAAA,EAAO,OAAO,GAAA,EAAI;AAAA,EAC7D;AACA,EAAA,OAAO,IAAA;AACT;AAEA,IAAM,UAAA,GAAa,CAAC,MAAA,KAA2B;AAC7C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,MAAA,CAAO,eAAe,UAAA,EAAY;AAC5E,IAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAA,CAAO,YAAY,CAAA,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,GAAG,MAAM,CAAA,CAAA,EAAI,KAAK,GAAA,EAAI,CAAE,SAAS,EAAE,CAAC,IAAI,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AACxF,CAAA;AAEO,SAAS,YAAA,GAAuB;AACrC,EAAA,OAAO,WAAW,KAAK,CAAA;AACzB;AAEO,SAAS,YAAA,GAAuB;AACrC,EAAA,OAAO,WAAW,KAAK,CAAA;AACzB;AAEO,SAAS,WAAA,GAAiC;AAC/C,EAAA,IAAI,CAAC,SAAA,EAAU,EAAG,OAAO,IAAA;AACzB,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,WAAW,CAAA;AACnD,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC9B,MAAA,MAAM,QACJ,OAAA,IACA,OAAO,OAAA,CAAQ,KAAA,KAAU,YACzB,OAAA,CAAQ,KAAA,CAAM,MAAA,GAAS,CAAA,IACvB,OAAO,OAAA,CAAQ,SAAA,KAAc,YAC7B,IAAA,CAAK,GAAA,KAAQ,OAAA,CAAQ,SAAA;AACvB,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,MAAMA,YAAW,iBAAA,EAAkB;AAGnC,QAAA,IAAI,CAACA,SAAAA,IAAY,OAAA,CAAQ,UAAA,KAAe,UAAA,EAAY;AAClD,UAAA,YAAA,EAAa;AACb,UAAA,OAAO,IAAA;AAAA,QACT;AAEA,QAAA,IAAIA,SAAAA,IAAYA,SAAAA,CAAS,KAAA,KAAU,OAAA,CAAQ,KAAA,EAAO;AAChD,UAAA,OAAO,kBAAA,CAAmBA,SAAAA,CAAS,KAAA,EAAO,OAAO,CAAA;AAAA,QACnD;AACA,QAAA,OAAO,OAAA;AAAA,MACT;AAEA,MAAA,YAAA,EAAa;AAAA,IACf;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,YAAA,EAAa;AAAA,EACf;AAEA,EAAA,MAAM,WAAW,iBAAA,EAAkB;AACnC,EAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,EAAA,OAAO,kBAAA,CAAmB,QAAA,CAAS,KAAA,EAAO,IAAI,CAAA;AAChD;AAEA,SAAS,kBAAA,CAAmB,OAAe,KAAA,EAA6C;AACtF,EAAA,MAAM,MAAA,GAAS,aAAa,KAAK,CAAA;AACjC,EAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,EAAA,MAAM,SAAA,GAAY,MAAA,IAAU,GAAA,GAAM,2BAAA,GAA8B,GAAA;AAChE,EAAA,IAAI,GAAA,IAAO,WAAW,OAAO,IAAA;AAC7B,EAAA,MAAM,OAAA,GAAsB;AAAA,IAC1B,KAAA;AAAA,IACA,MAAA,EAAQ,SAAA,CAAU,KAAK,CAAA,IAAK,OAAO,MAAA,IAAU,EAAA;AAAA,IAC7C,SAAA,EAAW,KAAA,EAAO,SAAA,IAAa,YAAA,EAAa;AAAA,IAC5C,SAAA;AAAA,IACA,UAAA,EAAY,GAAA;AAAA,IACZ,OAAA,EAAS,KAAA,EAAO,OAAA,IAAW,EAAC;AAAA,IAC5B,UAAA,EAAY;AAAA,GACd;AACA,EAAA,IAAI,WAAU,EAAG;AACf,IAAA,IAAI;AACF,MAAA,MAAA,CAAO,aAAa,OAAA,CAAQ,WAAA,EAAa,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,IAClE,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,YAAA,CACd,IAAA,EACA,UAAA,GAAqB,2BAAA,EACF;AACnB,EAAA,IAAI,CAAC,SAAA,EAAU,EAAG,OAAO,IAAA;AACzB,EAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,MAAM,OACJ,QAAA,IACA;AAAA,IACE,KAAA,EAAO,EAAA;AAAA,IACP,MAAA,EAAQ,EAAA;AAAA,IACR,WAAW,YAAA,EAAa;AAAA,IACxB,SAAA,EAAW,MAAM,UAAA,GAAa,GAAA;AAAA,IAC9B,UAAA,EAAY,GAAA;AAAA,IACZ,SAAS;AAAC,GACZ;AAEF,EAAA,MAAM,MAAA,GAAqB;AAAA,IACzB,GAAG,IAAA;AAAA,IACH,GAAG,IAAA;AAAA,IACH,SAAA,EAAW,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,aAAa,YAAA,EAAa;AAAA,IAC5D,UAAA,EAAY,GAAA;AAAA,IACZ,SAAA,EAAW,MAAM,UAAA,GAAa,GAAA;AAAA,IAC9B,OAAA,EAAS,WAAA,CAAY,IAAA,CAAK,OAAA,IAAW,KAAK,OAAO;AAAA,GACnD;AAEA,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,aAAa,OAAA,CAAQ,WAAA,EAAa,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AAC/D,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEO,SAAS,YAAA,GAAqB;AACnC,EAAA,IAAI,CAAC,WAAU,EAAG;AAClB,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,YAAA,CAAa,WAAW,WAAW,CAAA;AAAA,EAC5C,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAEO,SAAS,YAAY,OAAA,EAA2C;AACrE,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,SAAU,EAAC;AACrC,EAAA,IAAI,OAAA,CAAQ,MAAA,IAAU,WAAA,EAAa,OAAO,OAAA;AAC1C,EAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,OAAA,CAAQ,MAAA,GAAS,WAAW,CAAA;AACnD;AAEO,SAAS,YAAA,CACd,aAAqB,2BAAA,EACF;AACnB,EAAA,MAAM,UAAU,WAAA,EAAY;AAC5B,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,EAAA,OAAO,YAAA,CAAa,EAAC,EAAG,UAAU,CAAA;AACpC;;;ACtLA,IAAM,kBAAA,GAAqB,GAAA;AAC3B,IAAM,eAAA,GAAkB,CAAA;AACxB,IAAM,mBAAA,GAAsB,GAAA;AAErB,IAAM,QAAA,GAAN,cAAuB,KAAA,CAAM;AAAA,EAGlC,WAAA,CAAY,OAAA,EAAiB,MAAA,EAAgB,SAAA,EAAmB;AAC9D,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,UAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAEO,IAAM,gBAAA,GAAN,cAA+B,QAAA,CAAS;AAAA,EAC7C,YAAY,SAAA,EAAmB;AAC7B,IAAA,KAAA,CAAM,4BAAA,EAA8B,KAAK,SAAS,CAAA;AAClD,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AAAA,EACd;AACF;AAWA,IAAM,oBAAoB,MAAc;AACtC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,MAAA,CAAO,eAAe,UAAA,EAAY;AAC5E,IAAA,OAAO,OAAO,UAAA,EAAW;AAAA,EAC3B;AACA,EAAA,OAAO,OAAO,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAClF,CAAA;AAEA,IAAM,KAAA,GAAQ,CAAC,EAAA,KACb,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AAElD,IAAM,OAAA,GAAU,CAAC,IAAA,EAAc,IAAA,KAAyB;AACtD,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAC3C,EAAA,MAAM,cAAc,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAC1D,EAAA,OAAO,CAAA,EAAG,WAAW,CAAA,EAAG,WAAW,CAAA,CAAA;AACrC,CAAA;AAEA,eAAe,OAAA,CACb,OAAA,EACA,IAAA,EACA,IAAA,GAAuB,EAAC,EACZ;AACZ,EAAA,MAAM;AAAA,IACJ,MAAA,GAAS,KAAA;AAAA,IACT,IAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA,GAAY,kBAAA;AAAA,IACZ,OAAA,GAAU,eAAA;AAAA,IACV,IAAA,GAAO;AAAA,GACT,GAAI,IAAA;AAEJ,EAAA,MAAM,GAAA,GAAM,gBAAgB,IAAA,CAAK,IAAI,IAAI,IAAA,GAAO,OAAA,CAAQ,SAAS,IAAI,CAAA;AACrE,EAAA,MAAM,YAAY,iBAAA,EAAkB;AAEpC,EAAA,IAAI,SAAA,GAA0B,IAAA;AAE9B,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,OAAA,EAAS,OAAA,EAAA,EAAW;AACnD,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,YAAY,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,SAAS,CAAA;AAEhE,IAAA,MAAM,WAAA,GAAc,MAAM,UAAA,CAAW,KAAA,EAAM;AAC3C,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,IAAI,OAAO,OAAA,EAAS;AAClB,QAAA,YAAA,CAAa,SAAS,CAAA;AACtB,QAAA,MAAM,IAAI,YAAA,CAAa,SAAA,EAAW,YAAY,CAAA;AAAA,MAChD;AACA,MAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,WAAA,EAAa,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,IAC9D;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,GAAkC;AAAA,QACtC,cAAA,EAAgB,kBAAA;AAAA,QAChB,cAAA,EAAgB;AAAA,OAClB;AACA,MAAA,IAAI,IAAA,EAAM;AACR,QAAA,MAAM,UAAU,WAAA,EAAY;AAC5B,QAAA,IAAI,SAAS,KAAA,EAAO,OAAA,CAAQ,eAAe,CAAA,GAAI,CAAA,OAAA,EAAU,QAAQ,KAAK,CAAA,CAAA;AAAA,MACxE;AAEA,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAC3B,MAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAM,IAAA,KAAS,KAAA,CAAA,GAAY,KAAA,CAAA,GAAY,IAAA,CAAK,UAAU,IAAI,CAAA;AAAA,QAC1D,QAAQ,UAAA,CAAW,MAAA;AAAA,QACnB,WAAA,EAAa;AAAA,OACd,CAAA;AAED,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,GAAA,IAAO,GAAA,CAAI,WAAW,GAAA,EAAK;AAC5C,QAAA,YAAA,EAAa;AACb,QAAA,MAAM,IAAI,iBAAiB,SAAS,CAAA;AAAA,MACtC;AAEA,MAAA,IAAI,GAAA,CAAI,MAAA,IAAU,GAAA,IAAO,OAAA,GAAU,OAAA,EAAS;AAC1C,QAAA,SAAA,GAAY,IAAI,SAAS,CAAA,aAAA,EAAgB,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,QAAQ,SAAS,CAAA;AAC5E,QAAA,MAAM,SAAA;AAAA,MACR;AAEA,MAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,QAAA,IAAI,SAAS,GAAA,CAAI,UAAA;AACjB,QAAA,IAAI;AACF,UAAA,MAAM,OAAA,GAAW,MAAM,GAAA,CAAI,IAAA,EAAK;AAChC,UAAA,MAAA,GAAS,OAAA,CAAQ,OAAA,IAAW,OAAA,CAAQ,MAAA,IAAU,MAAA;AAAA,QAChD,CAAA,CAAA,MAAQ;AAAA,QAER;AACA,QAAA,MAAM,IAAI,SAAS,MAAA,IAAU,CAAA,KAAA,EAAQ,IAAI,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,MAAA,EAAQ,SAAS,CAAA;AAAA,MAC1E;AAEA,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK,OAAO,KAAA,CAAA;AAC/B,MAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AAAA,IACzB,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,KAAA,GAAQ,GAAA;AACd,MAAA,MAAM,OAAA,GAAU,MAAM,IAAA,KAAS,YAAA;AAC/B,MAAA,MAAM,SAAS,KAAA,YAAiB,gBAAA;AAChC,MAAA,MAAM,WAAA,GACJ,CAAC,MAAA,IACD,CAAC,MAAA,EAAQ,YACR,OAAA,IAAW,KAAA,YAAiB,SAAA,IAAc,KAAA,CAAmB,MAAA,IAAU,GAAA,CAAA;AAE1E,MAAA,IAAI,MAAA,IAAU,CAAC,WAAA,IAAe,OAAA,KAAY,OAAA,EAAS;AACjD,QAAA,MAAM,KAAA;AAAA,MACR;AACA,MAAA,SAAA,GAAY,KAAA;AACZ,MAAA,MAAM,MAAM,mBAAA,GAAsB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAO,CAAC,CAAA;AAAA,IACxD,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,SAAS,CAAA;AACtB,MAAA,IAAI,MAAA,EAAQ,MAAA,CAAO,mBAAA,CAAoB,OAAA,EAAS,WAAW,CAAA;AAAA,IAC7D;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,IAAa,IAAI,KAAA,CAAM,gBAAgB,CAAA;AAC/C;AAEA,eAAsB,SAAA,CACpB,OAAA,EACA,SAAA,EACA,MAAA,EACkB;AAClB,EAAA,OAAO,OAAA,CAAiB,SAAS,SAAA,EAAW;AAAA,IAC1C,MAAA,EAAQ,KAAA;AAAA,IACR,MAAA;AAAA,IACA,OAAA,EAAS;AAAA,GACV,CAAA;AACH;AAEA,eAAsB,QAAA,CACpB,OAAA,EACA,IAAA,EACA,SAAA,EACA,MAAA,EACsB;AACtB,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,IAAW,CAAA,MAAA,EAAS,KAAK,EAAE,CAAA,CAAA;AAC7C,EAAA,OAAO,OAAA,CAAqB,SAAS,IAAA,EAAM;AAAA,IACzC,MAAA,EAAQ,MAAA;AAAA,IACR,IAAA,EAAM,EAAE,MAAA,EAAQ,IAAA,CAAK,IAAI,SAAA,EAAU;AAAA,IACnC;AAAA,GACD,CAAA;AACH;AAEA,eAAsB,WAAA,CACpB,OAAA,EACA,OAAA,EACA,SAAA,EACA,SACA,MAAA,EACsB;AACtB,EAAA,OAAO,OAAA,CAAqB,SAAS,UAAA,EAAY;AAAA,IAC/C,MAAA,EAAQ,MAAA;AAAA,IACR,IAAA,EAAM,EAAE,OAAA,EAAS,SAAA,EAAW,OAAA,EAAQ;AAAA,IACpC;AAAA,GACD,CAAA;AACH;AAEA,eAAsB,MAAA,CAAO,SAAiB,MAAA,EAAqC;AACjF,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,CAAc,SAAS,cAAA,EAAgB;AAAA,MAC3C,MAAA,EAAQ,MAAA;AAAA,MACR,MAAA;AAAA,MACA,OAAA,EAAS;AAAA,KACV,CAAA;AAAA,EACH,CAAA,SAAE;AACA,IAAA,YAAA,EAAa;AAAA,EACf;AACF;;;AChLO,SAAS,QAAQ,IAAA,EAAqC;AAC3D,EAAA,MAAM,EAAE,OAAA,EAAS,SAAA,EAAW,OAAA,GAAU,IAAA,EAAM,SAAQ,GAAI,IAAA;AACxD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAyB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAkB,IAAI,CAAA;AACpD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,CAAC,CAAA;AAClC,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AAEjC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAAA,EACvB,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAU,WAAA,EAAY;AAC5B,IAAA,IAAI,CAAC,SAAS,KAAA,EAAO;AACnB,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CAAS,IAAI,CAAA;AAEb,IAAA,SAAA,CAAU,SAAS,SAAA,EAAW,UAAA,CAAW,MAAM,CAAA,CAC5C,IAAA,CAAK,CAAC,CAAA,KAAM;AACX,MAAA,OAAA,CAAQ,CAAC,CAAA;AACT,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACf,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KAAe;AACrB,MAAA,IAAI,GAAA,CAAI,SAAS,YAAA,EAAc;AAC/B,MAAA,IAAI,eAAe,gBAAA,EAAkB;AACnC,QAAA,YAAA,EAAa;AACb,QAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,MACd,CAAA,MAAO;AACL,QAAA,QAAA,CAAS,GAAG,CAAA;AACZ,QAAA,UAAA,CAAW,UAAU,GAAG,CAAA;AAAA,MAC1B;AAAA,IACF,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,IAAI,CAAC,UAAA,CAAW,MAAA,CAAO,OAAA,aAAoB,KAAK,CAAA;AAAA,IAClD,CAAC,CAAA;AAEH,IAAA,OAAO,MAAM,WAAW,KAAA,EAAM;AAAA,EAChC,GAAG,CAAC,OAAA,EAAS,SAAA,EAAW,OAAA,EAAS,IAAI,CAAC,CAAA;AAEtC,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,MAAM,OAAA,CAAQ,CAAC,MAAM,CAAA,GAAI,CAAC,CAAA,EAAG,EAAE,CAAA;AAE3D,EAAA,OAAO;AAAA,IACL,YAAY,IAAA,KAAS,IAAA;AAAA,IACrB,IAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AACF;AClEA,IAAM,YAAA,GAA0B;AAAA,EAC9B,UAAU,EAAC;AAAA,EACX,QAAA,EAAU,KAAA;AAAA,EACV,MAAA,EAAQ,MAAA;AAAA,EACR,KAAA,EAAO;AACT,CAAA;AAEA,SAAS,OAAA,CAAQ,OAAkB,MAAA,EAA+B;AAChE,EAAA,QAAQ,OAAO,IAAA;AAAM,IACnB,KAAK,aAAA,EAAe;AAClB,MAAA,MAAM,OAAO,CAAC,GAAG,KAAA,CAAM,QAAA,EAAU,OAAO,OAAO,CAAA;AAC/C,MAAA,MAAM,OAAA,GAAU,KAAK,MAAA,GAAS,WAAA,GAAc,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,GAAS,WAAW,CAAA,GAAI,IAAA;AACpF,MAAA,OAAO,EAAE,GAAG,KAAA,EAAO,QAAA,EAAU,OAAA,EAAQ;AAAA,IACvC;AAAA,IACA,KAAK,YAAA;AACH,MAAA,OAAO,EAAE,GAAG,KAAA,EAAO,QAAA,EAAU,OAAO,OAAA,EAAQ;AAAA,IAC9C,KAAK,YAAA;AACH,MAAA,OAAO,EAAE,GAAG,KAAA,EAAO,MAAA,EAAQ,OAAO,OAAA,EAAQ;AAAA,IAC5C,KAAK,WAAA;AACH,MAAA,OAAO,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,OAAO,OAAA,EAAQ;AAAA,IAC3C,KAAK,kBAAA;AACH,MAAA,OAAO;AAAA,QACL,GAAG,KAAA;AAAA,QACH,QAAA,EAAU,MAAM,QAAA,CAAS,GAAA;AAAA,UAAI,CAAC,CAAA,KAC5B,CAAA,CAAE,EAAA,KAAO,MAAA,CAAO,OAAA,CAAQ,QAAA,GAAW,CAAA,GAAI,EAAE,GAAG,CAAA,EAAG,WAAA,EAAa,KAAA;AAAM;AACpE,OACF;AAAA,IACF,KAAK,cAAA;AACH,MAAA,OAAO,EAAE,GAAG,KAAA,EAAO,QAAA,EAAU,OAAO,OAAA,EAAQ;AAAA,IAC9C,KAAK,OAAA;AACH,MAAA,OAAO,YAAA;AAAA,IACT;AACE,MAAA,OAAO,KAAA;AAAA;AAEb;AAsBO,SAAS,QAAQ,IAAA,EAAqC;AAC3D,EAAA,MAAM,EAAE,OAAA,EAAS,SAAA,EAAW,iBAAiB,eAAA,EAAiB,OAAA,EAAS,eAAc,GAAI,IAAA;AACzF,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,UAAA,CAAW,SAAS,YAAY,CAAA;AAC1D,EAAA,MAAM,QAAA,GAAWC,OAA+B,IAAI,CAAA;AACpD,EAAA,MAAM,kBAAA,GAAqBA,OAAO,eAAe,CAAA;AACjD,EAAA,MAAM,UAAA,GAAaA,OAAO,OAAO,CAAA;AACjC,EAAA,MAAM,gBAAA,GAAmBA,OAAO,aAAa,CAAA;AAE7C,EAAAC,UAAU,MAAM;AACd,IAAA,kBAAA,CAAmB,OAAA,GAAU,eAAA;AAC7B,IAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AACrB,IAAA,gBAAA,CAAiB,OAAA,GAAU,aAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,eAAA,EAAiB,OAAA,EAAS,aAAa,CAAC,CAAA;AAE5C,EAAAA,UAAU,MAAM;AACd,IAAA,IAAI,eAAA,IAAmB,eAAA,CAAgB,MAAA,GAAS,CAAA,EAAG;AACjD,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,cAAA,EAAgB,OAAA,EAAS,iBAAiB,CAAA;AAAA,IAC7D;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAAA,UAAU,MAAM;AACd,IAAA,kBAAA,CAAmB,OAAA,GAAU,MAAM,QAAQ,CAAA;AAAA,EAC7C,CAAA,EAAG,CAAC,KAAA,CAAM,QAAQ,CAAC,CAAA;AAEnB,EAAAA,UAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AAAA,IAC1B,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,iBAAA,GAAoBC,WAAAA,CAAY,CAAC,OAAA,KAA6B;AAClE,IAAA,MAAM,GAAA,GAAe;AAAA,MACnB,IAAI,YAAA,EAAa;AAAA,MACjB,IAAA,EAAM,MAAA;AAAA,MACN,OAAA;AAAA,MACA,SAAA,EAAW,KAAK,GAAA;AAAI,KACtB;AACA,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,aAAA,EAAe,OAAA,EAAS,KAAK,CAAA;AAC9C,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,iBAAA,GAAoBA,WAAAA,CAAY,CAAC,IAAA,KAAsB;AAC3D,IAAA,MAAM,MAAA,GAAkB;AAAA,MACtB,IAAI,YAAA,EAAa;AAAA,MACjB,IAAA,EAAM,KAAA;AAAA,MACN,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,KAAA,EAAO,IAAA,CAAK,KAAA,IAAS,EAAC;AAAA,MACtB,WAAA,EAAA,CAAc,IAAA,CAAK,KAAA,IAAS,IAAI,MAAA,GAAS,CAAA;AAAA,MACzC,SAAA,EAAW,KAAK,GAAA;AAAI,KACtB;AACA,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,aAAA,EAAe,OAAA,EAAS,QAAQ,CAAA;AAAA,EACnD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,oBAAA,GAAuBA,WAAAA,CAAY,CAAC,MAAA,KAAoB;AAC5D,IAAA,QAAA,CAAS,EAAE,MAAM,kBAAA,EAAoB,OAAA,EAAS,EAAE,QAAA,EAAU,MAAA,IAAU,CAAA;AAAA,EACtE,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,QAAA,GAAWA,WAAAA;AAAA,IACf,OAAO,IAAA,KAAiB;AACtB,MAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,MAAA,IAAI,CAAC,OAAA,IAAW,CAAC,SAAA,EAAW;AAE5B,MAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AACxB,MAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,MAAA,QAAA,CAAS,OAAA,GAAU,UAAA;AAEnB,MAAA,oBAAA,EAAqB;AACrB,MAAA,iBAAA,CAAkB,OAAO,CAAA;AACzB,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,YAAA,EAAc,OAAA,EAAS,MAAM,CAAA;AAC9C,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,YAAA,EAAc,OAAA,EAAS,WAAW,CAAA;AACnD,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAAS,MAAM,CAAA;AAE7C,MAAA,IAAI;AACF,QAAA,MAAM,OAAA,GAAU,KAAA,CAAM,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,CAAA;AACxC,QAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAe,OAAA,EAAS,SAAS,SAAA,EAAW,OAAA,EAAS,WAAW,MAAM,CAAA;AACzF,QAAA,iBAAA,CAAkB,IAAI,CAAA;AACtB,QAAA,QAAA,CAAS,EAAE,IAAA,EAAM,YAAA,EAAc,OAAA,EAAS,QAAQ,CAAA;AAAA,MAClD,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,KAAA,GAAQ,GAAA;AACd,QAAA,IAAI,KAAA,CAAM,SAAS,YAAA,EAAc;AACjC,QAAA,IAAI,iBAAiB,gBAAA,EAAkB;AACrC,UAAA,gBAAA,CAAiB,OAAA,IAAU;AAAA,QAC7B,CAAA,MAAO;AACL,UAAA,UAAA,CAAW,UAAU,KAAK,CAAA;AAC1B,UAAA,QAAA,CAAS,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAAS,KAAA,CAAM,SAAS,CAAA;AAAA,QACxD;AACA,QAAA,QAAA,CAAS,EAAE,IAAA,EAAM,YAAA,EAAc,OAAA,EAAS,SAAS,CAAA;AAAA,MACnD,CAAA,SAAE;AACA,QAAA,QAAA,CAAS,EAAE,IAAA,EAAM,YAAA,EAAc,OAAA,EAAS,OAAO,CAAA;AAAA,MACjD;AAAA,IACF,CAAA;AAAA,IACA,CAAC,OAAA,EAAS,SAAA,EAAW,MAAM,QAAA,EAAU,iBAAA,EAAmB,mBAAmB,oBAAoB;AAAA,GACjG;AAEA,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC9B,IAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AACxB,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AAAA,EAC5B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,WAAA,GAAcA,WAAAA,CAAY,CAAC,OAAA,KAAuB;AACtD,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,cAAA,EAAgB,OAAA,EAAS,SAAS,CAAA;AAAA,EACrD,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,SAAA,GAAYA,WAAAA,CAAY,CAAC,KAAA,KAAmB;AAChD,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,YAAA,EAAc,OAAA,EAAS,OAAO,CAAA;AAAA,EACjD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,QAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,oBAAA;AAAA,IACA,KAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AACF;AC7KO,SAAS,QAAQ,IAAA,EAAqC;AAC3D,EAAA,MAAM,EAAE,OAAA,EAAS,SAAA,EAAW,aAAA,EAAe,SAAQ,GAAI,IAAA;AACvD,EAAA,MAAM,QAAA,GAAWF,OAA+B,IAAI,CAAA;AACpD,EAAA,MAAM,gBAAA,GAAmBA,OAAO,aAAa,CAAA;AAC7C,EAAA,MAAM,UAAA,GAAaA,OAAO,OAAO,CAAA;AAEjC,EAAAC,UAAU,MAAM;AACd,IAAA,gBAAA,CAAiB,OAAA,GAAU,aAAA;AAC3B,IAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAAA,EACvB,CAAA,EAAG,CAAC,aAAA,EAAe,OAAO,CAAC,CAAA;AAE3B,EAAAA,UAAU,MAAM;AACd,IAAA,OAAO,MAAM,QAAA,CAAS,OAAA,EAAS,KAAA,EAAM;AAAA,EACvC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,QAAA,GAAWC,WAAAA;AAAA,IACf,OAAO,IAAA,KAA4C;AACjD,MAAA,IAAI,CAAC,WAAW,OAAO,IAAA;AACvB,MAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AACxB,MAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,MAAA,QAAA,CAAS,OAAA,GAAU,UAAA;AAEnB,MAAA,IAAI;AACF,QAAA,OAAO,MAAM,QAAA,CAAS,OAAA,EAAS,IAAA,EAAM,SAAA,EAAW,WAAW,MAAM,CAAA;AAAA,MACnE,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,KAAA,GAAQ,GAAA;AACd,QAAA,IAAI,KAAA,CAAM,IAAA,KAAS,YAAA,EAAc,OAAO,IAAA;AACxC,QAAA,IAAI,iBAAiB,gBAAA,EAAkB;AACrC,UAAA,gBAAA,CAAiB,OAAA,IAAU;AAAA,QAC7B,CAAA,MAAO;AACL,UAAA,UAAA,CAAW,UAAU,KAAK,CAAA;AAAA,QAC5B;AACA,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,CAAA;AAAA,IACA,CAAC,SAAS,SAAS;AAAA,GACrB;AAEA,EAAA,OAAO,EAAE,QAAA,EAAS;AACpB;AC7BO,SAAS,UAAA,CAAW,IAAA,GAA0B,EAAC,EAAqB;AACzE,EAAA,MAAM,EAAE,UAAA,GAAa,2BAAA,EAA6B,QAAA,EAAS,GAAI,IAAA;AAC/D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIC,SAA4B,IAAI,CAAA;AAC9D,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,SAAS,CAAC,CAAA;AAChD,EAAA,MAAM,UAAA,GAAaH,OAAO,KAAK,CAAA;AAC/B,EAAA,MAAM,WAAA,GAAcA,OAAO,QAAQ,CAAA;AAEnC,EAAAC,UAAU,MAAM;AACd,IAAA,WAAA,CAAY,OAAA,GAAU,QAAA;AAAA,EACxB,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAAA,UAAU,MAAM;AACd,IAAA,MAAM,UAAU,WAAA,EAAY;AAC5B,IAAA,UAAA,CAAW,OAAO,CAAA;AAClB,IAAA,cAAA,CAAe,UAAU,OAAA,CAAQ,SAAA,GAAY,IAAA,CAAK,GAAA,KAAQ,CAAC,CAAA;AAAA,EAC7D,CAAA,EAAG,EAAE,CAAA;AAEL,EAAAA,UAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,MAAM,QAAA,GAAW,MAAA,CAAO,WAAA,CAAY,MAAM;AACxC,MAAA,MAAM,UAAU,WAAA,EAAY;AAC5B,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AACvB,UAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AACrB,UAAA,WAAA,CAAY,OAAA,IAAU;AAAA,QACxB;AACA,QAAA,UAAA,CAAW,IAAI,CAAA;AACf,QAAA,cAAA,CAAe,CAAC,CAAA;AAChB,QAAA;AAAA,MACF;AACA,MAAA,cAAA,CAAe,OAAA,CAAQ,SAAA,GAAY,IAAA,CAAK,GAAA,EAAK,CAAA;AAAA,IAC/C,GAAG,IAAK,CAAA;AACR,IAAA,OAAO,MAAM,MAAA,CAAO,aAAA,CAAc,QAAQ,CAAA;AAAA,EAC5C,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAAA,UAAU,MAAM;AACd,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,IAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAAoB;AACrC,MAAA,IAAI,CAAA,CAAE,GAAA,IAAO,CAAA,CAAE,GAAA,KAAQ,aAAA,EAAe;AACtC,MAAA,MAAM,UAAU,WAAA,EAAY;AAC5B,MAAA,UAAA,CAAW,OAAO,CAAA;AAClB,MAAA,cAAA,CAAe,UAAU,OAAA,CAAQ,SAAA,GAAY,IAAA,CAAK,GAAA,KAAQ,CAAC,CAAA;AAAA,IAC7D,CAAA;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC5C,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,SAAS,CAAA;AAAA,EAC9D,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,QAAA,GAAWC,WAAAA;AAAA,IACf,CAAC,OAAe,MAAA,KAAmB;AACjC,MAAA,UAAA,CAAW,OAAA,GAAU,KAAA;AACrB,MAAA,MAAM,OAAA,GAAU,YAAA;AAAA,QACd;AAAA,UACE,KAAA;AAAA,UACA,MAAA;AAAA,UACA,SAAA,EAAW,WAAA,EAAY,EAAG,SAAA,IAAa,YAAA,EAAa;AAAA,UACpD,OAAA,EAAS,WAAA,EAAY,EAAG,OAAA,IAAW;AAAC,SACtC;AAAA,QACA;AAAA,OACF;AACA,MAAA,UAAA,CAAW,OAAO,CAAA;AAClB,MAAA,cAAA,CAAe,UAAU,OAAA,CAAQ,SAAA,GAAY,IAAA,CAAK,GAAA,KAAQ,CAAC,CAAA;AAAA,IAC7D,CAAA;AAAA,IACA,CAAC,UAAU;AAAA,GACb;AAEA,EAAA,MAAM,UAAA,GAAaA,WAAAA;AAAA,IACjB,CAAC,OAAA,KAAuB;AACtB,MAAA,MAAM,OAAA,GAAU,YAAA,CAAa,EAAE,OAAA,IAAW,UAAU,CAAA;AACpD,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,UAAA,CAAW,OAAO,CAAA;AAClB,QAAA,cAAA,CAAe,OAAA,CAAQ,SAAA,GAAY,IAAA,CAAK,GAAA,EAAK,CAAA;AAAA,MAC/C;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU;AAAA,GACb;AAEA,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC9B,IAAA,MAAM,OAAA,GAAU,aAAa,UAAU,CAAA;AACvC,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,UAAA,CAAW,OAAO,CAAA;AAClB,MAAA,cAAA,CAAe,OAAA,CAAQ,SAAA,GAAY,IAAA,CAAK,GAAA,EAAK,CAAA;AAAA,IAC/C;AAAA,EACF,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAEf,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC9B,IAAA,YAAA,EAAa;AACb,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,cAAA,CAAe,CAAC,CAAA;AAChB,IAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAAA,EACvB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,OAAA,EAAS,WAAA,EAAa,QAAA,EAAU,UAAA,EAAY,OAAO,KAAA,EAAM;AACpE;;;ACrHA,IAAA,YAAA,GAAA;AAAA,EAAC,IAAA,EAAAE,WAAAA;AAAA,EA0BA,cAAA,EAAAC,qBAAAA;AAAA,EAwBA,aAAA,EAAAC,oBAAAA;AAAA,EAIA,YAAA,EAAAC,mBAAAA;AAAA,EAIA,MAAA,EAAAC,aAAAA;AAAA,EAmCA,MAAA,EAAAC,aAAAA;AAAA,EAUA,WAAA,EAAAC,kBAAAA;AAAA,EAMA,UAAA,EAAAC,iBAAAA;AAAA,EAMA,aAAA,EAAAC,oBAAAA;AAAA,EAMA,UAAA,EAAAC,iBAAAA;AAAA,EAeA,IAAA,EAAAC,WAAAA;AAAA,EAoBA,MAAA,EAAAC,aAAAA;AAAA,EAeA,QAAA,EAAAC,eAAAA;AAAA,EAiDA,SAAA,EAAAC,gBAAAA;AAAA,EAOA,UAAA,EAAAC,iBAAAA;AAAA,EAcA,OAAA,EAAAC,cAAAA;AAAA,EAOA,IAAA,EAAAC,WAAAA;AAAA,EAiBA,UAAA,EAAAC,iBAAAA;AAAA,EAKA,YAAA,EAAAC,mBAAAA;AAAA,EAKA,MAAA,EAAAC,aAAAA;AAAA,EAUA,SAAA,EAAAC,gBAAAA;AAAA,EAoBA,KAAA,EAAAC,YAAAA;AAAA,EAQA,QAAA,EAAAC,eAAAA;AAAA,EAsBA,UAAA,EAAAC,iBAAAA;AAAA,EAiBA,QAAA,EAAAC,eAAAA;AAAA,EAWA,YAAA,EAAAC,mBAAAA;AAAA,EAIA,aAAA,EAAAC,oBAAAA;AAAA,EAKA,YAAA,EAAAC,mBAAAA;AAAA,EAMA,cAAA,EAAAC,qBAAAA;AAAA,EAaA,kBAAA,EAAAC,yBAAAA;AAAA,EAWA,uBAAA,EAAAC,8BAAAA;AAAA,EAQA,WAAA,EAAAC;AAAA,CAAA;AClZM,SAAS,QAAA,CAAS,EAAE,SAAA,EAAW,QAAA,EAAU,cAAa,EAAkB;AAC7E,EAAA,MAAM,cAAc,MAAM;AACxB,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,YAAA,EAAa;AACb,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,MAAA,CAAO,SAAS,IAAA,GAAO,QAAA;AAAA,IACzB;AAAA,EACF,CAAA;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,YAAA,CAAO,QAAA,EACrB,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,SAAI,SAAA,EAAW,YAAA,CAAO,YAAA,EAAc,aAAA,EAAY,QAAO,QAAA,EAAA,WAAA,EAAE,CAAA;AAAA,oBAC1D,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,YAAA,CAAO,eAAe,QAAA,EAAA,gBAAA,EAAc,CAAA;AAAA,oBACpD,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,YAAA,CAAO,YAAA,EAAc,QAAA,EAAA;AAAA,MAAA,YAAA;AAAA,MACxB,SAAA;AAAA,MAAU;AAAA,KAAA,EACvB,CAAA;AAAA,oBACA,GAAA,CAAC,YAAO,IAAA,EAAK,QAAA,EAAS,WAAW,YAAA,CAAO,cAAA,EAAgB,OAAA,EAAS,WAAA,EAAa,QAAA,EAAA,2BAAA,EAE9E,CAAA;AAAA,oBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,YAAA,CAAO,kBAAA,EACrB,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,YAAA,CAAO,uBAAA,EAAyB,QAAA,EAAA,YAAA,EAAU,CAAA;AAAA,sBAC1D,GAAA,CAAC,SAAI,QAAA,EAAA,4DAAA,EAAqD,CAAA;AAAA,sBAC1D,GAAA,CAAC,SAAI,QAAA,EAAA,oDAAA,EAA6C,CAAA;AAAA,sBAClD,GAAA,CAAC,SAAI,QAAA,EAAA,4DAAA,EAAqD;AAAA,KAAA,EAC5D;AAAA,GAAA,EACF,CAAA;AAEJ;AC3BO,SAAS,KAAK,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,SAAQ,EAAc;AACnE,EAAA,MAAM,OAAA,GAAU;AAAA,IACd,YAAA,CAAO,IAAA;AAAA,IACP,MAAA,GAAS,aAAO,UAAA,GAAa,EAAA;AAAA,IAC7B,QAAA,GAAW,aAAO,YAAA,GAAe;AAAA,GACnC,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACEC,IAAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,QAAA;AAAA,MACL,SAAA,EAAW,OAAA;AAAA,MACX,QAAA;AAAA,MACA,OAAA,EAAS,MAAM,CAAC,QAAA,IAAY,QAAQ,IAAI,CAAA;AAAA,MAEvC,QAAA,EAAA;AAAA,QAAA,IAAA,CAAK,IAAA,mBAAOC,GAAAA,CAAC,MAAA,EAAA,EAAK,eAAY,MAAA,EAAQ,QAAA,EAAA,IAAA,CAAK,MAAK,CAAA,GAAU,IAAA;AAAA,wBAC3DA,GAAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,IAAA,CAAK,KAAA,EAAM;AAAA;AAAA;AAAA,GACpB;AAEJ;ACpBO,SAAS,OAAA,CAAQ,EAAE,KAAA,EAAO,QAAA,EAAU,SAAQ,EAAiB;AAClE,EAAA,IAAI,CAAC,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,IAAA;AACzC,EAAA,uBACEA,IAAC,KAAA,EAAA,EAAI,SAAA,EAAW,aAAO,OAAA,EACpB,QAAA,EAAA,KAAA,CAAM,IAAI,CAAC,CAAA,qBACVA,GAAAA,CAAC,IAAA,EAAA,EAAgB,MAAM,CAAA,EAAG,QAAA,EAAoB,WAAnC,CAAA,CAAE,EAAmD,CACjE,CAAA,EACH,CAAA;AAEJ;ACLA,IAAM,YAAA,GAAe;AAAA,EACnB,GAAA;AAAA,EAAK,QAAA;AAAA,EACL,GAAA;AAAA,EAAK,IAAA;AAAA,EACL,GAAA;AAAA,EAAK,KAAA;AAAA,EACL,GAAA;AAAA,EAAK,QAAA;AAAA,EAAU,KAAA;AAAA,EACf,IAAA;AAAA,EACA,GAAA;AAAA,EACA,MAAA;AAAA,EAAQ,KAAA;AAAA,EACR,GAAA;AAAA,EACA,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,IAAA;AAAA,EACZ,YAAA;AAAA,EACA;AACF,CAAA;AAEA,IAAM,YAAA,GAAe,CAAC,MAAA,EAAQ,QAAA,EAAU,OAAO,OAAO,CAAA;AAEtD,IAAM,YAAA,GAAe;AAAA,EACnB,YAAA;AAAA,EACA,YAAA;AAAA;AAAA,EAEA,uBAAA,EAAyB,KAAA;AAAA,EACzB,kBAAA,EAAoB;AACtB,CAAA;AAmBA,SAAS,qBAAqB,IAAA,EAAsB;AAClD,EAAA,OAAO,KAEJ,OAAA,CAAQ,mCAAA,EAAqC,WAAW,CAAA,CAExD,OAAA,CAAQ,wBAAwB,WAAW,CAAA,CAC3C,QAAQ,kBAAA,EAAoB,WAAW,EAEvC,OAAA,CAAQ,gCAAA,EAAkC,WAAW,CAAA,CACrD,OAAA,CAAQ,oDAAoD,WAAW,CAAA;AAC5E;AAWO,SAAS,eAAe,IAAA,EAAsB;AACnD,EAAA,IAAI,CAAC,MAAM,OAAO,EAAA;AAElB,EAAA,MAAM,OAAA,GAAU,qBAAqB,IAAI,CAAA;AAGzC,EAAA,MAAM,aAAa,OAAA,CAChB,KAAA,CAAM,QAAQ,CAAA,CACd,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,QAAQ,KAAA,EAAO,MAAM,CAAC,CAAA,CACzC,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,CAAC,CAAA;AAE7B,EAAA,MAAM,OAAO,UAAA,CAAW,MAAA,GAAS,CAAA,GAC7B,UAAA,CAAW,IAAI,CAAC,CAAA,KAAM,CAAA,GAAA,EAAM,CAAC,MAAM,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA,GAC5C,UAAA,CAAW,CAAC,CAAA,IAAK,EAAA;AAErB,EAAA,OAAO,SAAA,CAAU,QAAA,CAAS,IAAA,EAAM,YAAY,CAAA;AAC9C;AClFO,SAAS,aAAA,CAAc,EAAE,OAAA,EAAS,WAAA,EAAY,EAAuB;AAC1E,EAAA,MAAM,KAAA,GAAQ,QAAQ,IAAA,KAAS,KAAA;AAC/B,EAAA,MAAM,WAAA,GAAc,KAAA,GAChB,CAAA,EAAG,YAAA,CAAO,MAAM,CAAA,CAAA,EAAI,YAAA,CAAO,SAAS,CAAA,CAAA,GACpC,CAAA,EAAG,YAAA,CAAO,MAAM,CAAA,CAAA,EAAI,aAAO,UAAU,CAAA,CAAA;AAEzC,EAAA,uBACED,KAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,aAAA,EAAe,QAAA,EAAS,EACrD,QAAA,EAAA;AAAA,oBAAAC,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,WAAA,EACb,kCACCA,GAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,WAAW,YAAA,CAAO,QAAA;AAAA,QAClB,yBAAyB,EAAE,MAAA,EAAQ,cAAA,CAAe,OAAA,CAAQ,OAAO,CAAA;AAAE;AAAA,KACrE,GAEA,QAAQ,OAAA,EAEZ,CAAA;AAAA,IACC,SAAS,OAAA,CAAQ,KAAA,IAAS,QAAQ,KAAA,CAAM,MAAA,GAAS,oBAChDA,GAAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,OAAO,OAAA,CAAQ,KAAA;AAAA,QACf,QAAA,EAAU,CAAC,OAAA,CAAQ,WAAA;AAAA,QACnB,OAAA,EAAS;AAAA;AAAA,KACX,GACE;AAAA,GAAA,EACN,CAAA;AAEJ;ACnCO,SAAS,eAAA,GAAkB;AAChC,EAAA,uBACED,KAAC,KAAA,EAAA,EAAI,SAAA,EAAW,aAAO,MAAA,EAAQ,WAAA,EAAU,QAAA,EAAS,YAAA,EAAW,qBAAA,EAC3D,QAAA,EAAA;AAAA,oBAAAC,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,YAAA,CAAO,SAAA,EAAW,CAAA;AAAA,oBACnCA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,aAAO,SAAA,EAAW,CAAA;AAAA,oBACnCA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,aAAO,SAAA,EAAW;AAAA,GAAA,EACrC,CAAA;AAEJ;ACSO,SAAS,QAAA,CAAS;AAAA,EACvB,UAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,EAAkB;AAChB,EAAA,MAAM,MAAA,GAASrC,OAA8B,IAAI,CAAA;AAEjD,EAAAC,UAAU,MAAM;AACd,IAAA,MAAA,CAAO,OAAA,EAAS,cAAA,CAAe,EAAE,QAAA,EAAU,UAAU,CAAA;AAAA,EACvD,CAAA,EAAG,CAAC,QAAA,CAAS,MAAA,EAAQ,QAAQ,CAAC,CAAA;AAE9B,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,uBACEoC,IAAC,KAAA,EAAA,EAAI,SAAA,EAAW,aAAO,IAAA,EACrB,QAAA,kBAAAA,GAAAA,CAAC,eAAA,EAAA,EAAgB,CAAA,EACnB,CAAA;AAAA,EAEJ;AAEA,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,uBACEA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,YAAA,CAAO,MACrB,QAAA,kBAAAA,GAAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,SAAA;AAAA,QACA,QAAA;AAAA,QACA;AAAA;AAAA,KACF,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACED,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,aAAO,IAAA,EACpB,QAAA,EAAA;AAAA,IAAA,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,qBACbC,GAAAA,CAAC,aAAA,EAAA,EAAyB,OAAA,EAAS,CAAA,EAAG,WAAA,EAAA,EAAlB,CAAA,CAAE,EAA0C,CACjE,CAAA;AAAA,IACA,QAAA,mBAAWA,GAAAA,CAAC,eAAA,EAAA,EAAgB,CAAA,GAAK,IAAA;AAAA,IACjC,KAAA,mBAAQA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAW,YAAA,CAAO,WAAA,EAAc,iBAAM,CAAA,GAAS,IAAA;AAAA,oBAC7DA,GAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,MAAA,EAAQ;AAAA,GAAA,EACpB,CAAA;AAEJ;;;AClEO,SAAS,gBAAgB,WAAA,EAA6B;AAC3D,EAAA,IAAI,WAAA,IAAe,GAAG,OAAO,SAAA;AAC7B,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,WAAA,GAAc,GAAI,CAAA;AAClD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,YAAA,GAAe,IAAI,CAAA;AAC5C,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAO,YAAA,GAAe,OAAQ,EAAE,CAAA;AACrD,EAAA,MAAM,UAAU,YAAA,GAAe,EAAA;AAE/B,EAAA,IAAI,QAAQ,CAAA,EAAG,OAAO,CAAA,EAAG,KAAK,KAAK,OAAO,CAAA,MAAA,CAAA;AAC1C,EAAA,IAAI,UAAU,CAAA,EAAG,OAAO,CAAA,EAAG,OAAO,KAAK,OAAO,CAAA,MAAA,CAAA;AAC9C,EAAA,OAAO,GAAG,OAAO,CAAA,MAAA,CAAA;AACnB;ACCO,SAAS,UAAA,CAAW;AAAA,EACzB,SAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAoB;AAClB,EAAA,uBACED,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,aAAO,MAAA,EACrB,QAAA,EAAA;AAAA,oBAAAA,KAAC,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAAC,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,YAAA,CAAO,aAAc,QAAA,EAAA,SAAA,EAAU,CAAA;AAAA,MAC9C,aAAA,IAAiB,cAAc,CAAA,mBAC9BD,KAAC,KAAA,EAAA,EAAI,SAAA,EAAW,aAAO,UAAA,EAAY,QAAA,EAAA;AAAA,QAAA,WAAA;AAAA,QAAU,gBAAgB,WAAW;AAAA,OAAA,EAAE,CAAA,GACxE;AAAA,KAAA,EACN,CAAA;AAAA,oBACAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,aAAO,aAAA,EACpB,QAAA,EAAA;AAAA,MAAA,OAAA,mBACCC,GAAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,WAAW,YAAA,CAAO,UAAA;AAAA,UAClB,OAAA,EAAS,OAAA;AAAA,UACT,YAAA,EAAW,oBAAA;AAAA,UACX,KAAA,EAAM,oBAAA;AAAA,UACP,QAAA,EAAA;AAAA;AAAA,OAED,GACE,IAAA;AAAA,sBACJA,GAAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,WAAW,YAAA,CAAO,UAAA;AAAA,UAClB,OAAA,EAAS,OAAA;AAAA,UACT,YAAA,EAAW,YAAA;AAAA,UACX,KAAA,EAAM,OAAA;AAAA,UACP,QAAA,EAAA;AAAA;AAAA;AAED,KAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ;ACzCO,SAAS,SAAA,CAAU,EAAE,QAAA,EAAU,WAAA,EAAa,QAAO,EAAmB;AAC3E,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIlC,SAAS,EAAE,CAAA;AAErC,EAAA,MAAM,SAAS,MAAM;AACnB,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,IAAA,IAAI,CAAC,WAAW,QAAA,EAAU;AAC1B,IAAA,MAAA,CAAO,OAAO,CAAA;AACd,IAAA,QAAA,CAAS,EAAE,CAAA;AAAA,EACb,CAAA;AAEA,EAAA,uBACEiC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,aAAO,KAAA,EACrB,QAAA,EAAA;AAAA,oBAAAC,GAAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,MAAA;AAAA,QACL,WAAW,YAAA,CAAO,QAAA;AAAA,QAClB,KAAA;AAAA,QACA,UAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,QACxC,SAAA,EAAW,CAAC,CAAA,KAAM;AAChB,UAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,OAAA,IAAW,CAAC,EAAE,QAAA,EAAU;AACpC,YAAA,CAAA,CAAE,cAAA,EAAe;AACjB,YAAA,MAAA,EAAO;AAAA,UACT;AAAA,QACF,CAAA;AAAA,QACA,QAAA;AAAA,QACA,aAAa,WAAA,IAAe,sBAAA;AAAA,QAC5B,YAAA,EAAW;AAAA;AAAA,KACb;AAAA,oBACAA,GAAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,QAAA;AAAA,QACL,WAAW,YAAA,CAAO,UAAA;AAAA,QAClB,OAAA,EAAS,MAAA;AAAA,QACT,QAAA,EAAU,QAAA,IAAY,KAAA,CAAM,IAAA,GAAO,MAAA,KAAW,CAAA;AAAA,QAC/C,QAAA,EAAA;AAAA;AAAA;AAED,GAAA,EACF,CAAA;AAEJ;ACrCO,SAAS,cAAA,CAAe,EAAE,QAAA,EAAU,OAAA,EAAS,YAAW,EAAwB;AACrF,EAAA,MAAM,QAAA,GAAW,QAAA,KAAa,aAAA,GAAgB,YAAA,CAAO,eAAe,YAAA,CAAO,aAAA;AAC3E,EAAA,uBACEA,GAAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,QAAA;AAAA,MACL,SAAA,EAAW,CAAA,EAAG,YAAA,CAAO,cAAc,IAAI,QAAQ,CAAA,CAAA;AAAA,MAC/C,OAAA;AAAA,MACA,YAAA,EAAW,WAAA;AAAA,MACX,KAAA,EAAO,UAAA,GAAa,EAAE,UAAA,EAAY,YAAW,GAAI,MAAA;AAAA,MAClD,QAAA,EAAA;AAAA;AAAA,GAED;AAEJ;ACPA,IAAM,kBAAA,GAAqB,iBAAA;AAC3B,IAAM,mBAAA,GAAsB,SAAA;AAC5B,IAAM,kBAAA,GAAqB,KAAA;AAE3B,IAAM,gBAAA,GACJ,yLAAA;AAKF,IAAM,QAAA,GACJ,iGAAA;AAEF,SAAS,YAAA,CAAa,MAA0B,IAAA,EAAkC;AAChF,EAAA,MAAM,QAAA,GAAW,IAAA,GACb,CAAA,cAAA,EAAiB,IAAI,CAAA,WAAA,EAAc,IAAA,GAAO,IAAA,GAAO,SAAS,CAAA,QAAA,CAAA,GAC1D,CAAA,kBAAA,EAAqB,IAAA,GAAO,IAAA,GAAO,SAAS,CAAA,QAAA,CAAA;AAChD,EAAA,OAAO,GAAG,QAAQ;;AAAA,EAAO,gBAAgB;;AAAA,EAAO,QAAQ,CAAA,CAAA;AAC1D;AAEO,SAAS,WAAW,KAAA,EAAwB;AACjD,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,SAAA,GAAY,kBAAA;AAAA,IACZ,QAAA;AAAA,IACA,UAAA,GAAa,2BAAA;AAAA,IACb,cAAA;AAAA,IACA,SAAA,GAAY,kBAAA;AAAA,IACZ,UAAA,GAAa,mBAAA;AAAA,IACb,QAAA,GAAW,cAAA;AAAA,IACX,WAAA,GAAc,KAAA;AAAA,IACd,aAAA,GAAgB,IAAA;AAAA,IAChB,OAAA;AAAA,IACA,QAAA;AAAA,IACA,eAAA;AAAA,IACA;AAAA,GACF,GAAI,KAAA;AAEJ,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIlC,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,SAAS,WAAW,CAAA;AAE5C,EAAAF,UAAU,MAAM;AACd,IAAA,UAAA,CAAW,IAAI,CAAA;AAAA,EACjB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA,EAAO;AAAA,MACL,UAAA,CAAW;AAAA,IACb,UAAA,EAAY,UAAA;AAAA,IACZ,QAAA,EAAU;AAAA,GACX,CAAA;AAED,EAAA,MAAM,EAAE,UAAA,EAAY,IAAA,EAAM,OAAA,EAAS,WAAA,KAAgB,OAAA,CAAQ;AAAA,IACzD,OAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAS,OAAA,IAAW,IAAA;AAAA,IACpB;AAAA,GACD,CAAA;AAED,EAAA,MAAM,iBAAA,GAAoBC,YAAY,MAAM;AAC1C,IAAA,iBAAA,EAAkB;AAClB,IAAA,eAAA,IAAkB;AAAA,EACpB,CAAA,EAAG,CAAC,iBAAA,EAAmB,eAAe,CAAC,CAAA;AAEvC,EAAA,MAAM,mBAAA,GAAsBA,WAAAA;AAAA,IAC1B,CAAC,OAAA,KAAuB;AACtB,MAAA,IAAI,CAAC,OAAA,EAAS;AACd,MAAA,UAAA,CAAW,OAAO,CAAA;AAAA,IACpB,CAAA;AAAA,IACA,CAAC,SAAS,UAAU;AAAA,GACtB;AAEA,EAAA,MAAM;AAAA,IACJ,KAAA,EAAO,SAAA;AAAA,IACP,QAAA;AAAA,IACA,iBAAA;AAAA,IACA,iBAAA;AAAA,IACA,oBAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,KAAA,EAAO;AAAA,MACL,OAAA,CAAQ;AAAA,IACV,OAAA;AAAA,IACA,SAAA,EAAW,SAAS,SAAA,IAAa,IAAA;AAAA,IACjC,iBAAiB,OAAA,EAAS,OAAA;AAAA,IAC1B,eAAA,EAAiB,mBAAA;AAAA,IACjB,aAAA,EAAe,iBAAA;AAAA,IACf;AAAA,GACD,CAAA;AAED,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,OAAA,CAAQ;AAAA,IAC3B,OAAA;AAAA,IACA,SAAA,EAAW,SAAS,SAAA,IAAa,IAAA;AAAA,IACjC,aAAA,EAAe,iBAAA;AAAA,IACf;AAAA,GACD,CAAA;AAED,EAAA,MAAM,eAAA,GAAkB,QAAQ,OAAO,EAAE,OAAO,KAAA,EAAM,CAAA,EAAI,EAAE,CAAA;AAE5D,EAAAD,UAAU,MAAM;AACd,IAAA,IAAI,CAAC,UAAA,IAAc,CAAC,IAAA,EAAM;AAC1B,IAAA,IAAI,SAAA,CAAU,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG;AACnC,IAAA,IAAI,gBAAgB,KAAA,EAAO;AAC3B,IAAA,eAAA,CAAgB,KAAA,GAAQ,IAAA;AACxB,IAAA,MAAM,WAAA,GACJ,kBACA,IAAA,EAAM,cAAA,IACN,aAAa,IAAA,EAAM,IAAA,EAAM,MAAM,IAAI,CAAA;AACrC,IAAA,iBAAA,CAAkB;AAAA,MAChB,OAAA,EAAS,WAAA;AAAA,MACT,KAAA,EAAO,IAAA,EAAM,SAAA,IAAa,EAAC;AAAA,MAC3B,SAAA,EAAW,SAAS,SAAA,IAAa,EAAA;AAAA,MACjC,SAAA,EAAW;AAAA,KACZ,CAAA;AACD,IAAA,OAAA,IAAU;AAAA,EACZ,CAAA,EAAG,CAAC,UAAA,EAAY,IAAA,EAAM,SAAA,CAAU,QAAA,CAAS,MAAA,EAAQ,cAAA,EAAgB,OAAA,EAAS,IAAA,EAAM,iBAAA,EAAmB,OAAA,EAAS,eAAe,CAAC,CAAA;AAE5H,EAAA,MAAM,eAAA,GAAkBC,WAAAA;AAAA,IACtB,OAAO,IAAA,KAAe;AACpB,MAAA,KAAA,EAAM;AACN,MAAA,oBAAA,EAAqB;AACrB,MAAA,iBAAA,CAAkB,KAAK,KAAK,CAAA;AAC5B,MAAA,SAAA,CAAU,IAAI,CAAA;AACd,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAI,CAAA;AAChC,QAAA,IAAI,IAAA,oBAAwB,IAAI,CAAA;AAAA,MAClC,CAAA,SAAE;AACA,QAAA,SAAA,CAAU,KAAK,CAAA;AAAA,MACjB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,oBAAA,EAAsB,iBAAA,EAAmB,QAAA,EAAU,WAAW,iBAAiB;AAAA,GACzF;AAEA,EAAA,MAAM,UAAA,GAAaA,WAAAA;AAAA,IACjB,CAAC,IAAA,KAAiB;AAChB,MAAA,KAAA,EAAM;AACN,MAAA,KAAK,SAAS,IAAI,CAAA;AAAA,IACpB,CAAA;AAAA,IACA,CAAC,OAAO,QAAQ;AAAA,GAClB;AAEA,EAAA,MAAM,cAAcA,WAAAA,CAAY,MAAM,QAAQ,KAAK,CAAA,EAAG,EAAE,CAAA;AAExD,EAAA,MAAM,WAAA,GAAcA,YAAY,MAAM;AACpC,IAAA,SAAA,EAAU;AACV,IAAA,eAAA,CAAgB,KAAA,GAAQ,KAAA;AACxB,IAAA,UAAA,CAAW,EAAE,CAAA;AAAA,EACf,CAAA,EAAG,CAAC,SAAA,EAAW,UAAA,EAAY,eAAe,CAAC,CAAA;AAE3C,EAAA,MAAM,gBAAA,GAAmBA,YAAY,MAAM;AACzC,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,MAAA,CAAO,SAAS,IAAA,GAAO,QAAA;AAAA,IACzB;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,EAAA,MAAM,SAAA,GAAY;AAAA,IAChB,CAAC,aAAuB,GAAG;AAAA,GAC7B;AAEA,EAAA,uBACEkC,IAAAA,CAAC,KAAA,EAAA,EAAI,WAAW,YAAA,CAAO,IAAA,EAAM,OAAO,SAAA,EACjC,QAAA,EAAA;AAAA,IAAA,CAAC,IAAA,mBACAC,GAAAA,CAAC,cAAA,EAAA,EAAe,QAAA,EAAoB,OAAA,EAAS,MAAM,OAAA,CAAQ,IAAI,CAAA,EAAG,UAAA,EAAwB,CAAA,GACxF,IAAA;AAAA,IACH,uBACCD,IAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,CAAA,EAAG,YAAA,CAAO,MAAM,CAAA,CAAA,EAAI,aAAa,aAAA,GAAgB,YAAA,CAAO,YAAA,GAAe,YAAA,CAAO,aAAa,CAAA,CAAA;AAAA,QAEtG,QAAA,EAAA;AAAA,0BAAAC,GAAAA;AAAA,YAAC,UAAA;AAAA,YAAA;AAAA,cACC,SAAA;AAAA,cACA,WAAA;AAAA,cACA,eAAe,aAAA,IAAiB,UAAA;AAAA,cAChC,OAAA,EAAS,WAAA;AAAA,cACT,OAAA,EAAS,aAAa,WAAA,GAAc;AAAA;AAAA,WACtC;AAAA,0BACAA,GAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,UAAA;AAAA,cACA,OAAA,EAAS,WAAA;AAAA,cACT,UAAU,SAAA,CAAU,QAAA;AAAA,cACpB,UAAU,SAAA,CAAU,QAAA;AAAA,cACpB,SAAA;AAAA,cACA,QAAA;AAAA,cACA,OAAO,SAAA,CAAU,KAAA;AAAA,cACjB,WAAA,EAAa,eAAA;AAAA,cACb,YAAA,EAAc;AAAA;AAAA,WAChB;AAAA,UACC,6BACCA,GAAAA;AAAA,YAAC,SAAA;AAAA,YAAA;AAAA,cACC,UAAU,SAAA,CAAU,QAAA;AAAA,cACpB,MAAA,EAAQ,UAAA;AAAA,cACR,WAAA,EAAY;AAAA;AAAA,WACd,GACE;AAAA;AAAA;AAAA,KACN,GACE;AAAA,GAAA,EACN,CAAA;AAEJ","file":"index.mjs","sourcesContent":["import type { Message, WACSession } from \"../types\";\n\nexport const SESSION_KEY = \"wac_session\";\nexport const DEFAULT_SESSION_TTL_SECONDS = 1800;\nexport const MAX_HISTORY = 100;\n\n// Fallback localStorage keys checked in order when wac_session is empty.\n// Host apps that already store a JWT under one of these keys get auth for free.\nconst FALLBACK_TOKEN_KEYS = [\"token\", \"access_token\", \"auth_token\", \"jwt\"];\n\nconst isBrowser = (): boolean =>\n typeof window !== \"undefined\" && typeof window.localStorage !== \"undefined\";\n\nfunction base64UrlDecode(input: string): string {\n const base64 = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = base64 + \"===\".slice((base64.length + 3) % 4);\n return atob(padded);\n}\n\nfunction decodeJwt(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3 || !parts[1]) return null;\n return JSON.parse(base64UrlDecode(parts[1])) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction jwtExpiresAt(token: string): number | null {\n const payload = decodeJwt(token);\n const exp = payload?.[\"exp\"];\n if (typeof exp !== \"number\") return null;\n return exp * 1000;\n}\n\nfunction jwtUserId(token: string): string {\n const payload = decodeJwt(token);\n if (!payload) return \"\";\n const id = payload[\"id\"] ?? payload[\"sub\"] ?? payload[\"user_id\"];\n return id != null ? String(id) : \"\";\n}\n\nfunction readFallbackToken(): { token: string; key: string } | null {\n if (!isBrowser()) return null;\n for (const key of FALLBACK_TOKEN_KEYS) {\n const value = window.localStorage.getItem(key);\n if (value && value.length > 20) return { token: value, key };\n }\n return null;\n}\n\nconst generateId = (prefix: string): string => {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return `${prefix}_${crypto.randomUUID()}`;\n }\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n};\n\nexport function newSessionId(): string {\n return generateId(\"ses\");\n}\n\nexport function newMessageId(): string {\n return generateId(\"msg\");\n}\n\nexport function readSession(): WACSession | null {\n if (!isBrowser()) return null;\n try {\n const raw = window.localStorage.getItem(SESSION_KEY);\n if (raw) {\n const session = JSON.parse(raw) as WACSession;\n const valid =\n session &&\n typeof session.token === \"string\" &&\n session.token.length > 0 &&\n typeof session.expiresAt === \"number\" &&\n Date.now() < session.expiresAt;\n if (valid) {\n const fallback = readFallbackToken();\n // If this session was adopted from a fallback key but that key is now gone,\n // the host app has logged out — drop the cached session.\n if (!fallback && session.bootSource === \"fallback\") {\n clearSession();\n return null;\n }\n // If the host app's raw token has rotated (re-login), prefer that one.\n if (fallback && fallback.token !== session.token) {\n return adoptFallbackToken(fallback.token, session);\n }\n return session;\n }\n // Stale wac_session — drop it and try fallback below.\n clearSession();\n }\n } catch {\n clearSession();\n }\n\n const fallback = readFallbackToken();\n if (!fallback) return null;\n return adoptFallbackToken(fallback.token, null);\n}\n\nfunction adoptFallbackToken(token: string, prior: WACSession | null): WACSession | null {\n const jwtExp = jwtExpiresAt(token);\n const now = Date.now();\n const expiresAt = jwtExp ?? now + DEFAULT_SESSION_TTL_SECONDS * 1000;\n if (now >= expiresAt) return null;\n const session: WACSession = {\n token,\n userId: jwtUserId(token) || prior?.userId || \"\",\n sessionId: prior?.sessionId ?? newSessionId(),\n expiresAt,\n lastActive: now,\n history: prior?.history ?? [],\n bootSource: \"fallback\",\n };\n if (isBrowser()) {\n try {\n window.localStorage.setItem(SESSION_KEY, JSON.stringify(session));\n } catch {\n /* ignore quota */\n }\n }\n return session;\n}\n\nexport function writeSession(\n data: Partial<WACSession>,\n ttlSeconds: number = DEFAULT_SESSION_TTL_SECONDS,\n): WACSession | null {\n if (!isBrowser()) return null;\n const now = Date.now();\n const existing = readSession();\n const base: WACSession =\n existing ??\n {\n token: \"\",\n userId: \"\",\n sessionId: newSessionId(),\n expiresAt: now + ttlSeconds * 1000,\n lastActive: now,\n history: [],\n };\n\n const merged: WACSession = {\n ...base,\n ...data,\n sessionId: data.sessionId ?? base.sessionId ?? newSessionId(),\n lastActive: now,\n expiresAt: now + ttlSeconds * 1000,\n history: trimHistory(data.history ?? base.history),\n };\n\n try {\n window.localStorage.setItem(SESSION_KEY, JSON.stringify(merged));\n return merged;\n } catch {\n return null;\n }\n}\n\nexport function clearSession(): void {\n if (!isBrowser()) return;\n try {\n window.localStorage.removeItem(SESSION_KEY);\n } catch {\n /* ignore */\n }\n}\n\nexport function trimHistory(history: Message[] | undefined): Message[] {\n if (!Array.isArray(history)) return [];\n if (history.length <= MAX_HISTORY) return history;\n return history.slice(history.length - MAX_HISTORY);\n}\n\nexport function touchSession(\n ttlSeconds: number = DEFAULT_SESSION_TTL_SECONDS,\n): WACSession | null {\n const current = readSession();\n if (!current) return null;\n return writeSession({}, ttlSeconds);\n}\n","import type { BotResponse, Chip, Message, WACUser } from \"../types\";\nimport { clearSession, newMessageId, readSession } from \"../utils/session\";\n\nconst DEFAULT_TIMEOUT_MS = 20000;\nconst DEFAULT_RETRIES = 2;\nconst RETRY_BASE_DELAY_MS = 400;\n\nexport class ApiError extends Error {\n status: number;\n requestId: string;\n constructor(message: string, status: number, requestId: string) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.requestId = requestId;\n }\n}\n\nexport class AuthExpiredError extends ApiError {\n constructor(requestId: string) {\n super(\"Session expired or invalid\", 401, requestId);\n this.name = \"AuthExpiredError\";\n }\n}\n\ninterface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n body?: unknown;\n signal?: AbortSignal;\n timeoutMs?: number;\n retries?: number;\n auth?: boolean;\n}\n\nconst generateRequestId = (): string => {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n};\n\nconst sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\nconst joinUrl = (base: string, path: string): string => {\n const trimmedBase = base.replace(/\\/+$/, \"\");\n const trimmedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return `${trimmedBase}${trimmedPath}`;\n};\n\nasync function request<T>(\n apiBase: string,\n path: string,\n opts: RequestOptions = {},\n): Promise<T> {\n const {\n method = \"GET\",\n body,\n signal,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n retries = DEFAULT_RETRIES,\n auth = true,\n } = opts;\n\n const url = /^https?:\\/\\//i.test(path) ? path : joinUrl(apiBase, path);\n const requestId = generateRequestId();\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n const onUserAbort = () => controller.abort();\n if (signal) {\n if (signal.aborted) {\n clearTimeout(timeoutId);\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n signal.addEventListener(\"abort\", onUserAbort, { once: true });\n }\n\n try {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-Request-Id\": requestId,\n };\n if (auth) {\n const session = readSession();\n if (session?.token) headers[\"Authorization\"] = `Bearer ${session.token}`;\n }\n\n const res = await fetch(url, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller.signal,\n credentials: \"same-origin\",\n });\n\n if (res.status === 401 || res.status === 403) {\n clearSession();\n throw new AuthExpiredError(requestId);\n }\n\n if (res.status >= 500 && attempt < retries) {\n lastError = new ApiError(`Server error ${res.status}`, res.status, requestId);\n throw lastError;\n }\n\n if (!res.ok) {\n let detail = res.statusText;\n try {\n const errBody = (await res.json()) as { message?: string; detail?: string };\n detail = errBody.message ?? errBody.detail ?? detail;\n } catch {\n /* ignore body parse */\n }\n throw new ApiError(detail || `HTTP ${res.status}`, res.status, requestId);\n }\n\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n } catch (err) {\n const error = err as Error;\n const isAbort = error.name === \"AbortError\";\n const isAuth = error instanceof AuthExpiredError;\n const isRetryable =\n !isAuth &&\n !signal?.aborted &&\n (isAbort || error instanceof TypeError || (error as ApiError).status >= 500);\n\n if (isAuth || !isRetryable || attempt === retries) {\n throw error;\n }\n lastError = error;\n await sleep(RETRY_BASE_DELAY_MS * Math.pow(2, attempt));\n } finally {\n clearTimeout(timeoutId);\n if (signal) signal.removeEventListener(\"abort\", onUserAbort);\n }\n }\n\n throw lastError ?? new Error(\"Request failed\");\n}\n\nexport async function checkAuth(\n apiBase: string,\n authCheck: string,\n signal?: AbortSignal,\n): Promise<WACUser> {\n return request<WACUser>(apiBase, authCheck, {\n method: \"GET\",\n signal,\n retries: 1,\n });\n}\n\nexport async function sendChip(\n apiBase: string,\n chip: Chip,\n sessionId: string,\n signal?: AbortSignal,\n): Promise<BotResponse> {\n const path = chip.apiPath ?? `/chip/${chip.id}`;\n return request<BotResponse>(apiBase, path, {\n method: \"POST\",\n body: { chipId: chip.id, sessionId },\n signal,\n });\n}\n\nexport async function sendMessage(\n apiBase: string,\n message: string,\n sessionId: string,\n context: Message[],\n signal?: AbortSignal,\n): Promise<BotResponse> {\n return request<BotResponse>(apiBase, \"/message\", {\n method: \"POST\",\n body: { message, sessionId, context },\n signal,\n });\n}\n\nexport async function logout(apiBase: string, signal?: AbortSignal): Promise<void> {\n try {\n await request<void>(apiBase, \"/auth/logout\", {\n method: \"POST\",\n signal,\n retries: 0,\n });\n } finally {\n clearSession();\n }\n}\n\nexport { generateRequestId };\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { AuthExpiredError, checkAuth } from \"../api/chatApi\";\nimport type { WACUser } from \"../types\";\nimport { clearSession, readSession } from \"../utils/session\";\n\ninterface UseAuthOptions {\n apiBase: string;\n authCheck: string;\n enabled?: boolean;\n onError?: (err: Error) => void;\n}\n\ninterface UseAuthReturn {\n isLoggedIn: boolean;\n user: WACUser | null;\n loading: boolean;\n error: Error | null;\n refresh: () => void;\n}\n\nexport function useAuth(opts: UseAuthOptions): UseAuthReturn {\n const { apiBase, authCheck, enabled = true, onError } = opts;\n const [user, setUser] = useState<WACUser | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n const [error, setError] = useState<Error | null>(null);\n const [tick, setTick] = useState(0);\n const onErrorRef = useRef(onError);\n\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n return;\n }\n\n const session = readSession();\n if (!session?.token) {\n setUser(null);\n setLoading(false);\n return;\n }\n\n const controller = new AbortController();\n setLoading(true);\n setError(null);\n\n checkAuth(apiBase, authCheck, controller.signal)\n .then((u) => {\n setUser(u);\n setError(null);\n })\n .catch((err: Error) => {\n if (err.name === \"AbortError\") return;\n if (err instanceof AuthExpiredError) {\n clearSession();\n setUser(null);\n } else {\n setError(err);\n onErrorRef.current?.(err);\n }\n })\n .finally(() => {\n if (!controller.signal.aborted) setLoading(false);\n });\n\n return () => controller.abort();\n }, [apiBase, authCheck, enabled, tick]);\n\n const refresh = useCallback(() => setTick((t) => t + 1), []);\n\n return {\n isLoggedIn: user !== null,\n user,\n loading,\n error,\n refresh,\n };\n}\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\nimport { AuthExpiredError, sendMessage as apiSendMessage } from \"../api/chatApi\";\nimport type { BotResponse, ChatState, Message } from \"../types\";\nimport { MAX_HISTORY, newMessageId } from \"../utils/session\";\n\ntype ChatAction =\n | { type: \"ADD_MESSAGE\"; payload: Message }\n | { type: \"SET_TYPING\"; payload: boolean }\n | { type: \"SET_STATUS\"; payload: ChatState[\"status\"] }\n | { type: \"SET_ERROR\"; payload: string | null }\n | { type: \"DEACTIVATE_CHIPS\"; payload: { exceptId?: string } }\n | { type: \"LOAD_HISTORY\"; payload: Message[] }\n | { type: \"CLEAR\" };\n\nconst initialState: ChatState = {\n messages: [],\n isTyping: false,\n status: \"idle\",\n error: null,\n};\n\nfunction reducer(state: ChatState, action: ChatAction): ChatState {\n switch (action.type) {\n case \"ADD_MESSAGE\": {\n const next = [...state.messages, action.payload];\n const trimmed = next.length > MAX_HISTORY ? next.slice(next.length - MAX_HISTORY) : next;\n return { ...state, messages: trimmed };\n }\n case \"SET_TYPING\":\n return { ...state, isTyping: action.payload };\n case \"SET_STATUS\":\n return { ...state, status: action.payload };\n case \"SET_ERROR\":\n return { ...state, error: action.payload };\n case \"DEACTIVATE_CHIPS\":\n return {\n ...state,\n messages: state.messages.map((m) =>\n m.id === action.payload.exceptId ? m : { ...m, chipsActive: false },\n ),\n };\n case \"LOAD_HISTORY\":\n return { ...state, messages: action.payload };\n case \"CLEAR\":\n return initialState;\n default:\n return state;\n }\n}\n\ninterface UseChatOptions {\n apiBase: string;\n sessionId: string | null;\n initialMessages?: Message[];\n onHistoryChange?: (history: Message[]) => void;\n onError?: (err: Error) => void;\n onAuthExpired?: () => void;\n}\n\ninterface UseChatReturn {\n state: ChatState;\n sendText: (text: string) => Promise<void>;\n appendBotResponse: (resp: BotResponse) => void;\n appendUserMessage: (content: string) => Message;\n deactivatePriorChips: (keepId?: string) => void;\n setTyping: (value: boolean) => void;\n clear: () => void;\n loadHistory: (history: Message[]) => void;\n}\n\nexport function useChat(opts: UseChatOptions): UseChatReturn {\n const { apiBase, sessionId, initialMessages, onHistoryChange, onError, onAuthExpired } = opts;\n const [state, dispatch] = useReducer(reducer, initialState);\n const abortRef = useRef<AbortController | null>(null);\n const onHistoryChangeRef = useRef(onHistoryChange);\n const onErrorRef = useRef(onError);\n const onAuthExpiredRef = useRef(onAuthExpired);\n\n useEffect(() => {\n onHistoryChangeRef.current = onHistoryChange;\n onErrorRef.current = onError;\n onAuthExpiredRef.current = onAuthExpired;\n }, [onHistoryChange, onError, onAuthExpired]);\n\n useEffect(() => {\n if (initialMessages && initialMessages.length > 0) {\n dispatch({ type: \"LOAD_HISTORY\", payload: initialMessages });\n }\n }, []);\n\n useEffect(() => {\n onHistoryChangeRef.current?.(state.messages);\n }, [state.messages]);\n\n useEffect(() => {\n return () => {\n abortRef.current?.abort();\n };\n }, []);\n\n const appendUserMessage = useCallback((content: string): Message => {\n const msg: Message = {\n id: newMessageId(),\n role: \"user\",\n content,\n timestamp: Date.now(),\n };\n dispatch({ type: \"ADD_MESSAGE\", payload: msg });\n return msg;\n }, []);\n\n const appendBotResponse = useCallback((resp: BotResponse) => {\n const botMsg: Message = {\n id: newMessageId(),\n role: \"bot\",\n content: resp.message,\n chips: resp.chips ?? [],\n chipsActive: (resp.chips ?? []).length > 0,\n timestamp: Date.now(),\n };\n dispatch({ type: \"ADD_MESSAGE\", payload: botMsg });\n }, []);\n\n const deactivatePriorChips = useCallback((keepId?: string) => {\n dispatch({ type: \"DEACTIVATE_CHIPS\", payload: { exceptId: keepId } });\n }, []);\n\n const sendText = useCallback(\n async (text: string) => {\n const trimmed = text.trim();\n if (!trimmed || !sessionId) return;\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n deactivatePriorChips();\n appendUserMessage(trimmed);\n dispatch({ type: \"SET_TYPING\", payload: true });\n dispatch({ type: \"SET_STATUS\", payload: \"sending\" });\n dispatch({ type: \"SET_ERROR\", payload: null });\n\n try {\n const context = state.messages.slice(-20);\n const resp = await apiSendMessage(apiBase, trimmed, sessionId, context, controller.signal);\n appendBotResponse(resp);\n dispatch({ type: \"SET_STATUS\", payload: \"idle\" });\n } catch (err) {\n const error = err as Error;\n if (error.name === \"AbortError\") return;\n if (error instanceof AuthExpiredError) {\n onAuthExpiredRef.current?.();\n } else {\n onErrorRef.current?.(error);\n dispatch({ type: \"SET_ERROR\", payload: error.message });\n }\n dispatch({ type: \"SET_STATUS\", payload: \"error\" });\n } finally {\n dispatch({ type: \"SET_TYPING\", payload: false });\n }\n },\n [apiBase, sessionId, state.messages, appendUserMessage, appendBotResponse, deactivatePriorChips],\n );\n\n const clear = useCallback(() => {\n abortRef.current?.abort();\n dispatch({ type: \"CLEAR\" });\n }, []);\n\n const loadHistory = useCallback((history: Message[]) => {\n dispatch({ type: \"LOAD_HISTORY\", payload: history });\n }, []);\n\n /** Toggle the typing dots externally — used by WealthChat around chip clicks. */\n const setTyping = useCallback((value: boolean) => {\n dispatch({ type: \"SET_TYPING\", payload: value });\n }, []);\n\n return {\n state,\n sendText,\n appendBotResponse,\n appendUserMessage,\n deactivatePriorChips,\n clear,\n loadHistory,\n setTyping,\n };\n}\n","import { useCallback, useEffect, useRef } from \"react\";\nimport { AuthExpiredError, sendChip } from \"../api/chatApi\";\nimport type { BotResponse, Chip } from \"../types\";\n\ninterface UseChipOptions {\n apiBase: string;\n sessionId: string | null;\n onAuthExpired?: () => void;\n onError?: (err: Error) => void;\n}\n\ninterface UseChipReturn {\n callChip: (chip: Chip) => Promise<BotResponse | null>;\n}\n\nexport function useChip(opts: UseChipOptions): UseChipReturn {\n const { apiBase, sessionId, onAuthExpired, onError } = opts;\n const abortRef = useRef<AbortController | null>(null);\n const onAuthExpiredRef = useRef(onAuthExpired);\n const onErrorRef = useRef(onError);\n\n useEffect(() => {\n onAuthExpiredRef.current = onAuthExpired;\n onErrorRef.current = onError;\n }, [onAuthExpired, onError]);\n\n useEffect(() => {\n return () => abortRef.current?.abort();\n }, []);\n\n const callChip = useCallback(\n async (chip: Chip): Promise<BotResponse | null> => {\n if (!sessionId) return null;\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n return await sendChip(apiBase, chip, sessionId, controller.signal);\n } catch (err) {\n const error = err as Error;\n if (error.name === \"AbortError\") return null;\n if (error instanceof AuthExpiredError) {\n onAuthExpiredRef.current?.();\n } else {\n onErrorRef.current?.(error);\n }\n return null;\n }\n },\n [apiBase, sessionId],\n );\n\n return { callChip };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { Message, WACSession } from \"../types\";\nimport {\n DEFAULT_SESSION_TTL_SECONDS,\n clearSession,\n newSessionId,\n readSession,\n touchSession,\n writeSession,\n} from \"../utils/session\";\n\ninterface UseSessionOptions {\n ttlSeconds?: number;\n onExpire?: () => void;\n}\n\ninterface UseSessionReturn {\n session: WACSession | null;\n remainingMs: number;\n setToken: (token: string, userId: string) => void;\n setHistory: (history: Message[]) => void;\n touch: () => void;\n clear: () => void;\n}\n\nexport function useSession(opts: UseSessionOptions = {}): UseSessionReturn {\n const { ttlSeconds = DEFAULT_SESSION_TTL_SECONDS, onExpire } = opts;\n const [session, setSession] = useState<WACSession | null>(null);\n const [remainingMs, setRemainingMs] = useState(0);\n const expiredRef = useRef(false);\n const onExpireRef = useRef(onExpire);\n\n useEffect(() => {\n onExpireRef.current = onExpire;\n }, [onExpire]);\n\n useEffect(() => {\n const current = readSession();\n setSession(current);\n setRemainingMs(current ? current.expiresAt - Date.now() : 0);\n }, []);\n\n useEffect(() => {\n if (!session) return;\n const interval = window.setInterval(() => {\n const current = readSession();\n if (!current) {\n if (!expiredRef.current) {\n expiredRef.current = true;\n onExpireRef.current?.();\n }\n setSession(null);\n setRemainingMs(0);\n return;\n }\n setRemainingMs(current.expiresAt - Date.now());\n }, 15000);\n return () => window.clearInterval(interval);\n }, [session]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const onStorage = (e: StorageEvent) => {\n if (e.key && e.key !== \"wac_session\") return;\n const current = readSession();\n setSession(current);\n setRemainingMs(current ? current.expiresAt - Date.now() : 0);\n };\n window.addEventListener(\"storage\", onStorage);\n return () => window.removeEventListener(\"storage\", onStorage);\n }, []);\n\n const setToken = useCallback(\n (token: string, userId: string) => {\n expiredRef.current = false;\n const updated = writeSession(\n {\n token,\n userId,\n sessionId: readSession()?.sessionId ?? newSessionId(),\n history: readSession()?.history ?? [],\n },\n ttlSeconds,\n );\n setSession(updated);\n setRemainingMs(updated ? updated.expiresAt - Date.now() : 0);\n },\n [ttlSeconds],\n );\n\n const setHistory = useCallback(\n (history: Message[]) => {\n const updated = writeSession({ history }, ttlSeconds);\n if (updated) {\n setSession(updated);\n setRemainingMs(updated.expiresAt - Date.now());\n }\n },\n [ttlSeconds],\n );\n\n const touch = useCallback(() => {\n const updated = touchSession(ttlSeconds);\n if (updated) {\n setSession(updated);\n setRemainingMs(updated.expiresAt - Date.now());\n }\n }, [ttlSeconds]);\n\n const clear = useCallback(() => {\n clearSession();\n setSession(null);\n setRemainingMs(0);\n expiredRef.current = true;\n }, []);\n\n return { session, remainingMs, setToken, setHistory, touch, clear };\n}\n",".root {\n --wac-brand: #1a2d5a;\n --wac-brand-contrast: #ffffff;\n --wac-bg: #ffffff;\n --wac-fg: #1f2937;\n --wac-muted: #6b7280;\n --wac-border: #e5e7eb;\n --wac-bot-bg: #f3f4f6;\n --wac-user-bg: var(--wac-brand);\n --wac-chip-bg: #eef2ff;\n --wac-chip-fg: #1e293b;\n --wac-chip-active-bg: var(--wac-brand);\n --wac-chip-active-fg: #ffffff;\n --wac-radius: 14px;\n --wac-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n color: var(--wac-fg);\n box-sizing: border-box;\n}\n\n.root *,\n.root *::before,\n.root *::after {\n box-sizing: inherit;\n}\n\n.floatingButton {\n position: fixed;\n bottom: 24px;\n z-index: 2147483646;\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background: var(--wac-brand);\n color: var(--wac-brand-contrast);\n border: none;\n cursor: pointer;\n box-shadow: var(--wac-shadow);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 26px;\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n}\n\n.floatingButton:hover {\n transform: translateY(-2px);\n box-shadow: 0 16px 48px rgba(0, 0, 0, 0.22);\n}\n\n.positionRight {\n right: 24px;\n}\n\n.positionLeft {\n left: 24px;\n}\n\n.widget {\n position: fixed;\n bottom: 24px;\n z-index: 2147483647;\n width: 420px;\n max-width: calc(100vw - 24px);\n height: min(760px, calc(100vh - 48px));\n background: var(--wac-bg);\n color: var(--wac-fg);\n border-radius: var(--wac-radius);\n box-shadow: var(--wac-shadow);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid var(--wac-border);\n}\n\n@media (max-width: 640px) {\n .widget {\n width: calc(100vw - 12px);\n height: calc(100vh - 24px);\n bottom: 12px;\n }\n .positionRight,\n .positionLeft {\n right: 6px;\n left: auto;\n }\n .floatingButton {\n width: 56px;\n height: 56px;\n bottom: 16px;\n }\n}\n\n.header {\n background: var(--wac-brand);\n color: var(--wac-brand-contrast);\n padding: 14px 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.headerTitle {\n font-weight: 600;\n font-size: 15px;\n line-height: 1.2;\n}\n\n.headerMeta {\n font-size: 11px;\n opacity: 0.85;\n margin-top: 2px;\n}\n\n.headerActions {\n display: flex;\n gap: 4px;\n align-items: center;\n}\n\n.iconButton {\n background: transparent;\n color: inherit;\n border: none;\n cursor: pointer;\n font-size: 18px;\n padding: 4px 8px;\n border-radius: 6px;\n line-height: 1;\n}\n\n.iconButton:hover {\n background: rgba(255, 255, 255, 0.15);\n}\n\n.body {\n flex: 1;\n overflow-y: auto;\n padding: 14px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background: #fafafa;\n scroll-behavior: smooth;\n}\n\n.body::-webkit-scrollbar {\n width: 6px;\n}\n\n.body::-webkit-scrollbar-thumb {\n background: #d1d5db;\n border-radius: 3px;\n}\n\n.bubble {\n max-width: 80%;\n padding: 10px 14px;\n border-radius: 12px;\n font-size: 14px;\n line-height: 1.45;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n\n/* markdown-it output inside a bot bubble.\n markdown-it (breaks: true) emits a <br> for every \\n in the source,\n so we MUST NOT also apply white-space: pre-wrap here — otherwise every\n newline gets rendered twice (once via <br>, once via the literal \\n in\n CSS) and the bubble looks double-spaced. */\n.markdown {\n white-space: normal;\n}\n.markdown p {\n margin: 0 0 6px 0;\n}\n.markdown p:last-child {\n margin-bottom: 0;\n}\n.markdown br {\n /* Make consecutive <br>s collapse a bit — keeps line rhythm tight. */\n line-height: 1.4;\n}\n.markdown strong {\n font-weight: 700;\n color: var(--wac-fg);\n letter-spacing: 0.2px;\n}\n.markdown em {\n font-style: italic;\n color: var(--wac-muted);\n}\n.markdown ul,\n.markdown ol {\n margin: 4px 0 6px 0;\n padding-left: 22px;\n}\n.markdown li {\n margin: 1px 0;\n}\n.markdown a {\n color: var(--wac-brand);\n text-decoration: underline;\n}\n.markdown code {\n background: rgba(0, 0, 0, 0.06);\n padding: 1px 4px;\n border-radius: 4px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n}\n.markdown h1,\n.markdown h2,\n.markdown h3 {\n margin: 6px 0 2px 0;\n font-size: 14px;\n font-weight: 700;\n}\n\n.bubbleBot {\n align-self: flex-start;\n background: var(--wac-bot-bg);\n color: var(--wac-fg);\n border-bottom-left-radius: 4px;\n}\n\n.bubbleUser {\n align-self: flex-end;\n background: var(--wac-user-bg);\n color: var(--wac-brand-contrast);\n border-bottom-right-radius: 4px;\n}\n\n.bubbleMeta {\n font-size: 10px;\n color: var(--wac-muted);\n margin-top: 4px;\n text-align: right;\n}\n\n.chipRow {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 8px;\n}\n\n.chip {\n padding: 7px 12px;\n background: var(--wac-chip-bg);\n color: var(--wac-chip-fg);\n border: 1px solid transparent;\n border-radius: 999px;\n font-size: 13px;\n cursor: pointer;\n transition: background 0.15s ease, transform 0.15s ease;\n font-family: inherit;\n}\n\n.chip:hover:not(:disabled) {\n background: #dbeafe;\n transform: translateY(-1px);\n}\n\n.chipActive {\n background: var(--wac-chip-active-bg);\n color: var(--wac-chip-active-fg);\n}\n\n.chipDisabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.typing {\n align-self: flex-start;\n display: inline-flex;\n gap: 4px;\n padding: 10px 14px;\n background: var(--wac-bot-bg);\n border-radius: 12px;\n border-bottom-left-radius: 4px;\n}\n\n.typingDot {\n width: 7px;\n height: 7px;\n background: var(--wac-muted);\n border-radius: 50%;\n animation: wacBlink 1.2s infinite ease-in-out both;\n}\n\n.typingDot:nth-child(2) {\n animation-delay: 0.18s;\n}\n.typingDot:nth-child(3) {\n animation-delay: 0.36s;\n}\n\n@keyframes wacBlink {\n 0%, 80%, 100% { opacity: 0.3; transform: scale(0.85); }\n 40% { opacity: 1; transform: scale(1); }\n}\n\n.input {\n display: flex;\n gap: 8px;\n padding: 10px 12px;\n border-top: 1px solid var(--wac-border);\n background: var(--wac-bg);\n}\n\n.inputBox {\n flex: 1;\n padding: 10px 14px;\n border: 1px solid var(--wac-border);\n border-radius: 999px;\n font-size: 14px;\n outline: none;\n font-family: inherit;\n background: #f9fafb;\n color: var(--wac-fg);\n}\n\n.inputBox:focus {\n border-color: var(--wac-brand);\n background: var(--wac-bg);\n}\n\n.inputBox:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.sendButton {\n padding: 0 16px;\n background: var(--wac-brand);\n color: var(--wac-brand-contrast);\n border: none;\n border-radius: 999px;\n font-weight: 600;\n cursor: pointer;\n font-size: 14px;\n font-family: inherit;\n}\n\n.sendButton:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.authGate {\n margin: auto;\n text-align: center;\n padding: 32px 24px;\n max-width: 280px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n align-items: center;\n}\n\n.authGateIcon {\n font-size: 36px;\n}\n\n.authGateTitle {\n font-size: 16px;\n font-weight: 600;\n}\n\n.authGateText {\n font-size: 13px;\n color: var(--wac-muted);\n line-height: 1.5;\n}\n\n.authGateButton {\n margin-top: 8px;\n padding: 10px 20px;\n background: var(--wac-brand);\n color: var(--wac-brand-contrast);\n border: none;\n border-radius: 999px;\n cursor: pointer;\n font-weight: 600;\n font-size: 14px;\n font-family: inherit;\n}\n\n.authGateDisclaimer {\n margin-top: 16px;\n padding-top: 12px;\n border-top: 1px dashed var(--wac-border);\n font-size: 11px;\n color: var(--wac-muted);\n line-height: 1.5;\n text-align: left;\n width: 100%;\n}\n\n.authGateDisclaimerTitle {\n font-weight: 700;\n letter-spacing: 0.6px;\n font-size: 10px;\n margin-bottom: 4px;\n color: var(--wac-fg);\n}\n\n.errorBanner {\n background: #fef2f2;\n border: 1px solid #fecaca;\n color: #991b1b;\n padding: 8px 12px;\n font-size: 12px;\n border-radius: 8px;\n margin: 0 14px 8px;\n}\n","import styles from \"../styles/chat.module.css\";\n\ninterface AuthGateProps {\n brandName: string;\n loginUrl: string;\n onLoginClick?: () => void;\n}\n\nexport function AuthGate({ brandName, loginUrl, onLoginClick }: AuthGateProps) {\n const handleClick = () => {\n if (onLoginClick) {\n onLoginClick();\n return;\n }\n if (typeof window !== \"undefined\") {\n window.location.href = loginUrl;\n }\n };\n\n return (\n <div className={styles.authGate}>\n <div className={styles.authGateIcon} aria-hidden=\"true\">🔒</div>\n <div className={styles.authGateTitle}>Login required</div>\n <div className={styles.authGateText}>\n To access {brandName}, please sign in first.\n </div>\n <button type=\"button\" className={styles.authGateButton} onClick={handleClick}>\n Sign in / Register →\n </button>\n <div className={styles.authGateDisclaimer}>\n <div className={styles.authGateDisclaimerTitle}>DISCLAIMER</div>\n <div>• AI-assisted analysis for educational purposes only.</div>\n <div>• Not financial advice. Markets involve risk.</div>\n <div>• Consult a SEBI-registered advisor before investing.</div>\n </div>\n </div>\n );\n}\n","import type { Chip as ChipType } from \"../types\";\nimport styles from \"../styles/chat.module.css\";\n\ninterface ChipProps {\n chip: ChipType;\n disabled?: boolean;\n active?: boolean;\n onClick: (chip: ChipType) => void;\n}\n\nexport function Chip({ chip, disabled, active, onClick }: ChipProps) {\n const classes = [\n styles.chip,\n active ? styles.chipActive : \"\",\n disabled ? styles.chipDisabled : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <button\n type=\"button\"\n className={classes}\n disabled={disabled}\n onClick={() => !disabled && onClick(chip)}\n >\n {chip.icon ? <span aria-hidden=\"true\">{chip.icon}</span> : null}\n <span>{chip.label}</span>\n </button>\n );\n}\n","import type { Chip as ChipType } from \"../types\";\nimport { Chip } from \"./Chip\";\nimport styles from \"../styles/chat.module.css\";\n\ninterface ChipRowProps {\n chips: ChipType[];\n disabled?: boolean;\n onClick: (chip: ChipType) => void;\n}\n\nexport function ChipRow({ chips, disabled, onClick }: ChipRowProps) {\n if (!chips || chips.length === 0) return null;\n return (\n <div className={styles.chipRow}>\n {chips.map((c) => (\n <Chip key={c.id} chip={c} disabled={disabled} onClick={onClick} />\n ))}\n </div>\n );\n}\n","import DOMPurify from \"isomorphic-dompurify\";\n\n/**\n * Backend (chat_widget.py + chat_template_dispatch.py) returns Telegram-HTML in\n * BotResponse.message — e.g. `<b>Heading</b><br>Body<br><i>Note</i>`.\n *\n * We sanitize with DOMPurify against a tight allow-list and feed straight into\n * the bubble via dangerouslySetInnerHTML. This matches the exact look of the\n * Telegram bot for every response template.\n *\n * Markdown-it is intentionally NOT used here anymore — the backend handles all\n * formatting via the shared Telegram templates.\n */\n\nconst ALLOWED_TAGS = [\n \"b\", \"strong\",\n \"i\", \"em\",\n \"u\", \"ins\",\n \"s\", \"strike\", \"del\",\n \"br\",\n \"p\",\n \"code\", \"pre\",\n \"a\",\n \"ul\", \"ol\", \"li\",\n \"blockquote\",\n \"span\",\n];\n\nconst ALLOWED_ATTR = [\"href\", \"target\", \"rel\", \"class\"];\n\nconst SANITIZE_CFG = {\n ALLOWED_TAGS,\n ALLOWED_ATTR,\n // Block javascript: / data: hrefs by stripping URI schemes other than http(s) / mailto\n ALLOW_UNKNOWN_PROTOCOLS: false,\n ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.\\-:]|$))/i,\n};\n\n/**\n * Convert Telegram-style markdown shortcuts to HTML so chip prompts written\n * with `*bold*` / `_italic_` in Python source render the same way as the\n * template HTML output. Also handles the *standard* markdown that\n * AI-generated text (e.g. the sector-news `ai_verdict`) commonly emits.\n *\n * ## Heading → <b>Heading</b> (line-start ATX header, 1–6 #)\n * **text** → <b>text</b> (double asterisk, standard-bold)\n * __text__ → <b>text</b> (double underscore, standard-bold)\n * *text* → <b>text</b> (single asterisk, Telegram-bold)\n * _text_ → <i>text</i> (single underscore, italic)\n *\n * Order matters: double markers are converted before single ones so a `**`\n * pair isn't half-consumed by the single-asterisk rule. The single-marker\n * rules use lookbehind/ahead so they don't touch already-converted `<b>`\n * spans or dangling markers inside HTML attribute values.\n */\nfunction inlineMarkdownToHtml(text: string): string {\n return text\n // ATX headers at the start of a line → bold (verdict gap, no real <h*> here)\n .replace(/^[ \\t]*#{1,6}[ \\t]+(.+?)[ \\t]*$/gm, \"<b>$1</b>\")\n // Standard double-marker bold first, so the single-marker rules skip them.\n .replace(/\\*\\*([^\\n<>]+?)\\*\\*/g, \"<b>$1</b>\")\n .replace(/__([^\\n<>]+?)__/g, \"<b>$1</b>\")\n // Telegram single-asterisk bold / single-underscore italic.\n .replace(/(?<!\\*)\\*([^\\n*<>]+?)\\*(?!\\*)/g, \"<b>$1</b>\")\n .replace(/(?<![_a-zA-Z0-9])_([^\\n_<>]+?)_(?![_a-zA-Z0-9])/g, \"<i>$1</i>\");\n}\n\n/**\n * Render a Telegram-HTML or markdown-flavoured string from the backend as\n * sanitized HTML suitable for dangerouslySetInnerHTML.\n *\n * Pipeline:\n * 1. `*bold*` / `_italic_` markdown shortcuts → `<b>` / `<i>`\n * 2. Single `\\n` → `<br>`, `\\n\\n` → paragraph break (visual gap)\n * 3. DOMPurify sanitization against a tight allow-list\n */\nexport function renderMarkdown(text: string): string {\n if (!text) return \"\";\n\n const inlined = inlineMarkdownToHtml(text);\n\n // Split on blank lines into paragraphs, then within each paragraph turn \\n into <br>.\n const paragraphs = inlined\n .split(/\\n{2,}/)\n .map((para) => para.replace(/\\n/g, \"<br>\"))\n .filter((p) => p.length > 0);\n\n const html = paragraphs.length > 1\n ? paragraphs.map((p) => `<p>${p}</p>`).join(\"\")\n : paragraphs[0] ?? \"\";\n\n return DOMPurify.sanitize(html, SANITIZE_CFG);\n}\n","import type { Chip as ChipType, Message } from \"../types\";\nimport { ChipRow } from \"./ChipRow\";\nimport { renderMarkdown } from \"../utils/markdown\";\nimport styles from \"../styles/chat.module.css\";\n\ninterface MessageBubbleProps {\n message: Message;\n onChipClick: (chip: ChipType) => void;\n}\n\nexport function MessageBubble({ message, onChipClick }: MessageBubbleProps) {\n const isBot = message.role === \"bot\";\n const bubbleClass = isBot\n ? `${styles.bubble} ${styles.bubbleBot}`\n : `${styles.bubble} ${styles.bubbleUser}`;\n\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\" }}>\n <div className={bubbleClass}>\n {isBot ? (\n <div\n className={styles.markdown}\n dangerouslySetInnerHTML={{ __html: renderMarkdown(message.content) }}\n />\n ) : (\n message.content\n )}\n </div>\n {isBot && message.chips && message.chips.length > 0 ? (\n <ChipRow\n chips={message.chips}\n disabled={!message.chipsActive}\n onClick={onChipClick}\n />\n ) : null}\n </div>\n );\n}\n","import styles from \"../styles/chat.module.css\";\n\nexport function TypingIndicator() {\n return (\n <div className={styles.typing} aria-live=\"polite\" aria-label=\"Assistant is typing\">\n <span className={styles.typingDot} />\n <span className={styles.typingDot} />\n <span className={styles.typingDot} />\n </div>\n );\n}\n","import { useEffect, useRef } from \"react\";\nimport type { Chip as ChipType, Message } from \"../types\";\nimport { AuthGate } from \"./AuthGate\";\nimport { MessageBubble } from \"./MessageBubble\";\nimport { TypingIndicator } from \"./TypingIndicator\";\nimport styles from \"../styles/chat.module.css\";\n\ninterface ChatBodyProps {\n isLoggedIn: boolean;\n loading: boolean;\n messages: Message[];\n isTyping: boolean;\n brandName: string;\n loginUrl: string;\n error: string | null;\n onChipClick: (chip: ChipType) => void;\n onLoginClick?: () => void;\n}\n\nexport function ChatBody({\n isLoggedIn,\n loading,\n messages,\n isTyping,\n brandName,\n loginUrl,\n error,\n onChipClick,\n onLoginClick,\n}: ChatBodyProps) {\n const endRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n endRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages.length, isTyping]);\n\n if (loading) {\n return (\n <div className={styles.body}>\n <TypingIndicator />\n </div>\n );\n }\n\n if (!isLoggedIn) {\n return (\n <div className={styles.body}>\n <AuthGate\n brandName={brandName}\n loginUrl={loginUrl}\n onLoginClick={onLoginClick}\n />\n </div>\n );\n }\n\n return (\n <div className={styles.body}>\n {messages.map((m) => (\n <MessageBubble key={m.id} message={m} onChipClick={onChipClick} />\n ))}\n {isTyping ? <TypingIndicator /> : null}\n {error ? <div className={styles.errorBanner}>{error}</div> : null}\n <div ref={endRef} />\n </div>\n );\n}\n","export function formatCountdown(remainingMs: number): string {\n if (remainingMs <= 0) return \"expired\";\n const totalSeconds = Math.floor(remainingMs / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n if (hours > 0) return `${hours}h ${minutes}m left`;\n if (minutes > 0) return `${minutes}m ${seconds}s left`;\n return `${seconds}s left`;\n}\n\nexport function formatRelativeTime(ts: number, now: number = Date.now()): string {\n const diff = Math.max(0, now - ts);\n const seconds = Math.floor(diff / 1000);\n if (seconds < 60) return \"just now\";\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n","import styles from \"../styles/chat.module.css\";\nimport { formatCountdown } from \"../utils/time\";\n\ninterface ChatHeaderProps {\n brandName: string;\n remainingMs: number;\n showCountdown: boolean;\n onClose: () => void;\n onClear?: () => void;\n}\n\nexport function ChatHeader({\n brandName,\n remainingMs,\n showCountdown,\n onClose,\n onClear,\n}: ChatHeaderProps) {\n return (\n <div className={styles.header}>\n <div>\n <div className={styles.headerTitle}>{brandName}</div>\n {showCountdown && remainingMs > 0 ? (\n <div className={styles.headerMeta}>Session: {formatCountdown(remainingMs)}</div>\n ) : null}\n </div>\n <div className={styles.headerActions}>\n {onClear ? (\n <button\n type=\"button\"\n className={styles.iconButton}\n onClick={onClear}\n aria-label=\"Clear conversation\"\n title=\"Clear conversation\"\n >\n ↺\n </button>\n ) : null}\n <button\n type=\"button\"\n className={styles.iconButton}\n onClick={onClose}\n aria-label=\"Close chat\"\n title=\"Close\"\n >\n ✕\n </button>\n </div>\n </div>\n );\n}\n","import { useState } from \"react\";\nimport styles from \"../styles/chat.module.css\";\n\ninterface ChatInputProps {\n disabled?: boolean;\n placeholder?: string;\n onSend: (text: string) => void;\n}\n\nexport function ChatInput({ disabled, placeholder, onSend }: ChatInputProps) {\n const [value, setValue] = useState(\"\");\n\n const submit = () => {\n const trimmed = value.trim();\n if (!trimmed || disabled) return;\n onSend(trimmed);\n setValue(\"\");\n };\n\n return (\n <div className={styles.input}>\n <input\n type=\"text\"\n className={styles.inputBox}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n }}\n disabled={disabled}\n placeholder={placeholder ?? \"Type a message…\"}\n aria-label=\"Message input\"\n />\n <button\n type=\"button\"\n className={styles.sendButton}\n onClick={submit}\n disabled={disabled || value.trim().length === 0}\n >\n Send\n </button>\n </div>\n );\n}\n","import type { ChatPosition } from \"../types\";\nimport styles from \"../styles/chat.module.css\";\n\ninterface FloatingButtonProps {\n position: ChatPosition;\n onClick: () => void;\n brandColor?: string;\n}\n\nexport function FloatingButton({ position, onClick, brandColor }: FloatingButtonProps) {\n const posClass = position === \"bottom-left\" ? styles.positionLeft : styles.positionRight;\n return (\n <button\n type=\"button\"\n className={`${styles.floatingButton} ${posClass}`}\n onClick={onClick}\n aria-label=\"Open chat\"\n style={brandColor ? { background: brandColor } : undefined}\n >\n 💬\n </button>\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useAuth } from \"../hooks/useAuth\";\nimport { useChat } from \"../hooks/useChat\";\nimport { useChip } from \"../hooks/useChip\";\nimport { useSession } from \"../hooks/useSession\";\nimport type { Chip, Message, WealthChatProps } from \"../types\";\nimport { DEFAULT_SESSION_TTL_SECONDS } from \"../utils/session\";\nimport { ChatBody } from \"./ChatBody\";\nimport { ChatHeader } from \"./ChatHeader\";\nimport { ChatInput } from \"./ChatInput\";\nimport { FloatingButton } from \"./FloatingButton\";\nimport styles from \"../styles/chat.module.css\";\n\nconst DEFAULT_BRAND_NAME = \"Wealth Alpha AI\";\nconst DEFAULT_BRAND_COLOR = \"#1a2d5a\";\nconst DEFAULT_AUTH_CHECK = \"/me\";\n\nconst DISCLAIMER_BLOCK =\n \"DISCLAIMER:\\n\" +\n \"• AI-assisted analysis for educational purposes only.\\n\" +\n \"• Not financial advice. Markets involve risk.\\n\" +\n \"• Consult a SEBI-registered advisor before investing.\";\n\nconst CTA_LINE =\n \"Select a quick command below — or type your query to ask our AI Research Analyst directly.\";\n\nfunction buildWelcome(name: string | undefined, plan: string | undefined): string {\n const greeting = name\n ? `Welcome back, ${name}! You have ${plan ? plan : \"Premium\"} access.`\n : `Welcome! You have ${plan ? plan : \"Premium\"} access.`;\n return `${greeting}\\n\\n${DISCLAIMER_BLOCK}\\n\\n${CTA_LINE}`;\n}\n\nexport function WealthChat(props: WealthChatProps) {\n const {\n apiBase,\n authCheck = DEFAULT_AUTH_CHECK,\n loginUrl,\n sessionTTL = DEFAULT_SESSION_TTL_SECONDS,\n welcomeMessage,\n brandName = DEFAULT_BRAND_NAME,\n brandColor = DEFAULT_BRAND_COLOR,\n position = \"bottom-right\",\n defaultOpen = false,\n showCountdown = true,\n onLogin,\n onLogout,\n onSessionExpire,\n onError,\n } = props;\n\n const [mounted, setMounted] = useState(false);\n const [open, setOpen] = useState(defaultOpen);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n const {\n session,\n remainingMs,\n setHistory,\n touch,\n clear: clearSessionState,\n } = useSession({\n ttlSeconds: sessionTTL,\n onExpire: onSessionExpire,\n });\n\n const { isLoggedIn, user, loading: authLoading } = useAuth({\n apiBase,\n authCheck,\n enabled: mounted && open,\n onError,\n });\n\n const handleAuthExpired = useCallback(() => {\n clearSessionState();\n onSessionExpire?.();\n }, [clearSessionState, onSessionExpire]);\n\n const handleHistoryChange = useCallback(\n (history: Message[]) => {\n if (!session) return;\n setHistory(history);\n },\n [session, setHistory],\n );\n\n const {\n state: chatState,\n sendText,\n appendBotResponse,\n appendUserMessage,\n deactivatePriorChips,\n setTyping,\n loadHistory,\n clear: clearChat,\n } = useChat({\n apiBase,\n sessionId: session?.sessionId ?? null,\n initialMessages: session?.history,\n onHistoryChange: handleHistoryChange,\n onAuthExpired: handleAuthExpired,\n onError,\n });\n\n const { callChip } = useChip({\n apiBase,\n sessionId: session?.sessionId ?? null,\n onAuthExpired: handleAuthExpired,\n onError,\n });\n\n const welcomeShownRef = useMemo(() => ({ shown: false }), []);\n\n useEffect(() => {\n if (!isLoggedIn || !open) return;\n if (chatState.messages.length > 0) return;\n if (welcomeShownRef.shown) return;\n welcomeShownRef.shown = true;\n const messageText =\n welcomeMessage ??\n user?.welcomeMessage ??\n buildWelcome(user?.name, user?.plan);\n appendBotResponse({\n message: messageText,\n chips: user?.rootChips ?? [],\n sessionId: session?.sessionId ?? \"\",\n endOfFlow: false,\n });\n onLogin?.();\n }, [isLoggedIn, open, chatState.messages.length, welcomeMessage, session, user, appendBotResponse, onLogin, welcomeShownRef]);\n\n const handleChipClick = useCallback(\n async (chip: Chip) => {\n touch();\n deactivatePriorChips();\n appendUserMessage(chip.label);\n setTyping(true);\n try {\n const resp = await callChip(chip);\n if (resp) appendBotResponse(resp);\n } finally {\n setTyping(false);\n }\n },\n [touch, deactivatePriorChips, appendUserMessage, callChip, setTyping, appendBotResponse],\n );\n\n const handleSend = useCallback(\n (text: string) => {\n touch();\n void sendText(text);\n },\n [touch, sendText],\n );\n\n const handleClose = useCallback(() => setOpen(false), []);\n\n const handleClear = useCallback(() => {\n clearChat();\n welcomeShownRef.shown = false;\n setHistory([]);\n }, [clearChat, setHistory, welcomeShownRef]);\n\n const handleLoginClick = useCallback(() => {\n if (typeof window !== \"undefined\") {\n window.location.href = loginUrl;\n }\n }, [loginUrl]);\n\n if (!mounted) return null;\n\n const rootStyle = {\n [\"--wac-brand\" as string]: brandColor,\n } as React.CSSProperties;\n\n return (\n <div className={styles.root} style={rootStyle}>\n {!open ? (\n <FloatingButton position={position} onClick={() => setOpen(true)} brandColor={brandColor} />\n ) : null}\n {open ? (\n <div\n className={`${styles.widget} ${position === \"bottom-left\" ? styles.positionLeft : styles.positionRight}`}\n >\n <ChatHeader\n brandName={brandName}\n remainingMs={remainingMs}\n showCountdown={showCountdown && isLoggedIn}\n onClose={handleClose}\n onClear={isLoggedIn ? handleClear : undefined}\n />\n <ChatBody\n isLoggedIn={isLoggedIn}\n loading={authLoading}\n messages={chatState.messages}\n isTyping={chatState.isTyping}\n brandName={brandName}\n loginUrl={loginUrl}\n error={chatState.error}\n onChipClick={handleChipClick}\n onLoginClick={handleLoginClick}\n />\n {isLoggedIn ? (\n <ChatInput\n disabled={chatState.isTyping}\n onSend={handleSend}\n placeholder=\"Type a message…\"\n />\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# Chat Widget Backend — Design & Frontend Integration Guide
|
|
2
|
+
|
|
3
|
+
> **Scope of this doc**: covers only the two files I authored for the chat widget:
|
|
4
|
+
> - `app/api_v1/chat_widget.py` — the widget's HTTP router (chip + multi-step + proxy)
|
|
5
|
+
> - `app/api_v1/chat_formatters.py` — converts upstream JSON into chat-bubble text
|
|
6
|
+
>
|
|
7
|
+
> The team-authored files (`chat.py` for persistence, `intent.py`, `ama.py`) are **out of scope** here.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. What problem does `chat_widget.py` solve?
|
|
12
|
+
|
|
13
|
+
The npm library (`wealth-alpha-chat`) speaks one tiny protocol:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
GET /me → who is logged in?
|
|
17
|
+
POST /chip/{chip_id} → user clicked a chip
|
|
18
|
+
POST /message → user typed text
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Each response is a `BotResponse`:
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
{
|
|
25
|
+
"message": "text to show in the bubble",
|
|
26
|
+
"chips": [{ "id": "...", "label": "...", "icon": "..." }], // next chips
|
|
27
|
+
"sessionId": "ses_...",
|
|
28
|
+
"endOfFlow": false,
|
|
29
|
+
"metadata": { "endpoint": "/api/v1/...", "inputs": { ... } }
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
But your **business endpoints** (the 20 upstream telegram-api routes) speak a completely different language — they need specific query params (`symbol`, `entry_price`, `side`, `category`, `stance`, `market_range`, …) and return structured templates (`LONG_TERM`, `SWING`, `CRYPTO_ANALYSIS`, …).
|
|
34
|
+
|
|
35
|
+
`chat_widget.py` is the **adapter** between those two worlds.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Browser (npm lib)
|
|
39
|
+
│ POST /chat-widget/chip/act_long_term {sessionId:"abc"}
|
|
40
|
+
▼
|
|
41
|
+
chat_widget.py
|
|
42
|
+
│ chip "act_long_term" → prompt user for symbol
|
|
43
|
+
▼ returns {message: "Type ticker", chips: [Back], awaiting: "symbol"}
|
|
44
|
+
Browser shows prompt
|
|
45
|
+
│
|
|
46
|
+
User types RELIANCE
|
|
47
|
+
│ POST /chat-widget/message {message:"RELIANCE", sessionId:"abc"}
|
|
48
|
+
▼
|
|
49
|
+
chat_widget.py
|
|
50
|
+
│ session.awaiting=symbol → fill collected["symbol"]="RELIANCE"
|
|
51
|
+
│ all fields collected → call upstream
|
|
52
|
+
▼ httpx GET /api/v1/stock-analysis/long-term_analysis?symbol=RELIANCE
|
|
53
|
+
upstream returns JSON {template: "LONG_TERM", price_snapshot: {...}, signal: {...}, ...}
|
|
54
|
+
│
|
|
55
|
+
▼ chat_formatters.format_payload(payload)
|
|
56
|
+
│ dispatches by `template` → format_long_term() → multi-line text with ₹, •, secti.ons
|
|
57
|
+
▼ returns BotResponse {message: "📈 LONG-TERM\n...\n*SIGNAL*\n...", chips: [Back], endOfFlow: true}
|
|
58
|
+
Browser shows formatted result in chat bubble
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 2. The three building blocks of `chat_widget.py`
|
|
64
|
+
|
|
65
|
+
### A. Chip tree (`ROOT_CHIPS`, `NAV_TREE`, `ACTIONS`, `DIRECT_CALLS`)
|
|
66
|
+
|
|
67
|
+
A chip is one of three kinds:
|
|
68
|
+
|
|
69
|
+
| Kind | Data structure | Behavior on click |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| **nav** | entry in `NAV_TREE` | Returns next-level chips. No upstream call. (e.g. `stock_analysis` → 6 analysis-type chips) |
|
|
72
|
+
| **action** | entry in `ACTIONS` | Starts a multi-step flow: prompts the user for one field at a time, then fires the upstream call. (e.g. `act_position_summary` asks ticker → entry price → LONG/SHORT) |
|
|
73
|
+
| **direct** | entry in `DIRECT_CALLS` | One click → immediately fires the upstream call with bundled params. (e.g. `macro_monetary` → `GET /market-forecast/macro?category=MONETARY`) |
|
|
74
|
+
|
|
75
|
+
Special chip IDs:
|
|
76
|
+
- `__root__` — return to the main 8-chip menu, clear pending state
|
|
77
|
+
- `__set:FIELD=VALUE` — a chip that fills the currently-awaited field (e.g. `__set:side=LONG`)
|
|
78
|
+
|
|
79
|
+
### B. Multi-step session store (`_SessionStore`)
|
|
80
|
+
|
|
81
|
+
In-memory dict keyed by `sessionId`. Each entry:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
{
|
|
85
|
+
"action": "act_position_summary",
|
|
86
|
+
"collected": {"symbol": "BPCL", "entry_price": 310.0},
|
|
87
|
+
"awaiting": "side",
|
|
88
|
+
"updated_at": 1716800000.0,
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
TTL is 30 minutes (matches the frontend session). Each `/chip` or `/message` request either:
|
|
93
|
+
- starts a new action (creates the entry with `collected={}`, `awaiting=fields[0].name`)
|
|
94
|
+
- advances an action (fills `collected[awaiting]`, sets `awaiting` to next missing field)
|
|
95
|
+
- fires the call (when all fields collected → `httpx` → format → response)
|
|
96
|
+
|
|
97
|
+
> ⚠️ **Production note**: the dict lives in process memory. For multi-instance deploys, swap `_SessionStore` for a Redis-backed implementation. The interface (`get`, `set`, `clear`) is small enough that this is a 1-hour swap.
|
|
98
|
+
|
|
99
|
+
### C. Upstream proxy (`_call_upstream`)
|
|
100
|
+
|
|
101
|
+
Async `httpx.AsyncClient` that:
|
|
102
|
+
- Re-uses the **caller's** `Authorization` header → upstream sees the same JWT
|
|
103
|
+
- Derives base URL from the incoming `Request` → works under any host/scheme without config
|
|
104
|
+
- Maps upstream HTTP errors into clean `{"_error": "..."}` payloads:
|
|
105
|
+
- `401`/`403` → "auth failed / premium required"
|
|
106
|
+
- `404` → "no data for these inputs"
|
|
107
|
+
- `5xx` → "try again shortly"
|
|
108
|
+
|
|
109
|
+
The formatter (next section) checks for `_error` first and returns it as-is, so error text shows up in the chat bubble.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 3. How `chat_formatters.py` works
|
|
114
|
+
|
|
115
|
+
One Python function per template. Each turns a JSON payload into bubble-friendly text using `•` bullets, `*headings*`, `₹` / `$`, `+/−` percentages.
|
|
116
|
+
|
|
117
|
+
### Dispatcher
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
_TEMPLATE_MAP = {
|
|
121
|
+
"LONG_TERM": format_long_term,
|
|
122
|
+
"LONG_TERM_DETAILED": format_long_term_detailed,
|
|
123
|
+
"SWING": format_swing,
|
|
124
|
+
"INTRADAY": format_intraday,
|
|
125
|
+
"POSITION_SUMMARY": format_position_summary,
|
|
126
|
+
"POSITION_DETAILED": format_position_detailed,
|
|
127
|
+
"PORTFOLIO_CONSTRUCT": format_portfolio_construct,
|
|
128
|
+
"EXISTING_PORTFOLIO": format_existing_portfolio,
|
|
129
|
+
"PORTFOLIO_REBALANCE": format_portfolio_rebalance,
|
|
130
|
+
"MACRO_EVENT_UPDATE": format_macro_event,
|
|
131
|
+
"MACRO_OVERVIEW": format_macro_event,
|
|
132
|
+
"CRYPTO_ANALYSIS": format_crypto_analysis,
|
|
133
|
+
"CRYPTO_LIST": format_crypto_list,
|
|
134
|
+
"CRYPTO_LIST_ITEM": format_crypto_list_item,
|
|
135
|
+
"SMART_MONEY_PICKS": format_smart_money_picks,
|
|
136
|
+
"POTENTIAL_STARS": format_potential_stars,
|
|
137
|
+
"EXIT_WATCH": format_exit_watch,
|
|
138
|
+
"PEER_COMPARISON": format_peer_comparison,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def format_payload(payload, fallback_prefix=""):
|
|
142
|
+
# 1. error short-circuit
|
|
143
|
+
if isinstance(payload, dict) and "_error" in payload:
|
|
144
|
+
return payload["_error"]
|
|
145
|
+
# 2. shape detection for sector-news / discovery / IPO (no template field)
|
|
146
|
+
# 3. template dispatch via _TEMPLATE_MAP
|
|
147
|
+
# 4. unknown → compact JSON dump
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Helper toolbox
|
|
151
|
+
|
|
152
|
+
Shared building blocks all formatters use:
|
|
153
|
+
- `_money_rs(value)` → `₹1,450.25`
|
|
154
|
+
- `_money_usd(value)` → `$2,369.41`
|
|
155
|
+
- `_pct(value)` → `+1.23%` / `-4.47%`
|
|
156
|
+
- `_bullet_list(items, limit=10)` → `• item1\n• item2\n• … and 5 more`
|
|
157
|
+
- `_section(title, body)` → `\n*TITLE*\nbody`
|
|
158
|
+
- `_format_signal()`, `_format_invalidation()`, `_format_business_health()`, `_format_indicators()`, `_format_peers()` — composable sub-blocks reused across LONG_TERM, SWING, POSITION_*, etc.
|
|
159
|
+
|
|
160
|
+
### Why backend-side formatting
|
|
161
|
+
|
|
162
|
+
| | Format on backend | Format on frontend |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| Iterate text without rebuilding library | ✅ | ❌ npm publish/install loop |
|
|
165
|
+
| Bundle size | Smaller (no 16 formatters in JS) | Larger |
|
|
166
|
+
| Reusable for Telegram bot | ✅ same Python code | ❌ would need a JS port |
|
|
167
|
+
| Compliance redaction (omit fields) | ✅ at the boundary | ❌ raw data already in browser |
|
|
168
|
+
| Trade-off: structured rich UI (cards) later | Need to switch to passing raw JSON | Already raw on client |
|
|
169
|
+
|
|
170
|
+
The current `BotResponse.message` is plain text. `BotResponse.metadata` includes `endpoint` and `inputs` for tracing/debugging — and if you later want structured rendering, you can move the raw payload there.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 4. The 20 upstream endpoints — exact mapping
|
|
175
|
+
|
|
176
|
+
Every chip ID lives in one of these dicts (in `chat_widget.py`):
|
|
177
|
+
|
|
178
|
+
### `ACTIONS` (multi-step — collect inputs then fire)
|
|
179
|
+
|
|
180
|
+
| Chip ID | Upstream | Fields collected |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| `act_long_term` | `GET /api/v1/stock-analysis/long-term_analysis` | `symbol` |
|
|
183
|
+
| `act_long_term_detail` | `GET /api/v1/stock-analysis/long-term_detail_analysis` | `symbol` |
|
|
184
|
+
| `act_swing_trade` | `GET /api/v1/stock-analysis/swing_trade_analysis` | `symbol` |
|
|
185
|
+
| `act_intraday` | `GET /api/v1/stock-analysis/intraday_analysis` | `symbol` |
|
|
186
|
+
| `act_position_summary` | `GET /api/v1/stock-analysis/existing_investor_position_analysis` | `symbol`, `entry_price`, `side` |
|
|
187
|
+
| `act_position_detail` | `GET /api/v1/stock-analysis/existing_investor_position_analysis_detailed` | `symbol`, `entry_price`, `side` |
|
|
188
|
+
| `act_portfolio_construct` | `POST /api/v1/portfolio-construct/construct` (body) | `capital`, `risk_port_pct` |
|
|
189
|
+
| `act_portfolio_analyze` | `GET /api/v1/portfolio-existing/analyze` | `portfolio_id` |
|
|
190
|
+
| `act_portfolio_rebalance` | `GET /api/v1/portfolio-existing/rebalance` | `portfolio_id` |
|
|
191
|
+
| `act_sector_news` | `GET /api/v1/market-forecast/sector-news` | `sector` |
|
|
192
|
+
| `act_crypto_analyze` | `GET /api/v1/crypto/analyze` | `symbol` |
|
|
193
|
+
| `act_crypto_coin` | `GET /api/v1/crypto/coin` | `symbol` |
|
|
194
|
+
| `act_peer_compare` | `GET /api/v1/peer-analysis` | `symbol` |
|
|
195
|
+
|
|
196
|
+
### `DIRECT_CALLS` (one click — params bundled)
|
|
197
|
+
|
|
198
|
+
| Chip ID | Upstream | Bundled params |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| `macro_{monetary,economic,markets,policy,global}` | `GET /market-forecast/macro` | `category=…` |
|
|
201
|
+
| `crypto_list_{investible,watchlist,avoid}` | `GET /crypto/crypto_list` | `stance=…` |
|
|
202
|
+
| `smart_money_{large,mid,small}` | `GET /tg-tradable-candidate/smart-money-picks` | `market_range=…` |
|
|
203
|
+
| `stars_{large,mid,small}` | `GET /tg-tradable-candidate/potential-stars` | `market_range=…` |
|
|
204
|
+
| `exit_{large,mid,small}` | `GET /tg-tradable-candidate/exit-watch` | `market_range=…` |
|
|
205
|
+
| `discover_{large,mid,small}` | `GET /stock-ranks/discovery/stocks` | `sector=all, market_range=…, timeframe=D` |
|
|
206
|
+
| `ipo_{large,mid,small}` | `GET /stock-ranks/discovery/ipo-stocks` | `sector=all, market_range=…` |
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 5. How the frontend library plugs into this
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<WealthChat
|
|
214
|
+
apiBase="http://localhost:8013/api/v1/chat-widget" // ← the chat router prefix
|
|
215
|
+
authCheck="/me" // → GET {apiBase}/me
|
|
216
|
+
loginUrl="/login"
|
|
217
|
+
sessionTTL={1800} // 30 min sliding
|
|
218
|
+
brandName="Wealth Alpha AI"
|
|
219
|
+
brandColor="#1a2d5a"
|
|
220
|
+
position="bottom-right"
|
|
221
|
+
/>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The library appends `/me`, `/chip/{id}`, `/message` to `apiBase`. **Do not include `/chat/` in apiBase or it doubles up** (was the recent 404 cause).
|
|
225
|
+
|
|
226
|
+
### Token discovery (no bridge required)
|
|
227
|
+
|
|
228
|
+
`readSession()` looks for a JWT in this order:
|
|
229
|
+
1. `localStorage["wac_session"].token` (full session — written by the library itself, or by the host app for custom flows)
|
|
230
|
+
2. `localStorage["token"]` (your app's normal login key)
|
|
231
|
+
3. `localStorage["access_token"]`, `localStorage["auth_token"]`, `localStorage["jwt"]` (other common keys)
|
|
232
|
+
|
|
233
|
+
If a raw JWT is found, the library decodes its `exp` and `id` claims, builds a session on the fly, and persists it as `wac_session` for subsequent reads. On logout (the fallback key is removed), the library clears `wac_session` automatically — no host-side sync code needed.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 6. Three ways to improve / extend on the library side
|
|
238
|
+
|
|
239
|
+
Pick one based on how much surface area you want to give the host app. Each is independent and can be done without touching the backend.
|
|
240
|
+
|
|
241
|
+
### Option A — Render structured cards instead of plain text (highest visual impact)
|
|
242
|
+
|
|
243
|
+
Today the bubble shows `BotResponse.message` as `pre-wrap` text. Cards would render:
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
┌─ RELIANCE — Reliance Industries Ltd ────────┐
|
|
247
|
+
│ Energy · As of 27 May 2026 │
|
|
248
|
+
│ Price ₹1,450.25 +1.23% │
|
|
249
|
+
│ 52W high ₹1,600 (-9.36%) low ₹1,100 (+32%) │
|
|
250
|
+
│ ─── SIGNAL ───────────────────────────── │
|
|
251
|
+
│ [BUY] Long · Entry ₹1,455 · Stop ₹1,390 │
|
|
252
|
+
│ T1 ₹1,550 (+6.5%) R/R 1:2.1 │
|
|
253
|
+
└─────────────────────────────────────────────┘
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**What to change:**
|
|
257
|
+
- Backend: stop formatting in `chat_formatters.py` — put the raw payload in `BotResponse.metadata.payload`
|
|
258
|
+
- Library: add a `<TemplateCard template={metadata.template} data={metadata.payload} />` component, with sub-components per template (`<LongTermCard>`, `<SwingCard>`, `<PositionCard>`, …)
|
|
259
|
+
- Trade-off: ~16 React components to write + maintain. Library bundle ~2× larger. But the UI is dramatically better.
|
|
260
|
+
|
|
261
|
+
### Option B — Streaming token-by-token responses (best perceived speed)
|
|
262
|
+
|
|
263
|
+
When you add the AI Research Analyst (LLM-powered free-text), responses can take 5–10 s. Streaming makes them feel instant.
|
|
264
|
+
|
|
265
|
+
**What to change:**
|
|
266
|
+
- Backend: add `/chat-widget/stream` SSE endpoint that yields chunks
|
|
267
|
+
- Library: open an `EventSource` instead of `fetch`, append tokens to the active bubble as they arrive
|
|
268
|
+
- Trade-off: ~150 lines of new code; requires CORS to allow `text/event-stream`; harder to retry on disconnect
|
|
269
|
+
|
|
270
|
+
### Option C — Pluggable token / chat-tree config (most reusable across projects)
|
|
271
|
+
|
|
272
|
+
Make the library generic enough to drop into any host app or any backend.
|
|
273
|
+
|
|
274
|
+
**What to change:**
|
|
275
|
+
- Add props: `getToken?: () => string | null`, `tokenStorageKey?: string`, `welcomeBuilder?: (user) => string`
|
|
276
|
+
- Add prop: `chipTree?: ChipTree` — let the host app override the root chips and labels without touching the npm package
|
|
277
|
+
- Trade-off: more API surface; documentation effort
|
|
278
|
+
|
|
279
|
+
### Honorable mention — Persist chat history server-side
|
|
280
|
+
|
|
281
|
+
The team's new `chat.py` already exposes `/api/v1/chat/conversation` and `/api/v1/chat/session/{id}`. Have the library `POST /chat/conversation` after each turn so the conversation is recoverable across devices, not just `localStorage`. ~50 lines, big UX win.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 7. Where each piece lives — file map
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
WealthAlpha-Backend/
|
|
289
|
+
app/api_v1/
|
|
290
|
+
chat_widget.py ← chip router, multi-step state, upstream proxy
|
|
291
|
+
chat_formatters.py ← 18 templates → text
|
|
292
|
+
router.py ← includes chat_widget at prefix "/chat-widget"
|
|
293
|
+
app/utils/auth.py ← get_current_user dep (reused, untouched)
|
|
294
|
+
|
|
295
|
+
Wealth-alpha-chat-UI/ (the npm library)
|
|
296
|
+
src/
|
|
297
|
+
components/WealthChat.tsx ← entry component, props, welcome
|
|
298
|
+
components/AuthGate.tsx ← login required panel + disclaimer
|
|
299
|
+
components/ChatBody.tsx ← scrolling message list
|
|
300
|
+
components/MessageBubble.tsx ← single message + chips
|
|
301
|
+
api/chatApi.ts ← fetch wrapper (timeout, retry, auth, abort)
|
|
302
|
+
hooks/useAuth.ts ← /me check
|
|
303
|
+
hooks/useChat.ts ← message state + sendText
|
|
304
|
+
hooks/useChip.ts ← chip click → sendChip
|
|
305
|
+
hooks/useSession.ts ← TTL, sliding window, storage events
|
|
306
|
+
utils/session.ts ← localStorage adapter (auto-discovers JWT)
|
|
307
|
+
|
|
308
|
+
WealthAlpha-Frontend/ (consumer)
|
|
309
|
+
src/app/layout.tsx ← <WealthChat apiBase="…/chat-widget" />
|
|
310
|
+
.env.local ← NEXT_PUBLIC_API_BASE, NEXT_PUBLIC_CHAT_DEMO
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 8. The 4 reliable commands while developing
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
# Backend reload (when chat_widget.py or chat_formatters.py changes)
|
|
319
|
+
cd WealthAlpha-Backend && source venv/bin/activate
|
|
320
|
+
uvicorn main:app --reload --port 8013
|
|
321
|
+
|
|
322
|
+
# Library rebuild & ship (when src/ changes)
|
|
323
|
+
cd Wealth-alpha-chat-UI
|
|
324
|
+
npm run typecheck && npm run build && npm pack
|
|
325
|
+
|
|
326
|
+
# Frontend pickup
|
|
327
|
+
cd ../WealthAlpha-Frontend
|
|
328
|
+
npm install ../Wealth-alpha-chat-UI/wealth-alpha-chat-0.1.0.tgz --force
|
|
329
|
+
rm -rf .next && npm run dev
|
|
330
|
+
|
|
331
|
+
# Smoke-test from terminal (no UI needed)
|
|
332
|
+
TOKEN=$(curl -s -X POST http://localhost:8013/api/v1/auth/login \
|
|
333
|
+
-H 'Content-Type: application/json' \
|
|
334
|
+
-d '{"email":"you@example.com","password":"…"}' | jq -r '.data')
|
|
335
|
+
|
|
336
|
+
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8013/api/v1/chat-widget/me
|
|
337
|
+
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
|
|
338
|
+
-X POST http://localhost:8013/api/v1/chat-widget/chip/stock_analysis \
|
|
339
|
+
-d '{"chipId":"stock_analysis","sessionId":"test"}'
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## 9. Common failures & exact fixes
|
|
345
|
+
|
|
346
|
+
| Symptom | Cause | Fix |
|
|
347
|
+
|---|---|---|
|
|
348
|
+
| `404 /chat-widget/chat/chip/…` (path doubled) | `apiBase` ended with `/chat-widget` AND library prefixed `/chat/` | Fixed in chatApi.ts — now uses `/chip/{id}` & `/message` |
|
|
349
|
+
| `401 Unauthorized` on `/chat-widget/me` | Library couldn't find a JWT, OR JWT expired | Library now reads `localStorage["token"]` as fallback. Or re-login to refresh the JWT. |
|
|
350
|
+
| Chat shows AuthGate even when logged in | `wac_session` has stale token, host's `token` key not detected | `localStorage.removeItem("wac_session"); location.reload();` |
|
|
351
|
+
| `Upstream /api/v1/… is unreachable` | The 14 telegram-api routes not registered in `router.py` | Already fixed — they're included at the bottom of router.py |
|
|
352
|
+
| `503` or `httpx.SSL` after deploy | Reverse proxy strips `X-Forwarded-Proto` | `uvicorn main:app --proxy-headers --forwarded-allow-ips='*'` |
|
|
353
|
+
| Chip click does nothing | Chip ID not in `ROOT_CHIPS`/`NAV_TREE`/`ACTIONS`/`DIRECT_CALLS` | Check `chat_widget.py` — add the entry, restart backend |
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
*Doc version: 1.0 — kept beside the npm library for easy reference. Update when the chip tree or formatter list changes.*
|