@marktiderman/genesis-chat 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/runner.ts","../src/server/transcribers.ts","../src/server/crypto.ts","../src/server/mux.ts","../src/server/expo.ts","../src/server/handler.ts"],"names":["json"],"mappings":";;;AAmBO,IAAM,iBAAA,GAAoB;AACjC,IAAM,SAAA,GAAuC,CAAC,MAAA,EAAQ,YAAA,EAAc,kBAAkB,cAAc,CAAA;AACpG,IAAM,kBAAA,GAAqB,GAAA;AA4D3B,SAAS,SAAA,CAAU,IAAY,KAAA,EAAsE;AAEnG,EAAA,OAAO,IAAI,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAA,EAAK,OAAO,IAAA,IAAQ,KAAA,EAAO,OAAA,IAAW,OAAO,CAAA,CAAE,CAAA;AACvE;AAEA,SAAS,IAAI,KAAA,EAA+B;AAC1C,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,MAAA,GAAS,IAAI,KAAA,GAAQ,IAAA;AACjE;AAGA,eAAe,IAAA,CAAQ,KAAA,EAAqB,KAAA,EAAe,IAAA,EAAiD;AAC1G,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,KAAA,CAAM,MAAM,CAAA,EAAE,EAAG,YAAY;AAC9E,IAAA,OAAO,OAAO,KAAA,CAAM,MAAA,QAAc,IAAA,CAAK,KAAA,CAAM,MAAM,CAAE,CAAA;AAAA,EACvD,CAAC,CAAA;AACD,EAAA,MAAM,OAAA,CAAQ,IAAI,KAAK,CAAA;AACzB;AAEO,SAAS,iBAAiB,OAAA,EAAwC;AACvE,EAAA,MAAM,EAAE,QAAA,EAAU,OAAA,EAAQ,GAAI,OAAA;AAC9B,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAI,OAAA,CAAQ,SAAS,SAAU,CAAA;AAC9C,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,KAAA,EAAO,KAAA,IAAS,EAAA,EAAI,CAAC,CAAA,EAAG,GAAG,CAAA;AACnE,EAAA,MAAM,cAAc,IAAA,CAAK,GAAA,CAAI,QAAQ,KAAA,EAAO,WAAA,IAAe,GAAG,CAAC,CAAA;AAC/D,EAAA,MAAM,cAAc,IAAI,GAAA,CAAI,QAAQ,WAAA,IAAe,CAAC,QAAQ,CAAC,CAAA;AAC7D,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,KAAQ,MAAM;AAAA,EAAC,CAAA,CAAA;AACnC,EAAA,MAAM,EAAA,GAAK,MAAM,QAAA,CAAS,MAAA,CAAO,WAAW,CAAA;AAE5C,EAAA,eAAe,GAAA,CAAO,IAAY,IAAA,EAA2C;AAC3E,IAAA,MAAM,EAAE,MAAM,KAAA,EAAM,GAAK,MAAM,EAAA,EAAG,CAAE,GAAA,CAAI,EAAA,EAAI,IAAI,CAAA;AAChD,IAAA,IAAI,KAAA,EAAO,MAAM,SAAA,CAAU,EAAA,EAAI,KAAK,CAAA;AACpC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,eAAe,OAAO,MAAA,EAA+C;AACnE,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,OAAA,CAAQ,aAAa,OAAO,IAAA;AAC5C,IAAA,IAAI;AACF,MAAA,OAAO,GAAA,CAAI,MAAM,OAAA,CAAQ,WAAA,CAAY,MAAM,CAAC,CAAA;AAAA,IAC9C,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,eAAe,KAAK,MAAA,EAAmC;AACrD,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,UAAU,CAAA;AAC/C,IAAA,IAAI,CAAC,SAAA,EAAW;AAChB,IAAA,MAAM,OAAA,GAAW,MAAM,GAAA,CAAyB,cAAA,EAAgB,EAAE,YAAA,EAAc,SAAA,EAAW,CAAA,IAAM,EAAC;AAElG,IAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC1B,IAAA,MAAM,KAAA,GAAQ,QAAQ,CAAC,CAAA;AACvB,IAAA,MAAM,iBAAiB,KAAA,CAAM,eAAA;AAC7B,IAAA,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,OAAM,GAAK,MAAM,IAAG,CAC7C,IAAA,CAAK,eAAe,CAAA,CACpB,OAAO,MAAM,CAAA,CACb,GAAG,IAAA,EAAM,cAAc,EACvB,WAAA,EAAY;AACf,IAAA,IAAI,KAAA,EAAO,MAAM,SAAA,CAAU,eAAA,EAAiB,KAAK,CAAA;AACjD,IAAA,MAAM,SAAS,YAAA,GAAe,WAAA,CAAY,GAAA,CAAI,YAAA,CAAa,IAAI,CAAA,GAAI,KAAA;AAEnE,IAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAS,GAAK,MAAM,IAAG,CAAE,IAAA,CAAK,UAAU,CAAA,CAAE,OAAO,KAAK,CAAA,CAAE,GAAG,IAAA,EAAM,SAAS,EAAE,WAAA,EAAY;AACtG,IAAA,MAAM,GAAA,GAAM,UAAU,GAAA,IAAO,IAAA;AAC7B,IAAA,MAAM,UAAA,GAAa,MAAM,MAAA,CAAO,KAAA,CAAM,SAAS,CAAA;AAC/C,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,IAAW,aAAA;AAEjC,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,KAAK,KAAK,UAAA,IAAc,aAAA;AAChD,IAAA,MAAM,IAAA,GAAO,CAAC,MAAA,IAAU,UAAA,GAAa,GAAG,UAAU,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA,GAAK,OAAA;AACnE,IAAA,MAAM,YAAA,GAAiC;AAAA,MACrC,QAAA,EAAU,UAAA;AAAA,MACV,SAAA;AAAA,MACA,cAAA;AAAA,MACA,YAAY,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,MAAA,EAAQ,CAAA,CAAE,OAAA,EAAS,KAAA,EAAO,EAAE,YAAA,IAAgB,CAAA,EAAG,WAAW,CAAA,CAAE,SAAA,KAAc,MAAK,CAAE,CAAA;AAAA,MACnH,KAAA;AAAA,MACA,IAAA;AAAA,MACA,UAAU,KAAA,CAAM,SAAA;AAAA,MAChB,WAAA,EAAa,QAAQ,cAAc,CAAA,CAAA;AAAA,MACnC,SAAA,EAAW,cAAA;AAAA,MACX,cAAA,EAAgB,QAAQ,SAAS,CAAA,CAAA;AAAA,MACjC,IAAA,EAAM,EAAE,GAAA,EAAK,OAAA,CAAQ,OAAA,GAAU,cAAA,EAAgB,SAAS,CAAA,IAAK,CAAA,UAAA,EAAa,cAAc,CAAA,CAAA,EAAI,cAAA,EAAgB,WAAW,GAAA;AAAI,KAC7H;AACA,IAAA,MAAM,QAAQ,YAAY,CAAA;AAAA,EAC5B;AAEA,EAAA,eAAe,cAAc,MAAA,EAAmC;AAC9D,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA;AACpC,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,IAAK,WAAA;AAC7C,IAAA,MAAM,EAAE,KAAA,EAAM,GAAK,MAAM,QAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,CAAE,MAAA,CAAO,CAAC,IAAI,CAAC,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO;AACZ,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,KAAA,CAAM,UAAA,IAAc,EAAE,CAAA;AAC5C,IAAA,IAAI,WAAW,KAAA,IAAS,aAAA,CAAc,KAAK,KAAA,CAAM,OAAA,IAAW,EAAE,CAAA,EAAG;AACjE,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gBAAA,EAAmB,UAAU,KAAA,CAAM,OAAA,IAAW,OAAO,CAAA,CAAE,CAAA;AAAA,EACzE;AAEA,EAAA,eAAe,WAAW,MAAA,EAAmC;AAC3D,IAAA,MAAM,EAAA,GAAK,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,aAAa,CAAA;AAC3C,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,MAAM,EAAE,IAAA,EAAM,GAAA,EAAK,OAAM,GAAK,MAAM,IAAG,CACpC,IAAA,CAAK,aAAa,CAAA,CAClB,OAAO,gEAAgE,CAAA,CACvE,GAAG,IAAA,EAAM,EAAE,EACX,WAAA,EAAY;AACf,IAAA,IAAI,KAAA,EAAO,MAAM,SAAA,CAAU,aAAA,EAAe,KAAK,CAAA;AAE/C,IAAA,IAAI,CAAC,GAAA,IAAQ,GAAA,CAAI,sBAAsB,SAAA,IAAa,GAAA,CAAI,sBAAsB,IAAA,EAAO;AAErF,IAAA,IAAI,GAAA,CAAI,aAAa,SAAA,EAAW;AAChC,IAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,IAAe,CAAC,IAAI,IAAA,EAAM;AACrC,MAAA,MAAM,GAAA,CAAI,mBAAA,EAAqB,EAAE,eAAA,EAAiB,EAAA,EAAI,SAAS,EAAE,iBAAA,EAAmB,KAAA,EAAM,EAAG,CAAA;AAC7F,MAAA;AAAA,IACF;AACA,IAAA,MAAM,MAAA,GAAU,MAAM,QAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,WAAW,CAAA,CAAE,eAAA,CAAgB,GAAA,CAAI,IAAA,EAAM,kBAAkB,CAAA;AACrG,IAAA,IAAI,MAAA,CAAO,KAAA,IAAS,CAAC,MAAA,CAAO,IAAA,EAAM,WAAW,MAAM,SAAA,CAAU,iBAAA,EAAmB,MAAA,CAAO,KAAK,CAAA;AAC5F,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,WAAA,CAAY,UAAA,CAAW;AAAA,QAClD,GAAA,EAAK,OAAO,IAAA,CAAK,SAAA;AAAA,QACjB,MAAM,GAAA,CAAI,IAAA;AAAA,QACV,YAAY,GAAA,CAAI,WAAA;AAAA,QAChB,UAAU,OAAA,CAAQ;AAAA,OACnB,CAAA;AACD,MAAA,MAAM,IAAI,mBAAA,EAAqB,EAAE,eAAA,EAAiB,EAAA,EAAI,SAAS,EAAE,UAAA,EAAY,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAAG,GAAO,GAAG,iBAAA,EAAmB,OAAA,IAAW,CAAA;AAAA,IAC5I,SAAS,CAAA,EAAG;AAEV,MAAA,IAAI,MAAA,CAAO,YAAY,iBAAA,EAAmB;AACxC,QAAA,MAAM,GAAA,CAAI,mBAAA,EAAqB,EAAE,eAAA,EAAiB,EAAA,EAAI,OAAA,EAAS,EAAE,iBAAA,EAAmB,QAAA,EAAS,EAAG,CAAA,CAAE,MAAM,MAAM;AAAA,QAAC,CAAC,CAAA;AAAA,MAClH;AACA,MAAA,MAAM,CAAA;AAAA,IACR;AAAA,EACF;AAEA,EAAA,eAAe,YAAY,MAAA,EAAmC;AAC5D,IAAA,MAAM,UAAA,GAAa,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA;AACjD,IAAA,IAAI,CAAC,UAAA,IAAc,CAAC,OAAA,CAAQ,KAAA,EAAO;AACnC,IAAA,MAAM,QAAA,GAAW,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA;AAC5C,IAAA,IAAI,QAAA,IAAY,QAAA,KAAa,OAAA,CAAQ,KAAA,CAAM,IAAA,EAAM;AAC/C,MAAA,MAAM,IAAI,MAAM,CAAA,6BAAA,EAAgC,QAAQ,qBAAqB,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,IACpG;AACA,IAAA,MAAM,OAAA,CAAQ,KAAA,CAAM,MAAA,CAAO,UAAU,CAAA;AAAA,EACvC;AAEA,EAAA,eAAe,OAAO,MAAA,EAAmC;AACvD,IAAA,QAAQ,OAAO,IAAA;AAAM,MACnB,KAAK,MAAA;AACH,QAAA,OAAO,KAAK,MAAM,CAAA;AAAA,MACpB,KAAK,YAAA;AACH,QAAA,OAAO,WAAW,MAAM,CAAA;AAAA,MAC1B,KAAK,gBAAA;AACH,QAAA,OAAO,cAAc,MAAM,CAAA;AAAA,MAC7B,KAAK,cAAA;AACH,QAAA,OAAO,YAAY,MAAM,CAAA;AAAA,MAC3B;AACE,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,eAAA,EAAkB,MAAA,CAAO,IAAI,CAAA,CAAE,CAAA;AAAA;AACnD,EACF;AAEA,EAAA,eAAe,GAAA,GAA8B;AAC3C,IAAA,MAAM,OAAA,GAAW,MAAM,GAAA,CAAyB,eAAA,EAAiB,EAAE,OAAA,EAAS,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,CAAA,IAAM,EAAC;AAC1G,IAAA,MAAM,MAAA,GAAwB,EAAE,OAAA,EAAS,OAAA,CAAQ,QAAQ,IAAA,EAAM,CAAA,EAAG,QAAQ,CAAA,EAAE;AAC5E,IAAA,MAAM,MAAA,uBAAa,GAAA,EAA0B;AAC7C,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS,MAAA,CAAO,GAAA,CAAI,MAAA,CAAO,MAAM,CAAC,GAAI,MAAA,CAAO,GAAA,CAAI,OAAO,IAAI,CAAA,IAAK,EAAC,EAAI,MAAM,CAAC,CAAA;AAClG,IAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,MACZ,CAAC,GAAG,MAAA,CAAO,MAAA,EAAQ,CAAA,CAAE,GAAA;AAAA,QAAI,CAAC,OAAA,KACxB,IAAA,CAAK,OAAA,EAAS,WAAA,EAAa,OAAO,MAAA,KAAW;AAC3C,UAAA,IAAI,OAAA,GAAyB,IAAA;AAC7B,UAAA,IAAI;AACF,YAAA,MAAM,MAAA,CAAO,EAAE,GAAG,MAAA,EAAQ,SAAS,MAAA,CAAO,OAAA,IAAW,EAAC,EAAG,CAAA;AAAA,UAC3D,SAAS,CAAA,EAAG;AACV,YAAA,OAAA,GAAA,CAAW,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAA,EAAG,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,IAAK,OAAA;AAAA,UAC1E;AACA,UAAA,IAAI;AACF,YAAA,MAAM,GAAA,CAAI,mBAAmB,EAAE,IAAA,EAAM,OAAO,EAAA,EAAI,OAAA,EAAS,SAAS,CAAA;AAAA,UACpE,SAAS,CAAA,EAAG;AAEV,YAAA,GAAA,CAAI,CAAA,8BAAA,EAAiC,OAAO,EAAE,CAAA,SAAA,EAAY,aAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,OAAO,CAAA,CAAE,CAAA;AACpG,YAAA,OAAA,KAAY,WAAA;AAAA,UACd;AACA,UAAA,IAAI,OAAA,EAAS;AACX,YAAA,MAAA,CAAO,MAAA,EAAA;AACP,YAAA,GAAA,CAAI,CAAA,cAAA,EAAiB,MAAA,CAAO,IAAI,CAAA,QAAA,EAAW,MAAA,CAAO,EAAE,CAAA,UAAA,EAAa,MAAA,CAAO,QAAQ,CAAA,UAAA,EAAa,OAAO,CAAA,CAAE,CAAA;AAAA,UACxG,OAAO,MAAA,CAAO,IAAA,EAAA;AAAA,QAChB,CAAC;AAAA;AACH,KACF;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,EAAE,KAAK,MAAA,EAAO;AACvB;;;AClQA,IAAM,UAAA,GAAa,IAAA;AAEnB,eAAe,MAAA,CAAO,MAAc,QAAA,EAAoC;AACtE,EAAA,MAAM,QAAA,CAAS,IAAA,EAAM,MAAA,EAAO,CAAE,MAAM,MAAM;AAAA,EAAC,CAAC,CAAA;AAC5C,EAAA,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,IAAI,CAAA,eAAA,EAAkB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAC5D;AAeO,SAAS,oBAAoB,OAAA,EAAuC;AACzE,EAAA,MAAM,OAAA,GAAU,QAAQ,KAAA,IAAS,KAAA;AACjC,EAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,IAAS,QAAA;AAC/B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,UAAA;AAAA,IACN,MAAM,WAAW,KAAA,EAAmD;AAClE,MAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,EAAE,KAAA,EAAO,YAAA,EAAc,QAAQ,CAAA;AAClE,MAAA,IAAI,MAAM,QAAA,EAAU,MAAA,CAAO,GAAA,CAAI,UAAA,EAAY,MAAM,QAAQ,CAAA;AAAA,WACpD,MAAA,CAAO,GAAA,CAAI,iBAAA,EAAmB,MAAM,CAAA;AACzC,MAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,CAAA,mCAAA,EAAsC,MAAM,CAAA,CAAA,EAAI;AAAA,QAC7E,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,aAAA,EAAe,CAAA,MAAA,EAAS,OAAA,CAAQ,MAAM,CAAA,CAAA,EAAI,cAAA,EAAgB,kBAAA,EAAoB,MAAA,EAAQ,kBAAA,EAAmB;AAAA,QACpH,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,GAAA,EAAK,KAAA,CAAM,KAAK,CAAA;AAAA,QACvC,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,UAAU;AAAA,OACvC,CAAA;AACD,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,OAAO,MAAA,CAAO,YAAY,QAAQ,CAAA;AACpD,MAAA,MAAMA,KAAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAGlC,MAAA,MAAM,OAAA,GAAUA,KAAAA,CAAK,OAAA,EAAS,QAAA,GAAW,CAAC,CAAA;AAC1C,MAAA,MAAM,IAAA,GAAO,OAAA,EAAS,YAAA,GAAe,CAAC,CAAA,EAAG,UAAA;AACzC,MAAA,IAAI,OAAO,IAAA,KAAS,QAAA,EAAU,MAAM,IAAI,MAAM,wCAAwC,CAAA;AACtF,MAAA,OAAO,EAAE,MAAM,IAAA,CAAK,IAAA,IAAQ,QAAA,EAAU,OAAA,EAAS,iBAAA,IAAqB,KAAA,CAAM,QAAA,EAAS;AAAA,IACrF;AAAA,GACF;AACF;AASA,IAAM,UAAA,GAAqC;AAAA,EACzC,WAAA,EAAa,KAAA;AAAA,EACb,aAAA,EAAe,KAAA;AAAA,EACf,WAAA,EAAa,KAAA;AAAA,EACb,YAAA,EAAc,KAAA;AAAA,EACd,YAAA,EAAc,MAAA;AAAA,EACd,WAAA,EAAa,KAAA;AAAA,EACb,WAAA,EAAa,KAAA;AAAA,EACb,WAAA,EAAa,KAAA;AAAA,EACb,YAAA,EAAc;AAChB,CAAA;AAGA,IAAM,gBAAA,GAAmB,KAAK,IAAA,GAAO,IAAA;AAO9B,SAAS,kBAAkB,OAAA,EAAgD;AAChF,EAAA,MAAM,OAAA,GAAU,QAAQ,KAAA,IAAS,KAAA;AACjC,EAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,IAAS,wBAAA;AAC/B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA;AAAA,IACN,MAAM,WAAW,KAAA,EAAmD;AAClE,MAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,KAAA,CAAM,GAAA,EAAK,EAAE,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,UAAU,CAAA,EAAG,CAAA;AACjF,MAAA,IAAI,CAAC,IAAA,CAAK,EAAA,EAAI,OAAO,MAAA,CAAO,WAAW,IAAI,CAAA;AAC3C,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,EAAK;AAC7B,MAAA,IAAI,KAAK,IAAA,GAAO,gBAAA,EAAkB,MAAM,IAAI,MAAM,uCAAuC,CAAA;AACzF,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,KAAS,IAAA,CAAK,IAAA,IAAQ,WAAA,CAAA;AACzC,MAAA,MAAM,IAAA,GAAO,IAAI,QAAA,EAAS;AAC1B,MAAA,IAAA,CAAK,IAAI,MAAA,EAAQ,IAAI,IAAA,CAAK,CAAC,IAAI,CAAA,EAAG,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA,EAAG,CAAA,MAAA,EAAS,WAAW,IAAI,CAAA,IAAK,KAAK,CAAA,CAAE,CAAA;AACvF,MAAA,IAAA,CAAK,GAAA,CAAI,SAAS,KAAK,CAAA;AACvB,MAAA,IAAA,CAAK,GAAA,CAAI,mBAAmB,MAAM,CAAA;AAClC,MAAA,IAAI,MAAM,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,UAAA,EAAY,MAAM,QAAQ,CAAA;AACvD,MAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,gDAAA,EAAkD;AAAA,QAC/E,MAAA,EAAQ,MAAA;AAAA,QACR,SAAS,EAAE,aAAA,EAAe,CAAA,OAAA,EAAU,OAAA,CAAQ,MAAM,CAAA,CAAA,EAAG;AAAA,QACrD,IAAA,EAAM,IAAA;AAAA,QACN,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,UAAU;AAAA,OACvC,CAAA;AACD,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,OAAO,MAAA,CAAO,UAAU,QAAQ,CAAA;AAClD,MAAA,MAAMA,KAAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,MAAA,IAAI,OAAOA,KAAAA,CAAK,IAAA,KAAS,UAAU,MAAM,IAAI,MAAM,sCAAsC,CAAA;AACzF,MAAA,OAAO,EAAE,MAAMA,KAAAA,CAAK,IAAA,CAAK,MAAK,EAAG,QAAA,EAAU,MAAM,QAAA,EAAS;AAAA,IAC5D;AAAA,GACF;AACF;;;AC3GA,IAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAEhC,eAAe,OAAO,KAAA,EAAoC;AACxD,EAAA,OAAO,IAAI,UAAA,CAAW,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAC,CAAC,CAAA;AACpF;AAGA,eAAsB,SAAA,CAAU,GAAW,CAAA,EAA6B;AACtE,EAAA,MAAM,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,CAAO,CAAC,CAAA,EAAG,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AACvD,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAA,IAAQ,CAAA,CAAE,CAAC,CAAA,GAAK,CAAA,CAAE,CAAC,CAAA;AACtD,EAAA,OAAO,IAAA,KAAS,CAAA;AAClB;AAGA,eAAsB,aAAA,CAAc,QAAgB,OAAA,EAAkC;AACpF,EAAA,MAAM,MAAM,MAAM,MAAA,CAAO,OAAO,SAAA,CAAU,KAAA,EAAO,QAAQ,MAAA,CAAO,MAAM,GAAG,EAAE,IAAA,EAAM,QAAQ,IAAA,EAAM,SAAA,IAAa,KAAA,EAAO,CAAC,MAAM,CAAC,CAAA;AAC3H,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,MAAM,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,GAAA,EAAK,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAC,CAAC,CAAA;AACzF,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,GAAA,EAAK,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,SAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AACxE;AAEO,SAAS,cAAc,KAAA,EAA2B;AACvD,EAAA,MAAM,SAAS,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAC,CAAA;AAC7C,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,MAAA,CAAO,MAAM,CAAA;AACxC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,CAAO,MAAA,EAAQ,CAAA,EAAA,EAAK,GAAA,CAAI,CAAC,CAAA,GAAI,MAAA,CAAO,UAAA,CAAW,CAAC,CAAA;AACpE,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,UAAU,KAAA,EAAoC;AAC5D,EAAA,MAAM,IAAI,OAAO,KAAA,KAAU,WAAW,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA,GAAI,KAAA;AAC9D,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,MAAW,IAAA,IAAQ,CAAA,EAAG,MAAA,IAAU,MAAA,CAAO,aAAa,IAAI,CAAA;AACxD,EAAA,OAAO,IAAA,CAAK,MAAM,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC/E;AAEA,SAAS,UAAU,CAAA,EAAqB;AACtC,EAAA,IAAI,CAAA,GAAI,GAAA,EAAM,OAAO,CAAC,CAAC,CAAA;AACvB,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,GAAI,CAAA,EAAG,MAAM,CAAA,EAAG,KAAA,CAAM,OAAA,CAAQ,CAAA,GAAI,GAAI,CAAA;AACtD,EAAA,OAAO,CAAC,GAAA,GAAO,KAAA,CAAM,MAAA,EAAQ,GAAG,KAAK,CAAA;AACvC;AAEA,SAAS,GAAA,CAAI,KAAa,IAAA,EAA8B;AACtD,EAAA,MAAM,GAAA,GAAM,SAAA,CAAU,IAAA,CAAK,MAAM,CAAA;AACjC,EAAA,MAAM,MAAM,IAAI,UAAA,CAAW,IAAI,GAAA,CAAI,MAAA,GAAS,KAAK,MAAM,CAAA;AACvD,EAAA,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA;AACT,EAAA,GAAA,CAAI,GAAA,CAAI,KAAK,CAAC,CAAA;AACd,EAAA,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,CAAA,GAAI,GAAA,CAAI,MAAM,CAAA;AAC5B,EAAA,OAAO,GAAA;AACT;AAGA,SAAS,aAAa,KAAA,EAA+B;AAEnD,EAAA,MAAM,UAAU,IAAI,UAAA,CAAW,CAAC,CAAA,EAAM,CAAA,EAAM,CAAI,CAAC,CAAA;AACjD,EAAA,MAAM,YAAY,IAAI,UAAA,CAAW,CAAC,EAAA,EAAM,EAAA,EAAM,GAAM,CAAA,EAAM,EAAA,EAAM,KAAM,EAAA,EAAM,GAAA,EAAM,KAAM,EAAA,EAAM,CAAA,EAAM,GAAM,CAAA,EAAM,CAAA,EAAM,CAAI,CAAC,CAAA;AAC3H,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,CAAA,EAAM,KAAK,CAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,OAAA,CAAQ,SAAS,SAAA,CAAU,MAAA,GAAS,OAAO,MAAM,CAAA;AAC7E,EAAA,IAAA,CAAK,GAAA,CAAI,SAAS,CAAC,CAAA;AACnB,EAAA,IAAA,CAAK,GAAA,CAAI,SAAA,EAAW,OAAA,CAAQ,MAAM,CAAA;AAClC,EAAA,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,OAAA,CAAQ,MAAA,GAAS,UAAU,MAAM,CAAA;AAClD,EAAA,OAAO,GAAA,CAAI,IAAM,IAAI,CAAA;AACvB;AAMA,eAAsB,oBAAoB,GAAA,EAAiC;AACzE,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA,GAAI,GAAA,GAAM,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,aAAA,CAAc,GAAG,CAAC,CAAA;AAC1F,EAAA,MAAM,KAAA,GAAQ,8EAAA,CAA+E,IAAA,CAAK,GAAG,CAAA;AACrG,EAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,4CAA4C,CAAA;AACxE,EAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,KAAA,CAAM,CAAC,CAAE,CAAA;AACrC,EAAA,MAAM,QAAQ,KAAA,CAAM,CAAC,CAAA,GAAI,YAAA,CAAa,KAAK,CAAA,GAAI,KAAA;AAC/C,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,SAAA,CAAU,OAAA,EAAS,OAAuB,EAAE,IAAA,EAAM,mBAAA,EAAqB,IAAA,EAAM,SAAA,EAAU,EAAG,KAAA,EAAO,CAAC,MAAM,CAAC,CAAA;AAChI;AAGA,eAAsB,YAAA,CAAa,MAAA,EAAiC,MAAA,EAAiC,GAAA,EAAiC;AACpI,EAAA,MAAM,KAAA,GAAQ,GAAG,SAAA,CAAU,IAAA,CAAK,UAAU,EAAE,GAAA,EAAK,SAAS,GAAA,EAAK,KAAA,EAAO,GAAG,MAAA,EAAQ,CAAC,CAAC,CAAA,CAAA,EAAI,UAAU,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAC,CAAA,CAAA;AACxH,EAAA,MAAM,SAAA,GAAY,IAAI,UAAA,CAAW,MAAM,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,mBAAA,EAAqB,GAAA,EAAK,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAC,CAAC,CAAA;AAC1G,EAAA,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,SAAA,CAAU,SAAS,CAAC,CAAA,CAAA;AACzC;;;AC7DA,IAAM,GAAA,GAAM,8BAAA;AACZ,IAAM,iBAAA,GAAoB,GAAA;AAC1B,IAAM,IAAA,GAAO,iEAAA;AA2Bb,eAAsB,mBACpB,MAAA,EACA,IAAA,EACA,MAAA,EACA,OAAA,GAAuD,EAAC,EACtC;AAClB,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,EAAQ,OAAO,KAAA;AAC/B,EAAA,IAAI,SAAA,GAA2B,IAAA;AAC/B,EAAA,MAAM,aAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,EAAG;AACpC,IAAA,MAAM,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,KAAK,KAAA,CAAM,GAAA,EAAK,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,MAAM,CAAA;AACrD,IAAA,IAAI,CAAA,KAAM,GAAA,IAAO,CAAA,EAAG,SAAA,GAAY,CAAA;AAAA,SAAA,IACvB,MAAM,IAAA,IAAQ,CAAA,aAAc,IAAA,CAAK,CAAA,CAAE,aAAa,CAAA;AAAA,EAC3D;AACA,EAAA,IAAI,CAAC,SAAA,IAAa,CAAC,YAAA,CAAa,IAAA,CAAK,SAAS,CAAA,IAAK,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA;AACnF,EAAA,MAAM,GAAA,GAAM,KAAK,KAAA,CAAA,CAAO,OAAA,CAAQ,OAAO,IAAA,CAAK,GAAA,MAAS,GAAI,CAAA;AACzD,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,GAAA,GAAM,MAAA,CAAO,SAAS,CAAC,CAAA,IAAK,OAAA,CAAQ,gBAAA,IAAoB,iBAAA,CAAA,EAAoB,OAAO,KAAA;AAChG,EAAA,MAAM,QAAA,GAAW,MAAM,aAAA,CAAc,MAAA,EAAQ,GAAG,SAAS,CAAA,CAAA,EAAI,IAAI,CAAA,CAAE,CAAA;AACnE,EAAA,IAAI,EAAA,GAAK,KAAA;AACT,EAAA,KAAA,MAAW,SAAA,IAAa,YAAY,IAAI,MAAM,UAAU,SAAA,EAAW,QAAQ,GAAG,EAAA,GAAK,IAAA;AACnF,EAAA,OAAO,EAAA;AACT;AAMA,eAAsB,cAAA,CACpB,UAAA,EACA,GAAA,EACA,OAAA,GAAsE,EAAC,EACtD;AACjB,EAAA,MAAM,KAAA,GAAQ,MAAM,gBAAA,CAAiB,UAAA,EAAY,KAAK,OAAO,CAAA;AAC7D,EAAA,OAAO,0BAA0B,UAAU,CAAA,EAAG,QAAQ,IAAA,IAAQ,OAAO,UAAU,KAAK,CAAA,CAAA;AACtF;AAEA,eAAsB,gBAAA,CACpB,UAAA,EACA,GAAA,EACA,OAAA,GAAyF,EAAC,EACzE;AACjB,EAAA,MAAM,GAAA,GAAM,KAAK,KAAA,CAAA,CAAO,OAAA,CAAQ,OAAO,IAAA,CAAK,GAAA,MAAS,GAAI,CAAA;AACzD,EAAA,MAAM,UAAA,GAAa,MAAM,mBAAA,CAAoB,GAAA,CAAI,UAAU,CAAA;AAC3D,EAAA,OAAO,YAAA;AAAA,IACL,EAAE,GAAA,EAAK,GAAA,CAAI,YAAA,EAAa;AAAA,IACxB,EAAE,GAAA,EAAK,UAAA,EAAY,GAAA,EAAK,OAAA,CAAQ,QAAA,IAAY,GAAA,EAAK,GAAA,EAAK,GAAA,IAAO,OAAA,CAAQ,gBAAA,IAAoB,CAAA,GAAI,IAAA,CAAA,EAAM;AAAA,IACnG;AAAA,GACF;AACF;AAgBA,IAAM,OAAO,CAAC,MAAA,EAAgB,IAAA,KAC5B,IAAI,SAAS,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,EAAG,EAAE,MAAA,EAAQ,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,IAAsB,CAAA;AAEzF,SAAS,SAAS,OAAA,EAEvB;AACA,EAAA,MAAM,OAAA,GAAU,QAAQ,KAAA,IAAS,KAAA;AACjC,EAAA,MAAM,MAAA,GAAS,QAAQ,cAAA,IAAkB,QAAA;AACzC,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,QAAA,KAAa,KAAA,GAAQ,OAAO,EAAE,YAAA,EAAc,OAAA,CAAQ,QAAA,EAAU,gBAAgB,IAAA,EAAM,IAAA,EAAM,OAAA,CAAQ,QAAA,EAAU,QAAQ,gBAAA,EAAiB;AAC9J,EAAA,MAAM,IAAA,GAAO,CAAA,MAAA,EAAS,IAAA,CAAK,CAAA,EAAG,OAAA,CAAQ,OAAO,CAAA,CAAA,EAAI,OAAA,CAAQ,WAAW,CAAA,CAAE,CAAC,CAAA,CAAA;AAEvE,EAAA,eAAe,GAAA,CAAI,MAAA,EAAgB,IAAA,EAAc,IAAA,EAAmC;AAClF,IAAA,OAAO,OAAA,CAAQ,CAAA,EAAG,GAAG,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI;AAAA,MAC9B,MAAA;AAAA,MACA,OAAA,EAAS,EAAE,aAAA,EAAe,IAAA,EAAM,MAAA,EAAQ,kBAAA,EAAoB,GAAI,IAAA,GAAO,EAAE,cAAA,EAAgB,kBAAA,EAAmB,GAAI,EAAC,EAAG;AAAA,MACpH,GAAI,OAAO,EAAE,IAAA,EAAM,KAAK,SAAA,CAAU,IAAI,CAAA,EAAE,GAAI,EAAC;AAAA,MAC7C,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,IAAM;AAAA,KACnC,CAAA;AAAA,EACH;AAEA,EAAA,eAAe,MAAA,CAAO,QAAA,EAAwB,EAAA,EAAY,KAAA,EAA+C;AACvG,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,SAAS,MAAA,CAAO,WAAW,CAAA,CAAE,GAAA,CAAI,qBAAqB,EAAE,eAAA,EAAiB,EAAA,EAAI,OAAA,EAAS,OAAO,CAAA;AACrH,IAAA,IAAI,KAAA,QAAa,IAAI,KAAA,CAAM,sBAAsB,KAAA,CAAM,IAAA,IAAQ,OAAO,CAAA,CAAE,CAAA;AAAA,EAC1E;AAGA,EAAA,eAAe,IAAA,CAAK,UAAwB,GAAA,EAAgF;AAC1H,IAAA,MAAM,KAAA,GAAQ,MAAM,QAAA,CAAS,MAAA,CAAO,WAAW,CAAA,CAAE,IAAA,CAAK,aAAa,CAAA,CAAE,MAAA,CAAO,oCAAoC,CAAA,CAAE,EAAA,CAAG,YAAY,KAAK,CAAA;AACtI,IAAA,IAAI,IAAI,WAAA,IAAe,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,WAAW,CAAA,EAAG;AACjD,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,KAAA,EAAM,CAAE,EAAA,CAAG,IAAA,EAAM,GAAA,CAAI,WAAW,CAAA,CAAE,WAAA,EAAY;AAC5E,MAAA,IAAI,KAAA,QAAa,IAAI,KAAA,CAAM,gBAAgB,KAAA,CAAM,IAAA,IAAQ,OAAO,CAAA,CAAE,CAAA;AAClE,MAAA,IAAI,MAAM,OAAO,IAAA;AAAA,IACnB;AACA,IAAA,KAAA,MAAW,UAAA,IAAc,IAAI,WAAA,EAAa;AACxC,MAAA,IAAI,CAAC,UAAA,EAAY;AACjB,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,KAAA,EAAM,CAAE,EAAA,CAAG,aAAA,EAAe,UAAU,CAAA,CAAE,KAAA,CAAM,CAAC,EAAE,WAAA,EAAY;AACzF,MAAA,IAAI,KAAA,QAAa,IAAI,KAAA,CAAM,gBAAgB,KAAA,CAAM,IAAA,IAAQ,OAAO,CAAA,CAAE,CAAA;AAClE,MAAA,IAAI,MAAM,OAAO,IAAA;AAAA,IACnB;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,eAAe,YAAA,CAAa,YAAoB,OAAA,EAAyC;AACvF,IAAA,IAAI,GAAA,GAAM,CAAA,uBAAA,EAA0B,UAAU,CAAA,MAAA,EAAS,OAAO,CAAA,IAAA,CAAA;AAC9D,IAAA,IAAI,WAAW,QAAA,EAAU;AACvB,MAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,EAAS,OAAO,IAAA;AAC7B,MAAA,GAAA,IAAO,CAAA,OAAA,EAAU,MAAM,gBAAA,CAAiB,UAAA,EAAY,OAAA,CAAQ,SAAS,EAAE,gBAAA,EAAkB,GAAA,EAAK,CAAC,CAAA,CAAA;AAAA,IACjG;AACA,IAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,GAAA,EAAK,EAAE,QAAQ,WAAA,CAAY,OAAA,CAAQ,IAAM,CAAA,EAAG,CAAA;AAC3E,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,QAAA,CAAS,IAAA,EAAM,MAAA,EAAO,CAAE,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAC5C,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACnE;AACA,IAAA,OAAA,CAAQ,MAAM,QAAA,CAAS,IAAA,EAAK,EAAG,IAAA,EAAK;AAAA,EACtC;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,KAAA;AAAA,IAEN,MAAM,YAAA,CAAa,EAAE,UAAA,EAAY,aAAY,EAAG;AAC9C,MAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,MAAA,EAAQ,UAAA,EAAY;AAAA,QAC7C,WAAA,EAAa,UAAA;AAAA,QACb,kBAAA,EAAoB;AAAA,UAClB,iBAAA,EAAmB,CAAC,MAAM,CAAA;AAAA,UAC1B,aAAA,EAAe,QAAQ,YAAA,IAAgB,OAAA;AAAA,UACvC,GAAI,WAAA,GAAc,EAAE,WAAA,EAAa,WAAA,CAAY,MAAM,CAAA,EAAG,GAAG,CAAA,EAAE,GAAI,EAAC;AAAA,UAChE,GAAI,WAAW,EAAE,MAAA,EAAQ,CAAC,EAAE,mBAAA,EAAqB,CAAC,EAAE,aAAA,EAAe,SAAS,YAAA,EAAc,IAAA,EAAM,SAAS,IAAA,EAAM,GAAG,CAAA,KAAM;AAAC;AAC3H,OACD,CAAA;AACD,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,QAAA,CAAS,IAAA,EAAM,MAAA,EAAO,CAAE,MAAM,MAAM;AAAA,QAAC,CAAC,CAAA;AAC5C,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MAChE;AACA,MAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,MAAA,IAAI,CAAC,IAAA,CAAK,IAAA,EAAM,EAAA,IAAM,CAAC,IAAA,CAAK,IAAA,CAAK,GAAA,EAAK,MAAM,IAAI,KAAA,CAAM,wCAAwC,CAAA;AAC9F,MAAA,OAAO,EAAE,UAAU,IAAA,CAAK,IAAA,CAAK,IAAI,GAAA,EAAK,IAAA,CAAK,KAAK,GAAA,EAAI;AAAA,IACtD,CAAA;AAAA,IAEA,MAAM,OAAO,OAAA,EAAS;AACpB,MAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,QAAA,EAAU,WAAW,kBAAA,CAAmB,OAAO,CAAC,CAAA,CAAE,CAAA;AAC7E,MAAA,MAAM,QAAA,CAAS,IAAA,EAAM,MAAA,EAAO,CAAE,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAC5C,MAAA,IAAI,QAAA,CAAS,EAAA,IAAM,QAAA,CAAS,MAAA,KAAW,GAAA,EAAK;AAC5C,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,+BAAA,EAAkC,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACrE,CAAA;AAAA,IAEA,WAAA,CAAY,YAAY,IAAA,EAAM;AAC5B,MAAA,IAAI,WAAW,QAAA,EAAU,OAAO,QAAQ,OAAA,CAAQ,CAAA,uBAAA,EAA0B,UAAU,CAAA,KAAA,CAAO,CAAA;AAC3F,MAAA,IAAI,CAAC,QAAQ,OAAA,EAAS,OAAO,QAAQ,MAAA,CAAO,IAAI,KAAA,CAAM,2CAA2C,CAAC,CAAA;AAClG,MAAA,OAAO,cAAA,CAAe,UAAA,EAAY,OAAA,CAAQ,OAAA,EAAS,IAAI,CAAA;AAAA,IACzD,CAAA;AAAA,IAEA,MAAM,aAAA,CAAc,OAAA,EAAS,EAAE,UAAS,EAAG;AACzC,MAAA,IAAI,OAAA,CAAQ,WAAW,MAAA,EAAQ,OAAO,KAAK,GAAA,EAAK,EAAE,KAAA,EAAO,oBAAA,EAAsB,CAAA;AAC/E,MAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,IAAA,EAAK;AAChC,MAAA,MAAM,EAAA,GAAK,MAAM,kBAAA,CAAmB,OAAA,CAAQ,OAAA,CAAQ,IAAI,eAAe,CAAA,EAAG,IAAA,EAAM,OAAA,CAAQ,aAAA,EAAe;AAAA,QACrG,GAAI,QAAQ,GAAA,GAAM,EAAE,KAAK,OAAA,CAAQ,GAAA,EAAI,EAAE,GAAI;AAAC,OAC7C,CAAA;AACD,MAAA,IAAI,CAAC,IAAI,OAAO,IAAA,CAAK,KAAK,EAAE,KAAA,EAAO,iBAAiB,CAAA;AACpD,MAAA,IAAI,KAAA;AACJ,MAAA,IAAI;AACF,QAAA,KAAA,GAAQ,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,MACzB,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACxC;AACA,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,IAAQ,EAAC;AAC5B,MAAA,QAAQ,MAAM,IAAA;AAAM,QAClB,KAAK,mBAAA;AAAA,QACL,KAAK,qBAAA,EAAuB;AAC1B,UAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,QAAA,EAAU,EAAE,WAAA,EAAa,IAAA,CAAK,WAAA,EAAa,WAAA,EAAa,CAAC,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,EAAE,GAAG,CAAA;AACjH,UAAA,IAAI,CAAC,YAAY,OAAO,IAAA,CAAK,KAAK,EAAE,KAAA,EAAO,wBAAwB,CAAA;AACnE,UAAA,IAAI,KAAA,CAAM,SAAS,qBAAA,EAAuB;AACxC,YAAA,MAAM,OAAO,QAAA,EAAU,UAAA,CAAW,EAAA,EAAI,EAAE,QAAQ,QAAA,EAAU,GAAI,IAAA,CAAK,EAAA,GAAK,EAAE,WAAA,EAAa,IAAA,CAAK,IAAG,GAAI,IAAK,CAAA;AAAA,UAC1G,CAAA,MAAO;AACL,YAAA,MAAM,QAAA,GAAW,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,MAAM,CAAA,IAAK,IAAA,CAAK,YAAA,GAAe,CAAC,CAAA;AAC7F,YAAA,MAAM,MAAA,CAAO,QAAA,EAAU,UAAA,CAAW,EAAA,EAAI;AAAA,cACpC,MAAA,EAAQ,OAAA;AAAA,cACR,GAAI,KAAK,EAAA,GAAK,EAAE,aAAa,IAAA,CAAK,EAAA,KAAO,EAAC;AAAA,cAC1C,GAAI,UAAU,EAAA,GAAK,EAAE,aAAa,QAAA,CAAS,EAAA,KAAO,EAAC;AAAA,cACnD,GAAI,OAAO,IAAA,CAAK,QAAA,KAAa,WAAW,EAAE,WAAA,EAAa,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,QAAA,GAAW,GAAI,CAAA,KAAM,EAAC;AAAA;AAAA,cAE7F,GAAI,CAAC,QAAA,GAAW,EAAE,iBAAA,EAAmB,KAAA,KAAU;AAAC,aACjD,CAAA;AAAA,UACH;AACA,UAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,EAAA,EAAI,MAAM,CAAA;AAAA,QAC/B;AAAA,QACA,KAAK,yBAAA;AAAA,QACL,KAAK,2BAAA,EAA6B;AAChC,UAAA,IAAI,IAAA,CAAK,IAAA,KAAS,MAAA,IAAU,IAAA,CAAK,WAAA,KAAgB,eAAA,EAAiB,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA;AACpG,UAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,QAAA,EAAU,EAAE,aAAa,CAAC,IAAA,CAAK,QAAQ,CAAA,EAAG,CAAA;AACxE,UAAA,IAAI,CAAC,YAAY,OAAO,IAAA,CAAK,KAAK,EAAE,KAAA,EAAO,wBAAwB,CAAA;AACnE,UAAA,IAAI,KAAA,CAAM,SAAS,2BAAA,EAA6B;AAC9C,YAAA,MAAM,OAAO,QAAA,EAAU,UAAA,CAAW,IAAI,EAAE,iBAAA,EAAmB,UAAU,CAAA;AACrE,YAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,EAAA,EAAI,MAAM,CAAA;AAAA,UAC/B;AAEA,UAAA,IAAI,CAAC,UAAA,CAAW,WAAA,IAAe,CAAC,IAAA,CAAK,EAAA,EAAI,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,KAAA,EAAO,oBAAA,EAAsB,CAAA;AACzF,UAAA,MAAM,OAAO,MAAM,YAAA,CAAa,UAAA,CAAW,WAAA,EAAa,KAAK,EAAE,CAAA;AAC/D,UAAA,IAAI,IAAA,KAAS,MAAM,OAAO,IAAA,CAAK,KAAK,EAAE,OAAA,EAAS,yCAAyC,CAAA;AACxF,UAAA,MAAM,MAAA,CAAO,QAAA,EAAU,UAAA,CAAW,EAAA,EAAI,EAAE,UAAA,EAAY,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,GAAO,CAAA,EAAG,iBAAA,EAAmB,SAAS,CAAA;AACxG,UAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,EAAA,EAAI,MAAM,CAAA;AAAA,QAC/B;AAAA,QACA;AACE,UAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA;AACtC,IACF;AAAA,GACF;AACF;;;AC7PO,IAAM,aAAA,GAAgB;AACtB,IAAM,UAAA,GAAa;AAyCnB,SAAS,kBAAkB,OAAA,EAAqF;AACrH,EAAA,MAAM,OAAA,GAAU,QAAQ,KAAA,IAAS,KAAA;AACjC,EAAA,OAAO,OAAO,YAAA,KAAiB;AAC7B,IAAA,MAAM,MAAA,GAAS,IAAI,GAAA,CAAI,YAAA,CAAa,WAAW,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAC9E,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,SAAA,CAAU,CAAC,GAAG,MAAA,CAAO,IAAA,EAAM,CAAC,CAAA;AAC1D,IAAA,MAAM,WAAW,OAAA,CAAQ,GAAA;AAAA,MACvB,CAAC,CAAA,MAA6C;AAAA,QAC5C,QAAQ,CAAA,CAAE,MAAA;AAAA,QACV,IAAI,CAAA,CAAE,KAAA;AAAA,QACN,OAAO,YAAA,CAAa,KAAA;AAAA,QACpB,MAAM,YAAA,CAAa,IAAA;AAAA,QACnB,MAAM,YAAA,CAAa,IAAA;AAAA,QACnB,KAAA,EAAO,OAAA,CAAQ,KAAA,KAAU,MAAA,GAAY,YAAY,OAAA,CAAQ,KAAA;AAAA,QACzD,KAAA,EAAO,MAAA,CAAO,GAAA,CAAI,CAAA,CAAE,MAAM,CAAA,IAAK,CAAA;AAAA,QAC/B,UAAU,YAAA,CAAa,SAAA;AAAA,QACvB,GAAI,QAAQ,QAAA,GAAW,EAAE,YAAY,YAAA,CAAa,WAAA,KAAgB,EAAC;AAAA,QACnE,SAAA,EAAW,QAAQ,SAAA,IAAa,UAAA;AAAA,QAChC,UAAA,EAAY,QAAQ,UAAA,IAAc;AAAA,OACpC;AAAA,KACF;AACA,IAAA,MAAM,UAAgC,EAAC;AACvC,IAAA,IAAI,IAAA,GAAO,CAAA;AACX,IAAA,IAAI,SAAA,GAAqB,IAAA;AACzB,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,MAAA,EAAQ,KAAK,UAAA,EAAY;AACpD,MAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,IAAI,UAAU,CAAA;AAC9C,MAAA,IAAI,MAAA,GAAkC,IAAA;AACtC,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,aAAA,EAAe;AAAA,UAC5C,MAAA,EAAQ,MAAA;AAAA,UACR,OAAA,EAAS;AAAA,YACP,MAAA,EAAQ,kBAAA;AAAA,YACR,cAAA,EAAgB,kBAAA;AAAA,YAChB,GAAI,OAAA,CAAQ,WAAA,GAAc,EAAE,aAAA,EAAe,UAAU,OAAA,CAAQ,WAAW,CAAA,CAAA,EAAG,GAAI;AAAC,WAClF;AAAA,UACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,GAAA,CAAI,CAAC,EAAE,MAAA,EAAQ,OAAA,EAAS,GAAG,CAAA,EAAE,KAAM,CAAC,CAAC,CAAA;AAAA,UAChE,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,IAAM;AAAA,SACnC,CAAA;AACD,QAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,UAAA,MAAM,QAAA,CAAS,IAAA,EAAM,MAAA,EAAO,CAAE,MAAM,MAAM;AAAA,UAAC,CAAC,CAAA;AAC5C,UAAA,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,QAC9D;AACA,QAAA,MAAMA,KAAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,QAAA,MAAA,GAAS,MAAM,OAAA,CAAQA,KAAAA,CAAK,IAAI,CAAA,GAAKA,KAAAA,CAAK,OAA4B,EAAC;AACvE,QAAA,IAAA,IAAQ,KAAA,CAAM,MAAA;AAAA,MAChB,SAAS,CAAA,EAAG;AACV,QAAA,SAAA,GAAY,CAAA;AAAA,MACd;AACA,MAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAM;AACtB,QAAA,MAAM,CAAA,GAAI,SAAS,CAAC,CAAA;AACpB,QAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,UACX,QAAQ,CAAA,CAAE,MAAA;AAAA,UACV,OAAO,CAAA,CAAE,EAAA;AAAA,UACT,QAAQ,CAAA,KAAM,CAAA,CAAE,WAAW,IAAA,IAAQ,CAAA,CAAE,WAAW,OAAA,CAAA,GAAW,CAAA,GAAI,EAAE,MAAA,EAAQ,SAAS,OAAA,EAAS,EAAE,OAAO,MAAA,GAAS,UAAA,GAAa,iBAAgB;AAAE,SAC7I,CAAA;AAAA,MACH,CAAC,CAAA;AAAA,IACH;AACA,IAAA,IAAI,QAAA,CAAS,MAAA,GAAS,CAAA,IAAK,IAAA,KAAS,CAAA,EAAG,MAAM,SAAA,YAAqB,KAAA,GAAQ,SAAA,GAAY,IAAI,KAAA,CAAM,kBAAkB,CAAA;AAClH,IAAA,OAAO,OAAA;AAAA,EACT,CAAA;AACF;AAGO,SAAS,WAAW,OAAA,EAAkD;AAC3E,EAAA,OAAO,QAAQ,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,CAAO,WAAW,OAAA,IAAW,CAAA,CAAE,MAAA,CAAO,OAAA,EAAS,UAAU,qBAAqB,CAAA,CAAE,IAAI,CAAC,CAAA,KAAM,EAAE,KAAK,CAAA;AACnI;;;ACrGA,IAAMA,QAAO,CAAC,MAAA,EAAgB,SAC5B,IAAI,QAAA,CAAS,KAAK,SAAA,CAAU,IAAI,GAAG,EAAE,MAAA,EAAQ,SAAS,EAAE,cAAA,EAAgB,oBAAoB,eAAA,EAAiB,UAAA,IAAc,CAAA;AAEtH,SAAS,kBAAkB,OAAA,EAA4E;AAC5G,EAAA,OAAO,OAAO,OAAA,KAAY;AAExB,IAAA,IAAI,CAAC,OAAA,CAAQ,MAAA,IAAU,OAAA,CAAQ,MAAA,CAAO,MAAA,GAAS,EAAA,EAAI,OAAOA,KAAAA,CAAK,GAAA,EAAK,EAAE,KAAA,EAAO,gDAAgD,CAAA;AAC7H,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,MAAA,IAAU,OAAA,CAAQ,MAAA,KAAW,KAAA,EAAO,OAAOA,KAAAA,CAAK,GAAA,EAAK,EAAE,KAAA,EAAO,oBAAA,EAAsB,CAAA;AAC3G,IAAA,MAAM,KAAA,GAAQ,oBAAoB,IAAA,CAAK,OAAA,CAAQ,QAAQ,GAAA,CAAI,eAAe,KAAK,EAAE,CAAA;AACjF,IAAA,IAAI,CAAC,KAAA,IAAS,CAAE,MAAM,SAAA,CAAU,MAAM,CAAC,CAAA,EAAI,OAAA,CAAQ,MAAM,GAAI,OAAOA,KAAAA,CAAK,KAAK,EAAE,KAAA,EAAO,gBAAgB,CAAA;AACvG,IAAA,IAAI;AACF,MAAA,OAAOA,MAAK,GAAA,EAAK,MAAM,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA;AAAA,IAC7C,SAAS,CAAA,EAAG;AACV,MAAA,OAAOA,KAAAA,CAAK,GAAA,EAAK,EAAE,KAAA,EAAO,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,GAAI,cAAc,CAAA;AAAA,IACzF;AAAA,EACF,CAAA;AACF","file":"server.js","sourcesContent":["// @feat:chat\n/**\n * The outbox runner: claims due effects from `genesis_chat.effects`, does\n * each one, and settles it. Settling with an error has `complete_effect` back\n * it off (30 s × 2^attempts); `claim_effects` gives up after the 8th attempt.\n *\n * push push_targets(message) → deliver(notification), once per message\n * transcribe signed URL (10 min) → transcriber → update_attachment\n * storage.remove storage.from(bucket).remove([path]); not found is done\n * video.remove video.remove(provider id), when a video provider is given\n *\n * Kinds it doesn't handle are never claimed, so an app's own runner can work\n * them. Effects run concurrently per kind, up to a cap; one failure never\n * stops the others. Errors are stored as short messages, never payloads.\n */\nimport { CHAT_BUCKET, CHAT_SCHEMA, type ChatSupabase } from \"../core/client\";\nimport type { ChatDeliver, ChatEffect, ChatEffectKind, ChatNotification, ChatRunReport, Transcriber, VideoProvider } from \"./types\";\n\n/** `claim_effects` stops claiming an effect after this many attempts. */\nexport const CHAT_MAX_ATTEMPTS = 8;\nconst ALL_KINDS: readonly ChatEffectKind[] = [\"push\", \"transcribe\", \"storage.remove\", \"video.remove\"];\nconst SIGNED_URL_SECONDS = 600;\n\nexport interface ChatRunnerOptions {\n /** A service-role supabase-js client (the runner reads past row level security). */\n supabase: ChatSupabase;\n /** Sends a message's push: `createExpoDeliver(...)`, or a function that calls genesis-notifications' `notify`. */\n deliver: ChatDeliver;\n /** Speech to text for voice notes and stored videos. Without one, their transcripts are set `off`. */\n transcriber?: Transcriber | undefined;\n /** The language to transcribe in (BCP-47). Default: detected. */\n transcriptLanguage?: string | undefined;\n /** Hosted video (Mux), to remove an asset when its attachment goes. */\n video?: Pick<VideoProvider, \"name\" | \"remove\"> | undefined;\n /** The kinds this runner works (default all four), so separate schedules can split them. */\n kinds?: readonly ChatEffectKind[] | undefined;\n batch?: {\n /** Effects claimed per run (default 50, at most 500). */\n limit?: number | undefined;\n /** Effects of one kind in flight at once (default 4). */\n concurrency?: number | undefined;\n };\n /** A person's display name, for push titles and group previews. */\n resolveName?: ((userId: string) => Promise<string | null | undefined> | string | null | undefined) | undefined;\n /** Kinds that are one-to-one (default `[\"direct\"]`): their push title is the sender's name. */\n directKinds?: readonly string[] | undefined;\n /** Where a tapped notification opens (default `/messages/<conversationId>`). */\n linkFor?: ((conversationId: string, messageId: string) => string) | undefined;\n /** Safe log lines (effect ids, kinds, short errors). Default: nothing is logged. */\n log?: ((line: string) => void) | undefined;\n}\n\nexport interface ChatRunner {\n /** One claim, its work and its settling. Rejects only when claiming fails. */\n run(): Promise<ChatRunReport>;\n /** Works one effect (exported for tests and for apps that claim themselves). Throws to retry. */\n handle(effect: ChatEffect): Promise<void>;\n}\n\ninterface PushTarget {\n user_id: string;\n conversation_id: string;\n title: string | null;\n sender_id: string | null;\n preview: string | null;\n mentioned: boolean | null;\n unread_total: number | null;\n}\n\ninterface AttachmentRow {\n id: string;\n kind: string;\n provider: string;\n path: string | null;\n mime: string | null;\n duration_ms: number | null;\n transcript_status: string | null;\n}\n\ntype Result<T> = { data: T | null; error: { message?: string; code?: string; statusCode?: string | number } | null };\n\nfunction errorText(fn: string, error: { message?: string; code?: string } | null | undefined): Error {\n // The code where there is one: a database message can quote the row it refused.\n return new Error(`${fn}: ${error?.code ?? error?.message ?? \"error\"}`);\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\n/** Runs `work` over `items`, at most `limit` at a time. */\nasync function pool<T>(items: readonly T[], limit: number, work: (item: T) => Promise<void>): Promise<void> {\n let next = 0;\n const lanes = Array.from({ length: Math.min(limit, items.length) }, async () => {\n while (next < items.length) await work(items[next++]!);\n });\n await Promise.all(lanes);\n}\n\nexport function createChatRunner(options: ChatRunnerOptions): ChatRunner {\n const { supabase, deliver } = options;\n const kinds = [...(options.kinds ?? ALL_KINDS)];\n const limit = Math.min(Math.max(options.batch?.limit ?? 50, 1), 500);\n const concurrency = Math.max(options.batch?.concurrency ?? 4, 1);\n const directKinds = new Set(options.directKinds ?? [\"direct\"]);\n const log = options.log ?? (() => {});\n const db = () => supabase.schema(CHAT_SCHEMA);\n\n async function rpc<T>(fn: string, args: Record<string, unknown>): Promise<T> {\n const { data, error } = (await db().rpc(fn, args)) as Result<T>;\n if (error) throw errorText(fn, error);\n return data as T;\n }\n\n async function nameOf(userId: string | null): Promise<string | null> {\n if (!userId || !options.resolveName) return null;\n try {\n return str(await options.resolveName(userId));\n } catch {\n return null;\n }\n }\n\n async function push(effect: ChatEffect): Promise<void> {\n const messageId = str(effect.payload.message_id);\n if (!messageId) return;\n const targets = (await rpc<PushTarget[] | null>(\"push_targets\", { p_message_id: messageId })) ?? [];\n // Deleted, silent, or nobody to tell: done.\n if (targets.length === 0) return;\n const first = targets[0]!;\n const conversationId = first.conversation_id;\n const { data: conversation, error } = (await db()\n .from(\"conversations\")\n .select(\"kind\")\n .eq(\"id\", conversationId)\n .maybeSingle()) as Result<{ kind: string }>;\n if (error) throw errorText(\"conversations\", error);\n const direct = conversation ? directKinds.has(conversation.kind) : false;\n // The message's number, so a tapped notification can open the thread at it.\n const { data: numbered } = (await db().from(\"messages\").select(\"seq\").eq(\"id\", messageId).maybeSingle()) as Result<{ seq: number }>;\n const seq = numbered?.seq ?? null;\n const senderName = await nameOf(first.sender_id);\n const preview = first.preview ?? \"New message\";\n // The conversation's title; a direct conversation (or an untitled group) shows who wrote.\n const title = str(first.title) ?? senderName ?? \"New message\";\n const body = !direct && senderName ? `${senderName}: ${preview}` : preview;\n const notification: ChatNotification = {\n category: \"messages\",\n messageId,\n conversationId,\n recipients: targets.map((t) => ({ userId: t.user_id, badge: t.unread_total ?? 0, mentioned: t.mentioned === true })),\n title,\n body,\n senderId: first.sender_id,\n collapseKey: `chat:${conversationId}`,\n threadKey: conversationId,\n idempotencyKey: `push:${messageId}`,\n data: { url: options.linkFor?.(conversationId, messageId) ?? `/messages/${conversationId}`, conversationId, messageId, seq },\n };\n await deliver(notification);\n }\n\n async function storageRemove(effect: ChatEffect): Promise<void> {\n const path = str(effect.payload.path);\n if (!path) return;\n const bucket = str(effect.payload.bucket) ?? CHAT_BUCKET;\n const { error } = (await supabase.storage.from(bucket).remove([path])) as Result<unknown>;\n if (!error) return;\n const status = String(error.statusCode ?? \"\");\n if (status === \"404\" || /not.?found/i.test(error.message ?? \"\")) return;\n throw new Error(`storage.remove: ${status || error.message || \"error\"}`);\n }\n\n async function transcribe(effect: ChatEffect): Promise<void> {\n const id = str(effect.payload.attachment_id);\n if (!id) return;\n const { data: row, error } = (await db()\n .from(\"attachments\")\n .select(\"id, kind, provider, path, mime, duration_ms, transcript_status\")\n .eq(\"id\", id)\n .maybeSingle()) as Result<AttachmentRow>;\n if (error) throw errorText(\"attachments\", error);\n // Deleted, or already settled: nothing to do.\n if (!row || (row.transcript_status !== \"pending\" && row.transcript_status !== null)) return;\n // A hosted video's captions come from its provider's webhook (Mux generated subtitles).\n if (row.provider !== \"storage\") return;\n if (!options.transcriber || !row.path) {\n await rpc(\"update_attachment\", { p_attachment_id: id, p_patch: { transcript_status: \"off\" } });\n return;\n }\n const signed = (await supabase.storage.from(CHAT_BUCKET).createSignedUrl(row.path, SIGNED_URL_SECONDS)) as Result<{ signedUrl: string }>;\n if (signed.error || !signed.data?.signedUrl) throw errorText(\"createSignedUrl\", signed.error);\n try {\n const result = await options.transcriber.transcribe({\n url: signed.data.signedUrl,\n mime: row.mime,\n durationMs: row.duration_ms,\n language: options.transcriptLanguage,\n });\n await rpc(\"update_attachment\", { p_attachment_id: id, p_patch: { transcript: result.text.slice(0, 100_000), transcript_status: \"ready\" } });\n } catch (e) {\n // The last attempt: say so, so the player stops waiting for it.\n if (effect.attempts >= CHAT_MAX_ATTEMPTS) {\n await rpc(\"update_attachment\", { p_attachment_id: id, p_patch: { transcript_status: \"failed\" } }).catch(() => {});\n }\n throw e;\n }\n }\n\n async function videoRemove(effect: ChatEffect): Promise<void> {\n const providerId = str(effect.payload.provider_id);\n if (!providerId || !options.video) return;\n const provider = str(effect.payload.provider);\n if (provider && provider !== options.video.name) {\n throw new Error(`video.remove: no adapter for ${provider} (this runner has ${options.video.name})`);\n }\n await options.video.remove(providerId);\n }\n\n async function handle(effect: ChatEffect): Promise<void> {\n switch (effect.kind) {\n case \"push\":\n return push(effect);\n case \"transcribe\":\n return transcribe(effect);\n case \"storage.remove\":\n return storageRemove(effect);\n case \"video.remove\":\n return videoRemove(effect);\n default:\n throw new Error(`no handler for ${effect.kind}`);\n }\n }\n\n async function run(): Promise<ChatRunReport> {\n const claimed = (await rpc<ChatEffect[] | null>(\"claim_effects\", { p_kinds: kinds, p_limit: limit })) ?? [];\n const report: ChatRunReport = { claimed: claimed.length, done: 0, failed: 0 };\n const byKind = new Map<string, ChatEffect[]>();\n for (const effect of claimed) byKind.set(effect.kind, [...(byKind.get(effect.kind) ?? []), effect]);\n await Promise.all(\n [...byKind.values()].map((effects) =>\n pool(effects, concurrency, async (effect) => {\n let failure: string | null = null;\n try {\n await handle({ ...effect, payload: effect.payload ?? {} });\n } catch (e) {\n failure = (e instanceof Error ? e.message : String(e)).slice(0, 500) || \"error\";\n }\n try {\n await rpc(\"complete_effect\", { p_id: effect.id, p_error: failure });\n } catch (e) {\n // Unsettled: its five-minute claim runs out and it is claimed again.\n log(`genesis-chat: settling effect ${effect.id} failed: ${e instanceof Error ? e.message : \"error\"}`);\n failure ??= \"unsettled\";\n }\n if (failure) {\n report.failed++;\n log(`genesis-chat: ${effect.kind} effect ${effect.id} (attempt ${effect.attempts}) failed: ${failure}`);\n } else report.done++;\n }),\n ),\n );\n return report;\n }\n\n return { run, handle };\n}\n","// @feat:chat\n/**\n * Speech-to-text providers behind `Transcriber`. A failed call throws with\n * the HTTP status only (an error body can echo the request), so the runner\n * retries it.\n */\nimport type { TranscribeInput, TranscribeResult, Transcriber } from \"./types\";\n\nconst TIMEOUT_MS = 120_000;\n\nasync function failed(name: string, response: Response): Promise<never> {\n await response.body?.cancel().catch(() => {});\n throw new Error(`${name} answered HTTP ${response.status}`);\n}\n\nexport interface DeepgramOptions {\n apiKey: string;\n /** Default `nova-3`. */\n model?: string | undefined;\n fetch?: typeof fetch | undefined;\n}\n\n/**\n * Deepgram pre-recorded transcription by URL\n * (https://developers.deepgram.com/reference/speech-to-text/listen-pre-recorded):\n * `POST /v1/listen?model=…&smart_format=true&detect_language=true` with\n * `{ url }` and `Authorization: Token <key>`; Deepgram fetches the file itself.\n */\nexport function deepgramTranscriber(options: DeepgramOptions): Transcriber {\n const fetchFn = options.fetch ?? fetch;\n const model = options.model ?? \"nova-3\";\n return {\n name: \"deepgram\",\n async transcribe(input: TranscribeInput): Promise<TranscribeResult> {\n const params = new URLSearchParams({ model, smart_format: \"true\" });\n if (input.language) params.set(\"language\", input.language);\n else params.set(\"detect_language\", \"true\");\n const response = await fetchFn(`https://api.deepgram.com/v1/listen?${params}`, {\n method: \"POST\",\n headers: { authorization: `Token ${options.apiKey}`, \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify({ url: input.url }),\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n if (!response.ok) return failed(\"Deepgram\", response);\n const json = (await response.json()) as {\n results?: { channels?: { detected_language?: string; alternatives?: { transcript?: string }[] }[] };\n };\n const channel = json.results?.channels?.[0];\n const text = channel?.alternatives?.[0]?.transcript;\n if (typeof text !== \"string\") throw new Error(\"Deepgram answered without a transcript\");\n return { text: text.trim(), language: channel?.detected_language ?? input.language };\n },\n };\n}\n\nexport interface OpenAiTranscriberOptions {\n apiKey: string;\n /** Default `gpt-4o-mini-transcribe`. */\n model?: string | undefined;\n fetch?: typeof fetch | undefined;\n}\n\nconst EXTENSIONS: Record<string, string> = {\n \"audio/mp4\": \"m4a\",\n \"audio/x-m4a\": \"m4a\",\n \"audio/aac\": \"m4a\",\n \"audio/mpeg\": \"mp3\",\n \"audio/webm\": \"webm\",\n \"audio/ogg\": \"ogg\",\n \"audio/wav\": \"wav\",\n \"video/mp4\": \"mp4\",\n \"video/webm\": \"webm\",\n};\n\n/** OpenAI's limit for one upload. */\nconst OPENAI_MAX_BYTES = 25 * 1024 * 1024;\n\n/**\n * OpenAI `POST /v1/audio/transcriptions` (multipart: file, model, language):\n * the file is downloaded from the signed URL first, as OpenAI takes no URL.\n * Files over 25 MB are refused by OpenAI, so they fail here without the upload.\n */\nexport function openAiTranscriber(options: OpenAiTranscriberOptions): Transcriber {\n const fetchFn = options.fetch ?? fetch;\n const model = options.model ?? \"gpt-4o-mini-transcribe\";\n return {\n name: \"openai\",\n async transcribe(input: TranscribeInput): Promise<TranscribeResult> {\n const file = await fetchFn(input.url, { signal: AbortSignal.timeout(TIMEOUT_MS) });\n if (!file.ok) return failed(\"Storage\", file);\n const blob = await file.blob();\n if (blob.size > OPENAI_MAX_BYTES) throw new Error(\"OpenAI transcription: file over 25 MB\");\n const mime = input.mime ?? (blob.type || \"audio/mp4\");\n const form = new FormData();\n form.set(\"file\", new Blob([blob], { type: mime }), `audio.${EXTENSIONS[mime] ?? \"m4a\"}`);\n form.set(\"model\", model);\n form.set(\"response_format\", \"json\");\n if (input.language) form.set(\"language\", input.language);\n const response = await fetchFn(\"https://api.openai.com/v1/audio/transcriptions\", {\n method: \"POST\",\n headers: { authorization: `Bearer ${options.apiKey}` },\n body: form,\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n if (!response.ok) return failed(\"OpenAI\", response);\n const json = (await response.json()) as { text?: string };\n if (typeof json.text !== \"string\") throw new Error(\"OpenAI answered without a transcript\");\n return { text: json.text.trim(), language: input.language };\n },\n };\n}\n","// @feat:chat\n/** Web Crypto helpers: constant-time compares, HMAC, base64url and RSA keys. */\n\nconst encoder = new TextEncoder();\n\nasync function sha256(value: string): Promise<Uint8Array> {\n return new Uint8Array(await crypto.subtle.digest(\"SHA-256\", encoder.encode(value)));\n}\n\n/** Equal strings, compared in constant time over their digests (so the length doesn't leak either). */\nexport async function safeEqual(a: string, b: string): Promise<boolean> {\n const [x, y] = await Promise.all([sha256(a), sha256(b)]);\n let diff = 0;\n for (let i = 0; i < x.length; i++) diff |= x[i]! ^ y[i]!;\n return diff === 0;\n}\n\n/** HMAC-SHA256 of `message` under `secret`, as lowercase hex. */\nexport async function hmacSha256Hex(secret: string, message: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", encoder.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n const mac = new Uint8Array(await crypto.subtle.sign(\"HMAC\", key, encoder.encode(message)));\n return Array.from(mac, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function base64ToBytes(value: string): Uint8Array {\n const binary = atob(value.replace(/\\s+/g, \"\"));\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n\nexport function base64Url(bytes: Uint8Array | string): string {\n const b = typeof bytes === \"string\" ? encoder.encode(bytes) : bytes;\n let binary = \"\";\n for (const byte of b) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nfunction derLength(n: number): number[] {\n if (n < 0x80) return [n];\n const bytes: number[] = [];\n for (let v = n; v > 0; v >>= 8) bytes.unshift(v & 0xff);\n return [0x80 | bytes.length, ...bytes];\n}\n\nfunction der(tag: number, body: Uint8Array): Uint8Array {\n const len = derLength(body.length);\n const out = new Uint8Array(1 + len.length + body.length);\n out[0] = tag;\n out.set(len, 1);\n out.set(body, 1 + len.length);\n return out;\n}\n\n/** Wraps a PKCS#1 RSA private key (`BEGIN RSA PRIVATE KEY`) as PKCS#8, which Web Crypto imports. */\nfunction pkcs1ToPkcs8(pkcs1: Uint8Array): Uint8Array {\n // SEQUENCE { INTEGER 0, SEQUENCE { OID 1.2.840.113549.1.1.1, NULL }, OCTET STRING { pkcs1 } }\n const version = new Uint8Array([0x02, 0x01, 0x00]);\n const algorithm = new Uint8Array([0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]);\n const octets = der(0x04, pkcs1);\n const body = new Uint8Array(version.length + algorithm.length + octets.length);\n body.set(version, 0);\n body.set(algorithm, version.length);\n body.set(octets, version.length + algorithm.length);\n return der(0x30, body);\n}\n\n/**\n * Imports an RSA private key for RS256 from a PEM (PKCS#1 or PKCS#8), or from\n * the base64 of a PEM as Mux hands out signing keys.\n */\nexport async function importRsaPrivateKey(key: string): Promise<CryptoKey> {\n const pem = key.includes(\"-----BEGIN\") ? key : new TextDecoder().decode(base64ToBytes(key));\n const match = /-----BEGIN (RSA )?PRIVATE KEY-----([\\s\\S]+?)-----END (RSA )?PRIVATE KEY-----/.exec(pem);\n if (!match) throw new Error(\"importRsaPrivateKey: not a PEM private key\");\n const bytes = base64ToBytes(match[2]!);\n const pkcs8 = match[1] ? pkcs1ToPkcs8(bytes) : bytes;\n return crypto.subtle.importKey(\"pkcs8\", pkcs8 as BufferSource, { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" }, false, [\"sign\"]);\n}\n\n/** Signs a JWT with RS256. */\nexport async function signJwtRs256(header: Record<string, unknown>, claims: Record<string, unknown>, key: CryptoKey): Promise<string> {\n const input = `${base64Url(JSON.stringify({ alg: \"RS256\", typ: \"JWT\", ...header }))}.${base64Url(JSON.stringify(claims))}`;\n const signature = new Uint8Array(await crypto.subtle.sign(\"RSASSA-PKCS1-v1_5\", key, encoder.encode(input)));\n return `${input}.${base64Url(signature)}`;\n}\n","// @feat:chat\n/**\n * Mux Video behind `VideoProvider` (https://www.mux.com/docs):\n *\n * createUpload POST /video/v1/uploads: a direct upload, its asset with English\n * auto-generated captions and the attachment's id as passthrough\n * remove DELETE /video/v1/assets/{id}; 404 counts as removed\n * handleWebhook verifies `mux-signature` (HMAC-SHA256 of `t.body`, 5 minutes'\n * tolerance) and applies:\n * video.asset.ready status ready, provider_id = asset id,\n * playback_id, duration_ms\n * video.asset.errored status failed\n * video.asset.track.ready a generated text track: its plain\n * text becomes the transcript\n * video.asset.track.errored a generated text track: transcript failed\n *\n * The attachment is found by passthrough (its id) or by provider_id (the upload\n * id the client sent with it, or the asset id once ready). An event for an\n * attachment not there yet answers 404, so Mux retries it.\n */\nimport { CHAT_SCHEMA, type ChatSupabase } from \"../core/client\";\nimport { hmacSha256Hex, importRsaPrivateKey, safeEqual, signJwtRs256 } from \"./crypto\";\nimport type { VideoProvider } from \"./types\";\n\nconst API = \"https://api.mux.com/video/v1\";\nconst TOLERANCE_SECONDS = 300;\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport interface MuxSigningKey {\n /** The signing key's id (`kid`). */\n signingKeyId: string;\n /** The private key as Mux gives it: base64 of a PEM. A PEM works too. */\n privateKey: string;\n}\n\nexport interface MuxVideoOptions {\n tokenId: string;\n tokenSecret: string;\n /** The webhook endpoint's signing secret (Settings → Webhooks). */\n webhookSecret: string;\n /** `public` (default) or `signed`; signed playback needs `signing` to build URLs. */\n playbackPolicy?: \"public\" | \"signed\" | undefined;\n /** Mux's auto-generated captions, kept as the transcript (default English; false turns them off). */\n captions?: { languageCode?: string; name?: string } | false | undefined;\n /** For signed playback: fetching generated captions as the transcript. */\n signing?: MuxSigningKey | undefined;\n /** Mux `video_quality` (default `basic`). */\n videoQuality?: \"basic\" | \"plus\" | \"premium\" | undefined;\n fetch?: typeof fetch | undefined;\n now?: (() => number) | undefined;\n}\n\n/** Verifies a `mux-signature` header (`t=<unix>,v1=<hex>`) over the raw body. */\nexport async function verifyMuxSignature(\n header: string | null,\n body: string,\n secret: string,\n options: { now?: number; toleranceSeconds?: number } = {},\n): Promise<boolean> {\n if (!header || !secret) return false;\n let timestamp: string | null = null;\n const signatures: string[] = [];\n for (const part of header.split(\",\")) {\n const [k, v] = part.split(\"=\", 2).map((s) => s.trim());\n if (k === \"t\" && v) timestamp = v;\n else if (k === \"v1\" && v) signatures.push(v.toLowerCase());\n }\n if (!timestamp || !/^\\d{1,12}$/.test(timestamp) || signatures.length === 0) return false;\n const now = Math.floor((options.now ?? Date.now()) / 1000);\n if (Math.abs(now - Number(timestamp)) > (options.toleranceSeconds ?? TOLERANCE_SECONDS)) return false;\n const expected = await hmacSha256Hex(secret, `${timestamp}.${body}`);\n let ok = false;\n for (const signature of signatures) if (await safeEqual(signature, expected)) ok = true;\n return ok;\n}\n\n/**\n * A signed playback URL (RS256 JWT with `sub` = playback id, `aud` = v, `kid`\n * in the header), valid for `expiresInSeconds` (default 6 hours).\n */\nexport async function muxPlaybackUrl(\n playbackId: string,\n key: MuxSigningKey,\n options: { expiresInSeconds?: number; now?: number; path?: string } = {},\n): Promise<string> {\n const token = await muxPlaybackToken(playbackId, key, options);\n return `https://stream.mux.com/${playbackId}${options.path ?? \".m3u8\"}?token=${token}`;\n}\n\nexport async function muxPlaybackToken(\n playbackId: string,\n key: MuxSigningKey,\n options: { expiresInSeconds?: number; now?: number; audience?: \"v\" | \"t\" | \"g\" | \"s\" } = {},\n): Promise<string> {\n const now = Math.floor((options.now ?? Date.now()) / 1000);\n const privateKey = await importRsaPrivateKey(key.privateKey);\n return signJwtRs256(\n { kid: key.signingKeyId },\n { sub: playbackId, aud: options.audience ?? \"v\", exp: now + (options.expiresInSeconds ?? 6 * 3600) },\n privateKey,\n );\n}\n\ninterface MuxEvent {\n type?: string;\n data?: {\n id?: string;\n asset_id?: string;\n upload_id?: string;\n passthrough?: string;\n duration?: number;\n playback_ids?: { id?: string; policy?: string }[];\n type?: string;\n text_source?: string;\n };\n}\n\nconst json = (status: number, body: unknown) =>\n new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\" } });\n\nexport function muxVideo(options: MuxVideoOptions): VideoProvider & {\n playbackUrl(playbackId: string, opts?: { expiresInSeconds?: number }): Promise<string>;\n} {\n const fetchFn = options.fetch ?? fetch;\n const policy = options.playbackPolicy ?? \"public\";\n const captions = options.captions === false ? null : { languageCode: options.captions?.languageCode ?? \"en\", name: options.captions?.name ?? \"English (auto)\" };\n const auth = `Basic ${btoa(`${options.tokenId}:${options.tokenSecret}`)}`;\n\n async function api(method: string, path: string, body?: unknown): Promise<Response> {\n return fetchFn(`${API}${path}`, {\n method,\n headers: { authorization: auth, accept: \"application/json\", ...(body ? { \"content-type\": \"application/json\" } : {}) },\n ...(body ? { body: JSON.stringify(body) } : {}),\n signal: AbortSignal.timeout(15_000),\n });\n }\n\n async function update(supabase: ChatSupabase, id: string, patch: Record<string, unknown>): Promise<void> {\n const { error } = await supabase.schema(CHAT_SCHEMA).rpc(\"update_attachment\", { p_attachment_id: id, p_patch: patch });\n if (error) throw new Error(`update_attachment: ${error.code ?? \"error\"}`);\n }\n\n /** The attachment an event is about, by passthrough (its id) or provider_id. */\n async function find(supabase: ChatSupabase, ids: { passthrough?: string | undefined; providerIds: (string | undefined)[] }) {\n const table = () => supabase.schema(CHAT_SCHEMA).from(\"attachments\").select(\"id, playback_id, transcript_status\").eq(\"provider\", \"mux\");\n if (ids.passthrough && UUID.test(ids.passthrough)) {\n const { data, error } = await table().eq(\"id\", ids.passthrough).maybeSingle();\n if (error) throw new Error(`attachments: ${error.code ?? \"error\"}`);\n if (data) return data as { id: string; playback_id: string | null; transcript_status: string | null };\n }\n for (const providerId of ids.providerIds) {\n if (!providerId) continue;\n const { data, error } = await table().eq(\"provider_id\", providerId).limit(1).maybeSingle();\n if (error) throw new Error(`attachments: ${error.code ?? \"error\"}`);\n if (data) return data as { id: string; playback_id: string | null; transcript_status: string | null };\n }\n return null;\n }\n\n async function transcriptOf(playbackId: string, trackId: string): Promise<string | null> {\n let url = `https://stream.mux.com/${playbackId}/text/${trackId}.txt`;\n if (policy === \"signed\") {\n if (!options.signing) return null;\n url += `?token=${await muxPlaybackToken(playbackId, options.signing, { expiresInSeconds: 600 })}`;\n }\n const response = await fetchFn(url, { signal: AbortSignal.timeout(15_000) });\n if (!response.ok) {\n await response.body?.cancel().catch(() => {});\n throw new Error(`Mux text track answered HTTP ${response.status}`);\n }\n return (await response.text()).trim();\n }\n\n return {\n name: \"mux\",\n\n async createUpload({ corsOrigin, passthrough }) {\n const response = await api(\"POST\", \"/uploads\", {\n cors_origin: corsOrigin,\n new_asset_settings: {\n playback_policies: [policy],\n video_quality: options.videoQuality ?? \"basic\",\n ...(passthrough ? { passthrough: passthrough.slice(0, 255) } : {}),\n ...(captions ? { inputs: [{ generated_subtitles: [{ language_code: captions.languageCode, name: captions.name }] }] } : {}),\n },\n });\n if (!response.ok) {\n await response.body?.cancel().catch(() => {});\n throw new Error(`Mux uploads answered HTTP ${response.status}`);\n }\n const body = (await response.json()) as { data?: { id?: string; url?: string } };\n if (!body.data?.id || !body.data.url) throw new Error(\"Mux uploads answered without an upload\");\n return { uploadId: body.data.id, url: body.data.url };\n },\n\n async remove(assetId) {\n const response = await api(\"DELETE\", `/assets/${encodeURIComponent(assetId)}`);\n await response.body?.cancel().catch(() => {});\n if (response.ok || response.status === 404) return;\n throw new Error(`Mux delete asset answered HTTP ${response.status}`);\n },\n\n playbackUrl(playbackId, opts) {\n if (policy !== \"signed\") return Promise.resolve(`https://stream.mux.com/${playbackId}.m3u8`);\n if (!options.signing) return Promise.reject(new Error(\"muxVideo: signed playback needs `signing`\"));\n return muxPlaybackUrl(playbackId, options.signing, opts);\n },\n\n async handleWebhook(request, { supabase }) {\n if (request.method !== \"POST\") return json(405, { error: \"method not allowed\" });\n const body = await request.text();\n const ok = await verifyMuxSignature(request.headers.get(\"mux-signature\"), body, options.webhookSecret, {\n ...(options.now ? { now: options.now() } : {}),\n });\n if (!ok) return json(401, { error: \"bad signature\" });\n let event: MuxEvent;\n try {\n event = JSON.parse(body) as MuxEvent;\n } catch {\n return json(400, { error: \"bad json\" });\n }\n const data = event.data ?? {};\n switch (event.type) {\n case \"video.asset.ready\":\n case \"video.asset.errored\": {\n const attachment = await find(supabase, { passthrough: data.passthrough, providerIds: [data.upload_id, data.id] });\n if (!attachment) return json(404, { error: \"attachment not found\" });\n if (event.type === \"video.asset.errored\") {\n await update(supabase, attachment.id, { status: \"failed\", ...(data.id ? { provider_id: data.id } : {}) });\n } else {\n const playback = data.playback_ids?.find((p) => p.policy === policy) ?? data.playback_ids?.[0];\n await update(supabase, attachment.id, {\n status: \"ready\",\n ...(data.id ? { provider_id: data.id } : {}),\n ...(playback?.id ? { playback_id: playback.id } : {}),\n ...(typeof data.duration === \"number\" ? { duration_ms: Math.round(data.duration * 1000) } : {}),\n // No captions asked for: nothing will fill the transcript.\n ...(!captions ? { transcript_status: \"off\" } : {}),\n });\n }\n return json(200, { ok: true });\n }\n case \"video.asset.track.ready\":\n case \"video.asset.track.errored\": {\n if (data.type !== \"text\" || data.text_source !== \"generated_vod\") return json(200, { ignored: true });\n const attachment = await find(supabase, { providerIds: [data.asset_id] });\n if (!attachment) return json(404, { error: \"attachment not found\" });\n if (event.type === \"video.asset.track.errored\") {\n await update(supabase, attachment.id, { transcript_status: \"failed\" });\n return json(200, { ok: true });\n }\n // The asset's ready event comes first; until it has, Mux retries this one.\n if (!attachment.playback_id || !data.id) return json(404, { error: \"no playback id yet\" });\n const text = await transcriptOf(attachment.playback_id, data.id);\n if (text === null) return json(200, { ignored: \"signed playback without a signing key\" });\n await update(supabase, attachment.id, { transcript: text.slice(0, 100_000), transcript_status: \"ready\" });\n return json(200, { ok: true });\n }\n default:\n return json(200, { ignored: true });\n }\n },\n };\n}\n","// @feat:chat\n/**\n * A plain `deliver` for apps without genesis-notifications yet: one Expo push\n * per device token, in requests of 100 (https://docs.expo.dev/push-notifications/sending-notifications/).\n * It returns every ticket, so the app can forget tokens Expo calls\n * `DeviceNotRegistered`. It throws only when nothing at all was sent, so a\n * retry never pushes twice to the devices that already got it.\n */\nimport type { ChatNotification } from \"./types\";\n\nexport const EXPO_SEND_URL = \"https://exp.host/--/api/v2/push/send\";\nexport const EXPO_BATCH = 100;\n\nexport interface ExpoPushMessage {\n to: string;\n title: string;\n body: string;\n data: Record<string, unknown>;\n sound: \"default\" | null;\n badge: number;\n threadId: string;\n collapseId?: string;\n channelId: string;\n categoryId: string;\n}\n\nexport type ExpoPushTicket = { status: \"ok\"; id: string } | { status: \"error\"; message?: string; details?: { error?: string } };\n\nexport interface ExpoDeliveryTicket {\n userId: string;\n token: string;\n ticket: ExpoPushTicket;\n}\n\nexport interface ExpoDeliverOptions {\n /** The Expo push tokens of these people (any number per person). */\n tokensFor: (userIds: string[]) => Promise<{ userId: string; token: string }[]>;\n fetch?: typeof fetch | undefined;\n /** Required when the Expo project has enhanced push security on. */\n accessToken?: string | undefined;\n /** Default `default`; null for silent. */\n sound?: \"default\" | null | undefined;\n /** Android channel (default `messages`) and notification category (default `messages`, the category genesis-native's push block registers for chat). */\n channelId?: string | undefined;\n categoryId?: string | undefined;\n /**\n * Replace a conversation's earlier notification with the newest (one per conversation).\n * Off by default: like iMessage and WhatsApp, each message shows, grouped by thread.\n */\n collapse?: boolean | undefined;\n}\n\nexport function createExpoDeliver(options: ExpoDeliverOptions): (n: ChatNotification) => Promise<ExpoDeliveryTicket[]> {\n const fetchFn = options.fetch ?? fetch;\n return async (notification) => {\n const badges = new Map(notification.recipients.map((r) => [r.userId, r.badge]));\n const devices = await options.tokensFor([...badges.keys()]);\n const messages = devices.map(\n (d): ExpoPushMessage & { userId: string } => ({\n userId: d.userId,\n to: d.token,\n title: notification.title,\n body: notification.body,\n data: notification.data,\n sound: options.sound === undefined ? \"default\" : options.sound,\n badge: badges.get(d.userId) ?? 0,\n threadId: notification.threadKey,\n ...(options.collapse ? { collapseId: notification.collapseKey } : {}),\n channelId: options.channelId ?? \"messages\",\n categoryId: options.categoryId ?? \"messages\",\n }),\n );\n const tickets: ExpoDeliveryTicket[] = [];\n let sent = 0;\n let lastError: unknown = null;\n for (let i = 0; i < messages.length; i += EXPO_BATCH) {\n const chunk = messages.slice(i, i + EXPO_BATCH);\n let answer: ExpoPushTicket[] | null = null;\n try {\n const response = await fetchFn(EXPO_SEND_URL, {\n method: \"POST\",\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n ...(options.accessToken ? { authorization: `Bearer ${options.accessToken}` } : {}),\n },\n body: JSON.stringify(chunk.map(({ userId: _userId, ...m }) => m)),\n signal: AbortSignal.timeout(15_000),\n });\n if (!response.ok) {\n await response.body?.cancel().catch(() => {});\n throw new Error(`Expo push answered HTTP ${response.status}`);\n }\n const json = (await response.json()) as { data?: unknown };\n answer = Array.isArray(json.data) ? (json.data as ExpoPushTicket[]) : [];\n sent += chunk.length;\n } catch (e) {\n lastError = e;\n }\n chunk.forEach((m, j) => {\n const t = answer?.[j];\n tickets.push({\n userId: m.userId,\n token: m.to,\n ticket: t && (t.status === \"ok\" || t.status === \"error\") ? t : { status: \"error\", details: { error: answer ? \"NoTicket\" : \"RequestFailed\" } },\n });\n });\n }\n if (messages.length > 0 && sent === 0) throw lastError instanceof Error ? lastError : new Error(\"Expo push failed\");\n return tickets;\n };\n}\n\n/** The tokens Expo says are gone (`DeviceNotRegistered`): forget them. */\nexport function deadTokens(tickets: readonly ExpoDeliveryTicket[]): string[] {\n return tickets.filter((t) => t.ticket.status === \"error\" && t.ticket.details?.error === \"DeviceNotRegistered\").map((t) => t.token);\n}\n","// @feat:chat\n/**\n * A fetch handler for a cron job or a database webhook: requires\n * `Authorization: Bearer <secret>` (compared in constant time), runs the\n * runner once and answers its counts as JSON.\n */\nimport { safeEqual } from \"./crypto\";\nimport type { ChatRunner } from \"./runner\";\n\nexport interface ChatRunnerHandlerOptions {\n runner: Pick<ChatRunner, \"run\">;\n /** At least 32 characters. */\n secret: string;\n}\n\nconst json = (status: number, body: unknown) =>\n new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\", \"cache-control\": \"no-store\" } });\n\nexport function chatRunnerHandler(options: ChatRunnerHandlerOptions): (request: Request) => Promise<Response> {\n return async (request) => {\n // Misconfigured: refuse everyone rather than accept a guessable secret.\n if (!options.secret || options.secret.length < 32) return json(500, { error: \"runner secret missing or under 32 characters\" });\n if (request.method !== \"POST\" && request.method !== \"GET\") return json(405, { error: \"method not allowed\" });\n const match = /^Bearer\\s+(\\S+)$/i.exec(request.headers.get(\"authorization\") ?? \"\");\n if (!match || !(await safeEqual(match[1]!, options.secret))) return json(401, { error: \"unauthorized\" });\n try {\n return json(200, await options.runner.run());\n } catch (e) {\n return json(500, { error: e instanceof Error ? e.message.slice(0, 200) : \"run failed\" });\n }\n };\n}\n"]}
package/dist/web.d.ts ADDED
@@ -0,0 +1,387 @@
1
+ import * as react from 'react';
2
+ import { ComponentType, ReactNode, Ref } from 'react';
3
+ import { SlotOverride } from '@marktiderman/genesis-core/slots';
4
+ import { e as ChatMessage, h as ChatInboxItem, j as ChatAttachment, a as ChatConversation, b as ChatMember, c as ChatRole, C as ChatClient, f as ChatActivity } from './client-BtdALFCx.js';
5
+ import { ConversationState } from './react.js';
6
+ import { ThreadItem, LocalFile } from './index.js';
7
+ import '@supabase/supabase-js';
8
+ import '@tanstack/query-core';
9
+
10
+ /** A person as the app names them. The package knows no people tables: the app supplies this. */
11
+ interface ChatUser {
12
+ name: string;
13
+ avatarUrl?: string | null;
14
+ }
15
+ type ResolveUser = (userId: string) => ChatUser;
16
+ /** 10:42 (in the reader's locale). */
17
+ declare function clockTime(iso: string, locale?: string): string;
18
+ /** The inbox's time: "now", "5m", "10:42" today, "Yesterday", "Mon", or "12 Mar". */
19
+ declare function relativeTime(iso: string | null | undefined, now?: Date, locale?: string): string;
20
+ /** 12 KB, 3.4 MB. */
21
+ declare function formatBytes(bytes: number | null | undefined): string;
22
+ /** What a screen reader hears for one bubble: "Sam, 10:42: See you then". */
23
+ declare function messageLabel(message: Pick<ChatMessage, "body" | "createdAt" | "deletedAt" | "attachments" | "editedAt" | "visibility">, sender: string, locale?: string): string;
24
+
25
+ interface ChatInboxEmptyProps {
26
+ archived: boolean;
27
+ }
28
+ interface ChatInboxProps {
29
+ /** Names people (the app knows them; the package doesn't). */
30
+ resolveUser: ResolveUser;
31
+ /** Called with a conversation's id when its row is chosen. */
32
+ onSelect: (conversationId: string) => void;
33
+ /** The open conversation, marked as current. */
34
+ selectedId?: string | null;
35
+ /** Only this kind of conversation. */
36
+ kind?: string;
37
+ /** The archived conversations instead. */
38
+ archived?: boolean;
39
+ /** A conversation's title when it has none and isn't two-person. */
40
+ fallbackTitle?: (item: ChatInboxItem) => string;
41
+ /** Replace or eject (`null`) the empty state. */
42
+ slots?: {
43
+ empty?: SlotOverride<ChatInboxEmptyProps>;
44
+ };
45
+ "aria-label"?: string;
46
+ className?: string;
47
+ }
48
+ /** The title an inbox row shows. */
49
+ declare function inboxTitle(item: ChatInboxItem, resolveUser: ResolveUser, fallback?: (item: ChatInboxItem) => string): string;
50
+ declare function ChatInbox({ resolveUser, onSelect, selectedId, kind, archived, fallbackTitle, slots, className, ...rest }: ChatInboxProps): react.JSX.Element;
51
+
52
+ /** One attachment as the renderers draw it, sent or still uploading. */
53
+ interface AttachmentView {
54
+ key: string;
55
+ kind: string;
56
+ /** Signed URL, or the local preview while uploading. */
57
+ url: string | null;
58
+ posterUrl: string | null;
59
+ name: string | null;
60
+ mime: string | null;
61
+ bytes: number | null;
62
+ width: number | null;
63
+ height: number | null;
64
+ durationMs: number | null;
65
+ waveform: number[] | null;
66
+ transcript: string | null;
67
+ transcriptStatus: ChatAttachment["transcriptStatus"];
68
+ /** Still uploading (or the server says it is still processing). */
69
+ pending: boolean;
70
+ /** The stored attachment; absent while it uploads. */
71
+ attachment?: ChatAttachment;
72
+ }
73
+ /** Props the `attachment` slot receives, for kinds the package doesn't draw (form, lesson…). */
74
+ interface ChatAttachmentSlotProps {
75
+ attachment: ChatAttachment;
76
+ message: ChatMessage;
77
+ /** Its signed URL when it has a file. */
78
+ url: string | null;
79
+ mine: boolean;
80
+ }
81
+ declare function toAttachmentView(a: ChatAttachment, urls: Record<string, string>): AttachmentView;
82
+ /** Generic card for an app's own kind, when no `attachment` slot is given. */
83
+ declare function GenericAttachment({ attachment, url }: ChatAttachmentSlotProps): react.JSX.Element;
84
+ interface ImageGridProps {
85
+ images: AttachmentView[];
86
+ className?: string;
87
+ }
88
+ /** Up to four photos in a grid (a "+N" on the fourth for more), opening a lightbox. */
89
+ declare function ImageGrid({ images, className }: ImageGridProps): react.JSX.Element;
90
+ interface LightboxProps {
91
+ images: AttachmentView[];
92
+ /** The photo shown; null closes it. */
93
+ index: number | null;
94
+ onIndexChange: (index: number | null) => void;
95
+ }
96
+ /** A photo full size, with the previous and next ones on the arrow keys. */
97
+ declare function Lightbox({ images, index, onIndexChange }: LightboxProps): react.JSX.Element;
98
+ declare function VideoAttachment({ view }: {
99
+ view: AttachmentView;
100
+ }): react.JSX.Element;
101
+ declare function FileChip({ view, tone }: {
102
+ view: AttachmentView;
103
+ tone?: "default" | "primary";
104
+ }): react.JSX.Element;
105
+ interface MessageAttachmentsProps {
106
+ views: AttachmentView[];
107
+ message?: ChatMessage;
108
+ mine: boolean;
109
+ tone: "default" | "primary";
110
+ /** For kinds the package doesn't draw; a generic card by default. */
111
+ slot?: ComponentType<ChatAttachmentSlotProps> | null;
112
+ }
113
+ /** Every attachment of one message, photos grouped into one grid. */
114
+ declare function MessageAttachments({ views, message, mine, tone, slot }: MessageAttachmentsProps): react.JSX.Element | null;
115
+
116
+ interface ChatThreadHeaderProps {
117
+ conversationId: string;
118
+ conversation: ChatConversation | null;
119
+ /** The title the thread worked out (the conversation's, or the other person's name). */
120
+ title: string;
121
+ /** How many people are in it, for a group. */
122
+ subtitle: string | null;
123
+ /** "Sam is typing…" while someone is. */
124
+ activity: string | null;
125
+ /** The other person in a two-person conversation. */
126
+ otherUserId: string | null;
127
+ members: ChatMember[];
128
+ role: ChatRole | null;
129
+ resolveUser: ResolveUser;
130
+ onBack?: () => void;
131
+ backClassName?: string;
132
+ }
133
+ interface ChatThreadEmptyProps {
134
+ conversationId: string;
135
+ canPost: boolean;
136
+ }
137
+ interface ChatComposerAccessoryProps {
138
+ conversationId: string;
139
+ insertText(text: string): void;
140
+ addFiles(files: File[]): void;
141
+ }
142
+ interface ChatThreadSlots {
143
+ /** The bar above the messages. */
144
+ header?: SlotOverride<ChatThreadHeaderProps>;
145
+ /** What a conversation with no messages shows. */
146
+ empty?: SlotOverride<ChatThreadEmptyProps>;
147
+ /** Draws the app's own attachment kinds (form, lesson…); a generic card by default, `null` hides them. */
148
+ attachment?: ComponentType<ChatAttachmentSlotProps> | null;
149
+ /** Extra controls in the composer, beside the attach button. */
150
+ composerAccessory?: SlotOverride<ChatComposerAccessoryProps>;
151
+ }
152
+ interface ChatThreadProps {
153
+ conversationId: string;
154
+ /** Names people. Required: the package knows no people tables, the app does. */
155
+ resolveUser: ResolveUser;
156
+ /** The header's title; worked out from the conversation and its members by default. */
157
+ title?: string;
158
+ /** Shows a back button in the header. */
159
+ onBack?: () => void;
160
+ /** Classes for that back button (ChatPanel hides it on wide screens). */
161
+ backClassName?: string;
162
+ /** One thread's replies instead of the main timeline. */
163
+ threadRootId?: string;
164
+ /** One topic's messages. */
165
+ topic?: string;
166
+ /** Shown on messages that have replies, to open their thread. */
167
+ onOpenThread?: (message: ChatMessage) => void;
168
+ /** The quick reactions; 👍 ❤️ 😂 😮 😢 🙏 by default. */
169
+ quickReactions?: readonly string[];
170
+ placeholder?: string;
171
+ slots?: ChatThreadSlots;
172
+ className?: string;
173
+ }
174
+ declare function ChatThread({ conversationId, resolveUser, title: titleProp, onBack, backClassName, threadRootId, topic, onOpenThread, quickReactions, placeholder, slots, className, }: ChatThreadProps): react.JSX.Element;
175
+
176
+ interface ChatPanelNoSelectionProps {
177
+ /** Words under the title; "Pick one on the left to read and reply." by default. */
178
+ hint?: string;
179
+ }
180
+ interface ChatPanelProps {
181
+ resolveUser: ResolveUser;
182
+ /** The open conversation (controlled). */
183
+ selectedId?: string | null;
184
+ /** The conversation open at first (uncontrolled). */
185
+ defaultSelectedId?: string | null;
186
+ /** Called when a conversation is opened, or with null on back. Put it in the URL to make it bookmarkable. */
187
+ onSelectedChange?: (conversationId: string | null) => void;
188
+ /** Above the inbox list: a title, a "New message" button. */
189
+ inboxHeader?: ReactNode;
190
+ inboxProps?: Partial<Omit<ChatInboxProps, "resolveUser" | "onSelect" | "selectedId">>;
191
+ threadProps?: Partial<Omit<ChatThreadProps, "conversationId" | "resolveUser" | "onBack">>;
192
+ /** What the right pane shows before a conversation is chosen. */
193
+ slots?: {
194
+ noSelection?: SlotOverride<ChatPanelNoSelectionProps>;
195
+ };
196
+ className?: string;
197
+ }
198
+ declare function ChatPanel({ resolveUser, selectedId: controlled, defaultSelectedId, onSelectedChange, inboxHeader, inboxProps, threadProps, slots, className, }: ChatPanelProps): react.JSX.Element;
199
+
200
+ interface ChatComposerHandle {
201
+ /** Adds files as if picked (a drop on the thread, say). Refused ones get a toast. */
202
+ addFiles(files: File[] | FileList): void;
203
+ /** Puts text at the cursor. */
204
+ insertText(text: string): void;
205
+ focus(): void;
206
+ }
207
+ interface ChatComposerProps {
208
+ /** From `useConversation`: how to send, and whether you may. */
209
+ conversation: Pick<ConversationState, "send" | "signalTyping" | "signalRecording" | "role" | "canPost" | "accessLoading" | "conversation">;
210
+ /** The message being replied to; a banner shows above the box. */
211
+ replyTo?: ChatMessage | null;
212
+ onCancelReply?: () => void;
213
+ /** Names the replied-to person. */
214
+ resolveUser?: ResolveUser;
215
+ placeholder?: string;
216
+ /** Extra controls beside the attach button (the `composerAccessory` slot of ChatThread). */
217
+ accessory?: ReactNode;
218
+ /** Called after a message is queued. */
219
+ onSent?: () => void;
220
+ autoFocus?: boolean;
221
+ className?: string;
222
+ ref?: Ref<ChatComposerHandle>;
223
+ }
224
+ declare function ChatComposer({ conversation: state, replyTo, onCancelReply, resolveUser, placeholder, accessory, onSent, autoFocus, className, ref, }: ChatComposerProps): react.JSX.Element;
225
+
226
+ interface MessageRowProps {
227
+ item: ThreadItem;
228
+ /** First of a run from one sender: the name and avatar show (others' messages, in groups). */
229
+ startsGroup: boolean;
230
+ className?: string;
231
+ }
232
+ /** Plain text for the clipboard: mentions as @Name. */
233
+ declare function plainText(body: string): string;
234
+ declare function MessageRow({ item, startsGroup, className }: MessageRowProps): react.JSX.Element;
235
+
236
+ interface VoiceNoteProps {
237
+ /** The audio's (signed) URL; while it loads the waveform still draws. */
238
+ src: string | null | undefined;
239
+ durationMs?: number | null;
240
+ /** Peaks 0–100. */
241
+ waveform?: number[] | null;
242
+ transcript?: string | null;
243
+ transcriptStatus?: "pending" | "ready" | "failed" | "off" | null;
244
+ /** On the primary (own) bubble. */
245
+ tone?: "default" | "primary";
246
+ className?: string;
247
+ }
248
+ declare function VoiceNote({ src, durationMs, waveform, transcript, transcriptStatus, tone, className }: VoiceNoteProps): react.JSX.Element;
249
+
250
+ type VoiceRecorderState = "idle" | "requesting" | "recording" | "denied" | "unsupported" | "error";
251
+ interface VoiceRecording {
252
+ blob: Blob;
253
+ /** The container's type without codec parameters, e.g. `audio/webm`. */
254
+ contentType: string;
255
+ name: string;
256
+ durationMs: number;
257
+ /** 48 peaks, 0–100. */
258
+ waveform: number[];
259
+ }
260
+ interface UseVoiceRecorderOptions {
261
+ /** The longest recording; it stops itself there (`recordingLimitMs(role)`). Default five minutes. */
262
+ maxMs?: number;
263
+ /** Called about every 100 ms while recording (e.g. to tell the others). */
264
+ onTick?: () => void;
265
+ /** Called with the recording when it stopped itself at `maxMs`. */
266
+ onLimit?: (recording: VoiceRecording) => void;
267
+ }
268
+ /** The type to record in: Opus in WebM where the browser has it, else MP4 (Safari). */
269
+ declare function pickVoiceMimeType(): string | null;
270
+ /** A recording as a file the outbox sends: a voice attachment with its duration and waveform. */
271
+ declare function voiceFile(recording: VoiceRecording): LocalFile;
272
+ declare function useVoiceRecorder(options?: UseVoiceRecorderOptions): {
273
+ state: VoiceRecorderState;
274
+ recording: boolean;
275
+ elapsedMs: number;
276
+ maxMs: number;
277
+ /** The latest levels, 0–1, newest last: a live meter. */
278
+ levels: number[];
279
+ error: string | null;
280
+ clearError: () => void;
281
+ start: () => Promise<void>;
282
+ stop: () => Promise<VoiceRecording | null>;
283
+ cancel: () => void;
284
+ };
285
+ type VoiceRecorder = ReturnType<typeof useVoiceRecorder>;
286
+
287
+ type MarkdownNode = {
288
+ type: "text";
289
+ text: string;
290
+ } | {
291
+ type: "bold";
292
+ children: MarkdownNode[];
293
+ } | {
294
+ type: "italic";
295
+ children: MarkdownNode[];
296
+ } | {
297
+ type: "code";
298
+ text: string;
299
+ } | {
300
+ type: "link";
301
+ href: string;
302
+ children: MarkdownNode[];
303
+ } | {
304
+ type: "mention";
305
+ name: string;
306
+ userId: string;
307
+ } | {
308
+ type: "break";
309
+ };
310
+ /** True for an absolute http or https URL; everything else (javascript:, data:, relative) is refused. */
311
+ declare function isSafeHref(href: string): boolean;
312
+ /** Parses one message body into inline nodes. Never throws. */
313
+ declare function parseMarkdown(text: string): MarkdownNode[];
314
+ interface ChatMarkdownProps {
315
+ /** The message body. */
316
+ text: string;
317
+ /** Draws a mention; a highlighted chip by default. */
318
+ renderMention?: (mention: {
319
+ name: string;
320
+ userId: string;
321
+ }) => ReactNode;
322
+ /** Mentions of this user are drawn stronger. */
323
+ me?: string;
324
+ /** Bubble colours: on `primary`, links and chips use the primary foreground. */
325
+ tone?: "default" | "primary";
326
+ className?: string;
327
+ }
328
+ /** Renders a message body's inline markdown as React elements. */
329
+ declare function ChatMarkdown({ text, renderMention, me, tone, className }: ChatMarkdownProps): react.JSX.Element;
330
+
331
+ /** The six quick reactions. */
332
+ declare const QUICK_REACTIONS: readonly ["👍", "❤️", "😂", "😮", "😢", "🙏"];
333
+
334
+ /**
335
+ * @feat:chat
336
+ * A chat client held in memory, for demos, stories and tests: the same shape
337
+ * as `createChatClient`, with no Supabase behind it. It also plays the other
338
+ * side (`receive`, `activity`) so a demo looks alive.
339
+ */
340
+
341
+ interface MemoryConversationSeed {
342
+ id: string;
343
+ kind?: string;
344
+ title?: string | null;
345
+ settings?: ChatConversation["settings"];
346
+ members: {
347
+ userId: string;
348
+ role?: ChatRole;
349
+ lastReadSeq?: number;
350
+ }[];
351
+ /** Your role when you aren't a member (an observer). */
352
+ myRole?: ChatRole | null;
353
+ canPost?: boolean;
354
+ muted?: boolean;
355
+ pinned?: boolean;
356
+ messages?: (Partial<Omit<ChatMessage, "attachments">> & {
357
+ attachments?: Partial<ChatAttachment>[];
358
+ })[];
359
+ pins?: string[];
360
+ }
361
+ interface MemoryChatOptions {
362
+ /** The signed-in user. */
363
+ me: string;
364
+ conversations: MemoryConversationSeed[];
365
+ /** Signed URLs for attachment paths. */
366
+ files?: Record<string, string>;
367
+ /** Milliseconds each call waits, to show loading states. Default 0. */
368
+ latencyMs?: number;
369
+ }
370
+ type MemoryChatClient = ChatClient & {
371
+ /** Someone else posts (a demo's other side). */
372
+ receive(conversationId: string, senderId: string, body: string, extra?: Partial<ChatMessage>): ChatMessage;
373
+ /** Someone else starts typing or recording. */
374
+ activity(conversationId: string, userId: string, activity: ChatActivity["activity"]): void;
375
+ /** The next `count` sends fail as if the connection dropped. */
376
+ failNextSends(count?: number): void;
377
+ /** The next send waits until the returned function is called (a test watching the sending state). */
378
+ holdNextSend(): () => void;
379
+ /** Every call made, for tests. */
380
+ calls: {
381
+ method: string;
382
+ args: unknown[];
383
+ }[];
384
+ };
385
+ declare function createMemoryChatClient(options: MemoryChatOptions): MemoryChatClient;
386
+
387
+ export { type AttachmentView, type ChatAttachmentSlotProps, ChatComposer, type ChatComposerAccessoryProps, type ChatComposerHandle, type ChatComposerProps, ChatInbox, type ChatInboxEmptyProps, type ChatInboxProps, ChatMarkdown, type ChatMarkdownProps, ChatPanel, type ChatPanelNoSelectionProps, type ChatPanelProps, ChatThread, type ChatThreadEmptyProps, type ChatThreadHeaderProps, type ChatThreadProps, type ChatThreadSlots, type ChatUser, FileChip, GenericAttachment, ImageGrid, type ImageGridProps, Lightbox, type LightboxProps, type MarkdownNode, type MemoryChatClient, type MemoryChatOptions, type MemoryConversationSeed, MessageAttachments, type MessageAttachmentsProps, MessageRow, type MessageRowProps, QUICK_REACTIONS, type ResolveUser, type UseVoiceRecorderOptions, VideoAttachment, VoiceNote, type VoiceNoteProps, type VoiceRecorder, type VoiceRecorderState, type VoiceRecording, clockTime, createMemoryChatClient, formatBytes, inboxTitle, isSafeHref, messageLabel, parseMarkdown, pickVoiceMimeType, plainText, relativeTime, toAttachmentView, useVoiceRecorder, voiceFile };