@btndprojects/loam-ui-native 0.1.0 → 0.2.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.
Files changed (263) hide show
  1. package/README.md +56 -0
  2. package/dist/accordion/index.js +5 -3
  3. package/dist/adaptive/index.js +6 -2
  4. package/dist/alert/index.js +5 -2
  5. package/dist/analyzer/index.js +9 -6
  6. package/dist/announcement/index.js +8 -5
  7. package/dist/audio-player/index.js +5 -3
  8. package/dist/avatar/index.js +5 -2
  9. package/dist/avatar-group/index.js +7 -3
  10. package/dist/badge/index.js +7 -4
  11. package/dist/banner/index.js +5 -2
  12. package/dist/board/index.js +11 -6
  13. package/dist/button/index.js +6 -3
  14. package/dist/camera-permission/index.js +15 -12
  15. package/dist/capture-result/index.js +19 -16
  16. package/dist/card/index.js +6 -3
  17. package/dist/card-form/index.js +12 -8
  18. package/dist/carousel/index.js +4 -2
  19. package/dist/chart/index.js +8 -5
  20. package/dist/charts/index.js +8 -6
  21. package/dist/chat/index.js +16 -0
  22. package/dist/checkbox/index.js +6 -4
  23. package/dist/checklist/index.js +11 -8
  24. package/dist/checkout/index.js +16 -11
  25. package/dist/chip/index.js +7 -4
  26. package/dist/{chunk-XNUVB5OL.js → chunk-2OLKPG4E.js} +92 -28
  27. package/dist/{chunk-RUZHTGKB.js → chunk-2PKYGLQ6.js} +71 -40
  28. package/dist/{chunk-UC2WGUP3.js → chunk-2SWKL3AJ.js} +91 -252
  29. package/dist/{chunk-AI4DWZCD.js → chunk-2SY63FCH.js} +63 -16
  30. package/dist/{chunk-F5XNPWS7.js → chunk-2TXERVVA.js} +92 -35
  31. package/dist/chunk-36EC2T6P.js +581 -0
  32. package/dist/chunk-3DUWIYV5.js +437 -0
  33. package/dist/{chunk-RGQWWKLA.js → chunk-3QMEOFW5.js} +275 -71
  34. package/dist/chunk-3WEZCGHX.js +265 -0
  35. package/dist/chunk-46MMSDGL.js +194 -0
  36. package/dist/{chunk-7O2FMUPT.js → chunk-4IZXHVFJ.js} +297 -92
  37. package/dist/{chunk-LAECSR4H.js → chunk-5LAAAXFP.js} +14 -8
  38. package/dist/{chunk-2A5NIQAK.js → chunk-5RENVKBQ.js} +134 -46
  39. package/dist/{chunk-2ZZ7TDJ4.js → chunk-5UTRIXQ6.js} +37 -31
  40. package/dist/{chunk-LOE5YNN3.js → chunk-5VFZRGVD.js} +37 -10
  41. package/dist/{chunk-52X2VYTD.js → chunk-67IZZ2MJ.js} +115 -60
  42. package/dist/{chunk-UYG5L34B.js → chunk-6YAQBXB3.js} +10 -4
  43. package/dist/{chunk-BTTFCMKG.js → chunk-74FLKD24.js} +94 -23
  44. package/dist/{chunk-ITTYJZWX.js → chunk-7BUIKDD6.js} +73 -18
  45. package/dist/{chunk-FOKG57CR.js → chunk-7OVVSVEZ.js} +273 -109
  46. package/dist/chunk-AEB7KCHQ.js +287 -0
  47. package/dist/chunk-APCWFOZW.js +28 -0
  48. package/dist/chunk-AWKQIE5I.js +139 -0
  49. package/dist/{chunk-VCDBXW5H.js → chunk-B7N4ASYE.js} +86 -42
  50. package/dist/{chunk-IVNRBPGC.js → chunk-C4GTDLRC.js} +104 -20
  51. package/dist/{chunk-4ZAHCY44.js → chunk-C6APNDVA.js} +12 -5
  52. package/dist/{chunk-4SZYY6AN.js → chunk-CMTVNF3D.js} +18 -13
  53. package/dist/{chunk-WVKKJA6U.js → chunk-CPTOADB2.js} +151 -96
  54. package/dist/chunk-D5N5QWI7.js +1325 -0
  55. package/dist/{chunk-IIQTAY3J.js → chunk-DN7MNQIP.js} +113 -29
  56. package/dist/{chunk-47FWHGUN.js → chunk-DVGBZ3TX.js} +84 -23
  57. package/dist/chunk-DW3L54DQ.js +305 -0
  58. package/dist/{chunk-3ARE4TES.js → chunk-E2E3I5W2.js} +69 -18
  59. package/dist/{chunk-DHWADYZX.js → chunk-EDCBJV7J.js} +41 -28
  60. package/dist/{chunk-WSW6ZFAQ.js → chunk-ENP3BMZU.js} +4 -4
  61. package/dist/{chunk-OOPPQH27.js → chunk-EVOIGUBI.js} +43 -19
  62. package/dist/{chunk-VEAYTRPX.js → chunk-EY4DD67D.js} +165 -97
  63. package/dist/{chunk-223Q2T2Q.js → chunk-F3H33DQX.js} +132 -61
  64. package/dist/{chunk-CYRBHQQZ.js → chunk-F5B3UINI.js} +68 -35
  65. package/dist/{chunk-6X6PQ443.js → chunk-FAJI7BSC.js} +20 -9
  66. package/dist/{chunk-MOUJOP55.js → chunk-FUMHAAF3.js} +96 -29
  67. package/dist/{chunk-6HT2LP62.js → chunk-FV6HMP7W.js} +192 -73
  68. package/dist/{chunk-S7F6XLTK.js → chunk-FYWNRX4F.js} +76 -47
  69. package/dist/{chunk-YG3ZJHGU.js → chunk-FZGAAOKS.js} +30 -8
  70. package/dist/{chunk-YDGVJETP.js → chunk-G3AFC24V.js} +1 -1
  71. package/dist/{chunk-EJBDRVIH.js → chunk-G6UESEVN.js} +15 -8
  72. package/dist/chunk-GDKVKNR2.js +339 -0
  73. package/dist/chunk-GEIGMWNF.js +10 -0
  74. package/dist/{chunk-JUAMLEHX.js → chunk-H7DWSL4O.js} +24 -12
  75. package/dist/chunk-HJFFOOJ4.js +1 -0
  76. package/dist/{chunk-RW6ZL2FQ.js → chunk-HJLPZA4X.js} +106 -21
  77. package/dist/chunk-HYEOQWT7.js +647 -0
  78. package/dist/chunk-I2KLQ2HA.js +19 -0
  79. package/dist/{chunk-CJ5EOYZE.js → chunk-I4FHVKEC.js} +11 -5
  80. package/dist/{chunk-JNH2TYIN.js → chunk-IDM7JJMO.js} +19 -5
  81. package/dist/{chunk-O4AEHWII.js → chunk-IPUNJMAQ.js} +141 -49
  82. package/dist/{chunk-J7Q7VCVC.js → chunk-IT6FUXXG.js} +13 -6
  83. package/dist/{chunk-QJJ6MADV.js → chunk-IVKMZ7BB.js} +80 -28
  84. package/dist/{chunk-Z7H7VSFZ.js → chunk-IXKWIFWZ.js} +17 -6
  85. package/dist/chunk-J64KGCGX.js +55 -0
  86. package/dist/chunk-JBMLWFSJ.js +270 -0
  87. package/dist/{chunk-4BXMXBJO.js → chunk-JJLPBRVZ.js} +22 -11
  88. package/dist/chunk-JLC6PM5S.js +406 -0
  89. package/dist/chunk-JUYWSSKW.js +335 -0
  90. package/dist/{chunk-7CFF4HXS.js → chunk-JV5YOTOB.js} +49 -11
  91. package/dist/{chunk-LZLHELAG.js → chunk-KFJGFLMW.js} +29 -8
  92. package/dist/{chunk-O7AE6DAF.js → chunk-KQZ3GLHR.js} +18 -8
  93. package/dist/{chunk-N32RGOA5.js → chunk-LKFC4CS7.js} +80 -24
  94. package/dist/{chunk-YC6WORFL.js → chunk-LMPHCVJJ.js} +82 -50
  95. package/dist/{chunk-3WPW4KUN.js → chunk-LQP4KEXZ.js} +45 -13
  96. package/dist/{chunk-TKQYWZ5V.js → chunk-LUMVGO2K.js} +47 -31
  97. package/dist/{chunk-AWJOCGFZ.js → chunk-M656FYNH.js} +25 -13
  98. package/dist/{chunk-35OFI3JG.js → chunk-MKSNFG7H.js} +73 -20
  99. package/dist/{chunk-4GBOGOTV.js → chunk-NBEF4EPJ.js} +16 -6
  100. package/dist/chunk-NNCOFLNE.js +24 -0
  101. package/dist/{chunk-4YQOR7JM.js → chunk-NTQ2F3AU.js} +73 -20
  102. package/dist/{chunk-QCDXZBC5.js → chunk-O5V4TG3V.js} +126 -44
  103. package/dist/{chunk-U7TFM2JA.js → chunk-OJQUBQWA.js} +3 -3
  104. package/dist/{chunk-VQHPQEUH.js → chunk-OWAMM2DK.js} +13 -6
  105. package/dist/chunk-PID3NJB7.js +405 -0
  106. package/dist/{chunk-66SG2H23.js → chunk-PMMTGBBS.js} +27 -17
  107. package/dist/{chunk-RIBDXGUG.js → chunk-PNF3NKB5.js} +49 -16
  108. package/dist/{chunk-JXJB2X6M.js → chunk-Q6NKEPRE.js} +81 -32
  109. package/dist/chunk-QDZGFXQD.js +176 -0
  110. package/dist/{chunk-SYNOJOQO.js → chunk-QEYFDRHR.js} +60 -26
  111. package/dist/{chunk-VBOXAEPI.js → chunk-QHAITQIF.js} +97 -22
  112. package/dist/{chunk-VXDZFJGP.js → chunk-QHTVYA65.js} +230 -76
  113. package/dist/{chunk-TUTI5HOZ.js → chunk-QIKT7LIB.js} +14 -8
  114. package/dist/{chunk-3ENTMPQY.js → chunk-QP3KMOVF.js} +82 -42
  115. package/dist/{chunk-GNNJ4GCC.js → chunk-QPWDDJJ4.js} +62 -148
  116. package/dist/{chunk-QWCMU5B2.js → chunk-QRUMA22E.js} +205 -80
  117. package/dist/{chunk-ZVFE5QGU.js → chunk-QYPE5JX3.js} +50 -10
  118. package/dist/{chunk-PUPM2YGF.js → chunk-ROCUG24E.js} +76 -36
  119. package/dist/{chunk-NQYBUO3K.js → chunk-RU3ZPNL6.js} +333 -142
  120. package/dist/{chunk-TOXAGRDQ.js → chunk-RXSCIO6W.js} +14 -5
  121. package/dist/{chunk-FIF2M24N.js → chunk-SATMR6SG.js} +631 -203
  122. package/dist/{chunk-4YIP3CWS.js → chunk-SHHY5OMK.js} +7 -5
  123. package/dist/chunk-SKWO3SZF.js +2284 -0
  124. package/dist/{chunk-YF4NSSMT.js → chunk-SWS5JQIF.js} +41 -13
  125. package/dist/{chunk-CUMUNPXY.js → chunk-T5LTPKX6.js} +15 -7
  126. package/dist/{chunk-H6NIOQHK.js → chunk-TDMG26JI.js} +26 -6
  127. package/dist/{chunk-UWFLYMAR.js → chunk-TLCIR6RG.js} +267 -126
  128. package/dist/{chunk-SBMNWWBV.js → chunk-TN2ZNJUI.js} +47 -9
  129. package/dist/{chunk-5YNTNN5A.js → chunk-TQVAIKFD.js} +106 -41
  130. package/dist/chunk-TRGFFU6A.js +1456 -0
  131. package/dist/{chunk-2GK6PHGE.js → chunk-TY4MTKQJ.js} +100 -22
  132. package/dist/{chunk-AZRX6SAC.js → chunk-U23FYWME.js} +15 -7
  133. package/dist/{chunk-PZXZJBMP.js → chunk-U5NLHYV6.js} +26 -12
  134. package/dist/chunk-UQEN55EY.js +1 -0
  135. package/dist/chunk-UQGBTPXH.js +218 -0
  136. package/dist/chunk-UXGIW7OK.js +116 -0
  137. package/dist/{chunk-PJXUJYS6.js → chunk-UYGDJWMN.js} +55 -30
  138. package/dist/{chunk-QDYDBCFP.js → chunk-V2OEB75Z.js} +50 -51
  139. package/dist/{chunk-LSS54GLJ.js → chunk-VAOAKWUK.js} +34 -13
  140. package/dist/{chunk-DM5QUKKO.js → chunk-VDPK5CY4.js} +18 -6
  141. package/dist/chunk-W23G6JLF.js +37 -0
  142. package/dist/{chunk-CDRZHT42.js → chunk-W63AJTM7.js} +276 -12
  143. package/dist/{chunk-XXXEFXY5.js → chunk-W74TXYFB.js} +13 -5
  144. package/dist/{chunk-LRJNDEIA.js → chunk-WRDSPUE7.js} +144 -29
  145. package/dist/{chunk-C2NTXVWM.js → chunk-XBZGDDMR.js} +11 -4
  146. package/dist/{chunk-BV3B2HYZ.js → chunk-XIDTBCBA.js} +189 -131
  147. package/dist/{chunk-DSJBXLUG.js → chunk-XN4KG3P4.js} +348 -153
  148. package/dist/chunk-XQXMLMH3.js +211 -0
  149. package/dist/{chunk-HPPW424V.js → chunk-XTORZWRF.js} +19 -8
  150. package/dist/{chunk-P23EQNRQ.js → chunk-YAAHPATV.js} +181 -30
  151. package/dist/{chunk-KMI5ML5I.js → chunk-YXOAPXMU.js} +57 -18
  152. package/dist/{chunk-RJQOOHT2.js → chunk-ZECZOOSB.js} +59 -28
  153. package/dist/{chunk-QKXCPTFS.js → chunk-ZLTO5VQI.js} +121 -31
  154. package/dist/{chunk-BEDBFJND.js → chunk-ZVATDZOM.js} +16 -18
  155. package/dist/coachmark/index.js +6 -3
  156. package/dist/code-block/index.js +5 -3
  157. package/dist/color-field/index.js +6 -4
  158. package/dist/commerce/index.js +19 -16
  159. package/dist/connectivity/index.js +5 -2
  160. package/dist/context-menu/index.js +5 -3
  161. package/dist/copy-button/index.js +4 -2
  162. package/dist/data-grid/index.js +21 -16
  163. package/dist/date-range-picker/index.js +11 -8
  164. package/dist/description-list/index.js +3 -1
  165. package/dist/dialog/index.js +4 -2
  166. package/dist/empty-state/index.js +4 -2
  167. package/dist/error-summary/index.js +4 -2
  168. package/dist/fab/index.js +5 -2
  169. package/dist/file-manager/index.js +28 -0
  170. package/dist/file-upload/index.js +10 -7
  171. package/dist/filter-bar/index.js +9 -6
  172. package/dist/flow-run/index.js +15 -12
  173. package/dist/form/index.js +5 -3
  174. package/dist/gallery/index.js +5 -2
  175. package/dist/hold-to-confirm/index.js +4 -2
  176. package/dist/index.d.ts +9557 -2985
  177. package/dist/index.js +355 -122
  178. package/dist/infinite-list/index.js +4 -2
  179. package/dist/inline-edit/index.js +6 -4
  180. package/dist/input/index.js +5 -3
  181. package/dist/interactions/index.js +2 -1
  182. package/dist/invoice/index.js +13 -9
  183. package/dist/kbd/index.js +5 -1
  184. package/dist/keyboard/index.js +4 -2
  185. package/dist/list-row/index.js +6 -4
  186. package/dist/log-viewer/index.js +4 -2
  187. package/dist/map-view/index.js +5 -3
  188. package/dist/markdown/index.js +9 -6
  189. package/dist/metric/index.js +7 -4
  190. package/dist/money/index.js +9 -3
  191. package/dist/nav-drawer/index.js +4 -2
  192. package/dist/nav-header/index.js +9 -0
  193. package/dist/navigation/index.js +5 -2
  194. package/dist/notification-center/index.js +12 -10
  195. package/dist/number-roll/index.js +5 -2
  196. package/dist/one-handed/index.js +5 -3
  197. package/dist/os-surfaces/index.js +6 -3
  198. package/dist/otp/index.js +7 -4
  199. package/dist/page-header/index.js +1 -0
  200. package/dist/pagination/index.js +6 -0
  201. package/dist/payment-method/index.js +14 -10
  202. package/dist/payment-status/index.js +17 -14
  203. package/dist/permission/index.js +5 -2
  204. package/dist/picker/index.js +11 -6
  205. package/dist/progress/index.js +6 -3
  206. package/dist/pull-to-refresh/index.js +4 -2
  207. package/dist/radio-group/index.js +6 -4
  208. package/dist/rating/index.js +5 -2
  209. package/dist/receivables/index.js +21 -16
  210. package/dist/refund/index.js +18 -14
  211. package/dist/save-status/index.js +5 -3
  212. package/dist/scroll-area/index.js +3 -1
  213. package/dist/search-field/index.js +8 -0
  214. package/dist/segmented-control/index.js +4 -3
  215. package/dist/sheet/index.js +5 -3
  216. package/dist/skeleton/index.js +3 -1
  217. package/dist/slider/index.js +4 -2
  218. package/dist/sortable/index.js +7 -0
  219. package/dist/spinner/index.js +3 -1
  220. package/dist/split-button/index.js +9 -6
  221. package/dist/spotlight/index.js +3 -1
  222. package/dist/stage-timeline/index.js +5 -3
  223. package/dist/status-dot/index.js +4 -2
  224. package/dist/stepper/index.js +7 -4
  225. package/dist/streaming-text/index.js +3 -2
  226. package/dist/survey/index.js +18 -15
  227. package/dist/switch/index.js +5 -3
  228. package/dist/system-state/index.js +7 -4
  229. package/dist/tab-bar/index.js +5 -2
  230. package/dist/tabs/index.js +4 -2
  231. package/dist/tags-input/index.js +10 -0
  232. package/dist/textarea/index.js +6 -4
  233. package/dist/thinking/index.js +3 -1
  234. package/dist/time-field/index.js +12 -0
  235. package/dist/time-range-control/index.js +22 -0
  236. package/dist/timeline/index.js +4 -2
  237. package/dist/toast/index.js +4 -2
  238. package/dist/toolbar/index.js +4 -2
  239. package/dist/tour/index.js +12 -9
  240. package/dist/truncation-notice/index.js +5 -2
  241. package/dist/update-notice/index.js +12 -9
  242. package/dist/video-player/index.js +5 -2
  243. package/dist/viewfinder/index.js +6 -4
  244. package/dist/widget/index.js +7 -4
  245. package/dist/wizard/index.js +7 -4
  246. package/package.json +13 -12
  247. package/dist/chunk-6N33LFQA.js +0 -35
  248. package/dist/chunk-GDKZTW34.js +0 -395
  249. package/dist/chunk-GL7B2DOW.js +0 -134
  250. package/dist/chunk-NKB7BZYK.js +0 -167
  251. package/dist/chunk-OETCUXQP.js +0 -134
  252. package/dist/chunk-OPXFZ4Y5.js +0 -204
  253. package/dist/chunk-Q72LJOVE.js +0 -169
  254. package/dist/chunk-RNGLIHFA.js +0 -191
  255. package/dist/chunk-RQ3HASCC.js +0 -6
  256. package/dist/chunk-RSOJT5YW.js +0 -124
  257. package/dist/chunk-TKVFVF5D.js +0 -95
  258. package/dist/chunk-XOSCY2CW.js +0 -242
  259. package/dist/header/index.js +0 -5
  260. package/dist/search-bar/index.js +0 -5
  261. /package/dist/{chunk-3ECPOSKO.js → chunk-3UG6JUCV.js} +0 -0
  262. /package/dist/{chunk-JRLTGLN4.js → chunk-5WWRTEPS.js} +0 -0
  263. /package/dist/{chunk-RTIKSY6A.js → chunk-5Z5ZYMOJ.js} +0 -0
@@ -0,0 +1,2284 @@
1
+ import { useThinkingPulse, Thinking } from './chunk-W74TXYFB.js';
2
+ import { Markdown } from './chunk-SWS5JQIF.js';
3
+ import { CopyButton } from './chunk-C6APNDVA.js';
4
+ import { Skeleton } from './chunk-PNF3NKB5.js';
5
+ import { useFormFactor } from './chunk-AWKQIE5I.js';
6
+ import { expandedProps } from './chunk-W23G6JLF.js';
7
+ import { Button } from './chunk-E2E3I5W2.js';
8
+ import { haptics } from './chunk-NY2U3PBI.js';
9
+ import { useLabels } from './chunk-APCWFOZW.js';
10
+ import { Icon } from './chunk-UXGIW7OK.js';
11
+ import { springConfig, BREAKAWAY, resist, rubberband, useResolvedMotion } from './chunk-J64KGCGX.js';
12
+ import * as React6 from 'react';
13
+ import { Platform, UIManager, Animated, View, TextInput, Text, Pressable, Image, I18nManager, PanResponder, LayoutAnimation, Easing, ScrollView, AccessibilityInfo, KeyboardAvoidingView } from 'react-native';
14
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
15
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
16
+
17
+ // src/chat/chat-labels.ts
18
+ function formatChatDuration(ms) {
19
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
20
+ const s = ms / 1e3;
21
+ if (s < 60) return `${s < 10 ? s.toFixed(1) : Math.round(s)}s`;
22
+ const m = Math.floor(s / 60);
23
+ return `${m}m ${String(Math.round(s % 60)).padStart(2, "0")}s`;
24
+ }
25
+ function formatChatBytes(bytes) {
26
+ if (bytes < 1024) return `${bytes} B`;
27
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
28
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
29
+ }
30
+ var DEFAULT_CHAT_LABELS = {
31
+ chat: "Chat",
32
+ messages: "Messages",
33
+ emptyTitle: "How can I help?",
34
+ placeholder: "Send a message\u2026",
35
+ composer: "Message",
36
+ assistant: "Assistant",
37
+ you: "You",
38
+ attachFiles: "Attach files",
39
+ attachments: "Attachments",
40
+ dropFiles: "Drop files to attach",
41
+ send: "Send",
42
+ stop: "Stop",
43
+ replyingTo: (author) => `Replying to ${author}`,
44
+ cancelReply: "Cancel reply",
45
+ removeAttachment: (name) => `Remove ${name}`,
46
+ uploading: "Uploading\u2026",
47
+ uploadFailed: "Upload failed",
48
+ file: "File",
49
+ duration: formatChatDuration,
50
+ bytes: formatChatBytes,
51
+ elapsed: (s) => `${s}s`,
52
+ saidBy: (name) => `${name} said`,
53
+ responding: (name) => `${name} is responding\u2026`,
54
+ said: (name, text) => `${name} said: ${text}`,
55
+ stoppedAnnouncement: (name, text) => `${name} stopped. ${text}`,
56
+ stopped: "Stopped",
57
+ loadEarlier: "Load earlier messages",
58
+ loadingEarlier: "Loading earlier messages",
59
+ writing: "Writing\u2026",
60
+ newMessages: "New messages",
61
+ jumpToLatest: "Jump to latest",
62
+ followUps: "Suggested follow-ups",
63
+ copyMessage: "Copy message",
64
+ copy: "Copy",
65
+ copyResponse: "Copy response",
66
+ editMessage: "Edit message",
67
+ editNote: "Sending replaces this message and everything after it.",
68
+ editHint: "Long-press to edit",
69
+ reply: "Reply",
70
+ regenerate: "Regenerate",
71
+ retry: "Retry",
72
+ notSent: "Not sent.",
73
+ cancel: "Cancel",
74
+ goodResponse: "Good response",
75
+ badResponse: "Bad response",
76
+ feedbackTitle: "What went wrong?",
77
+ feedbackReasons: [
78
+ "Not accurate",
79
+ "Not helpful",
80
+ "Too long",
81
+ "Didn\u2019t follow instructions",
82
+ "Something else"
83
+ ],
84
+ feedbackNote: "Tell us more (optional)",
85
+ sendFeedback: "Send feedback",
86
+ thanks: "Thanks for the feedback.",
87
+ thinking: "Thinking\u2026",
88
+ reasoning: "Reasoning",
89
+ thoughtFor: (d) => `Thought for ${d}`,
90
+ steps: (n) => `${n} ${n === 1 ? "step" : "steps"}`,
91
+ working: (n) => `Working \xB7 ${n} ${n === 1 ? "step" : "steps"}`,
92
+ usedTools: (n, failed) => `Used ${n} ${n === 1 ? "tool" : "tools"}${failed ? ` \xB7 ${failed} failed` : ""}`,
93
+ toolRunning: "running",
94
+ toolDone: "done",
95
+ toolFailed: "failed",
96
+ moreSuggestions: "More suggestions",
97
+ previousSuggestions: "Previous suggestions",
98
+ suggestionsPage: (p, n) => `Suggestions page ${p} of ${n}`,
99
+ suggestionsPageOf: (p, n) => `Page ${p} of ${n}`,
100
+ loadingSuggestions: "Loading suggestions",
101
+ notUseful: "Not useful",
102
+ notUsefulFor: (label) => `Not useful: ${label}`,
103
+ why: (r) => `Why: ${r}`
104
+ };
105
+ if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
106
+ UIManager.setLayoutAnimationEnabledExperimental(true);
107
+ }
108
+ function useArrival(enabled, kind = "in", delayMs = 0) {
109
+ const theme = useLoamTheme("chat");
110
+ const [run] = React6.useState(enabled);
111
+ const p = React6.useRef(new Animated.Value(run ? 0 : 1)).current;
112
+ React6.useEffect(() => {
113
+ if (!run) return;
114
+ if (theme.reduceMotion) {
115
+ Animated.timing(p, { toValue: 1, duration: theme.duration.fast, delay: delayMs, useNativeDriver: true }).start();
116
+ return;
117
+ }
118
+ Animated.sequence([
119
+ Animated.delay(delayMs),
120
+ Animated.spring(p, { ...springConfig(theme, "ds"), toValue: 1, useNativeDriver: true })
121
+ ]).start();
122
+ }, [run, p, delayMs, theme]);
123
+ if (!run) return void 0;
124
+ if (theme.reduceMotion) return { opacity: p };
125
+ const rise = 8;
126
+ const scale = kind === "sent" ? 0.96 : kind === "card" ? 0.985 : 1;
127
+ return {
128
+ opacity: p.interpolate({ inputRange: [0, 1], outputRange: [0, 1], extrapolate: "clamp" }),
129
+ transform: [
130
+ { translateY: p.interpolate({ inputRange: [0, 1], outputRange: [rise, 0] }) },
131
+ { scale: p.interpolate({ inputRange: [0, 1], outputRange: [scale, 1] }) }
132
+ ]
133
+ };
134
+ }
135
+ function useAutoDisclosure(live, animate) {
136
+ const theme = useLoamTheme("chat");
137
+ const [open, setOpen] = React6.useState(live);
138
+ const owned = React6.useRef(false);
139
+ const ease = React6.useCallback(() => {
140
+ if (!animate) return;
141
+ const duration = theme.motion.expand.duration;
142
+ LayoutAnimation.configureNext({
143
+ duration,
144
+ update: { type: LayoutAnimation.Types.easeInEaseOut },
145
+ create: { type: LayoutAnimation.Types.easeInEaseOut, property: LayoutAnimation.Properties.opacity, duration },
146
+ delete: { type: LayoutAnimation.Types.easeInEaseOut, property: LayoutAnimation.Properties.opacity, duration }
147
+ });
148
+ }, [animate, theme.motion.expand.duration]);
149
+ React6.useEffect(() => {
150
+ if (owned.current) return;
151
+ ease();
152
+ setOpen(live);
153
+ }, [live, ease]);
154
+ const toggle = React6.useCallback(() => {
155
+ owned.current = true;
156
+ ease();
157
+ setOpen((o) => !o);
158
+ }, [ease]);
159
+ return [open, toggle];
160
+ }
161
+ function Disclosure({
162
+ open,
163
+ onToggle,
164
+ summary,
165
+ icon,
166
+ shimmer,
167
+ children
168
+ }) {
169
+ const { styles } = useStyles(sheet);
170
+ const theme = useLoamTheme("chat");
171
+ const pulse = useThinkingPulse(shimmer);
172
+ return /* @__PURE__ */ jsxs(View, { children: [
173
+ /* @__PURE__ */ jsxs(
174
+ Pressable,
175
+ {
176
+ role: "button",
177
+ accessibilityRole: "button",
178
+ accessibilityLabel: summary,
179
+ ...expandedProps(open),
180
+ accessibilityState: { expanded: open },
181
+ onPress: onToggle,
182
+ hitSlop: theme.space[2],
183
+ style: styles.summary,
184
+ children: [
185
+ icon,
186
+ /* @__PURE__ */ jsx(Animated.Text, { style: [styles.summaryText, pulse], children: summary }),
187
+ /* @__PURE__ */ jsx(View, { style: { transform: [{ rotate: open ? "90deg" : "0deg" }] }, children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: theme.icon.xs, color: theme.color.text3 }) })
188
+ ]
189
+ }
190
+ ),
191
+ open ? /* @__PURE__ */ jsx(View, { style: styles.panel, children }) : null
192
+ ] });
193
+ }
194
+ function ReasoningView({
195
+ part,
196
+ live,
197
+ animate,
198
+ labels: labelsProp
199
+ }) {
200
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
201
+ const { styles } = useStyles(sheet);
202
+ const theme = useLoamTheme("chat");
203
+ const [open, toggle] = useAutoDisclosure(live, animate);
204
+ const summary = live ? labels.thinking : part.durationMs != null ? labels.thoughtFor(labels.duration(part.durationMs)) : labels.reasoning;
205
+ return /* @__PURE__ */ jsx(
206
+ Disclosure,
207
+ {
208
+ open,
209
+ onToggle: toggle,
210
+ summary,
211
+ shimmer: live && animate,
212
+ icon: /* @__PURE__ */ jsx(Icon, { name: "brain", size: theme.icon.sm, color: theme.color.text3 }),
213
+ children: /* @__PURE__ */ jsx(View, { style: styles.rail, children: /* @__PURE__ */ jsx(Text, { style: styles.reasoning, children: part.text }) })
214
+ }
215
+ );
216
+ }
217
+ function ToolMark({ status, animate }) {
218
+ const theme = useLoamTheme("chat");
219
+ const pop = React6.useRef(new Animated.Value(status === "running" || !animate ? 1 : 0)).current;
220
+ const prev = React6.useRef(status);
221
+ React6.useEffect(() => {
222
+ if (prev.current === "running" && status !== "running" && animate) {
223
+ pop.setValue(0);
224
+ Animated.spring(pop, { toValue: 1, useNativeDriver: true, speed: 18, bounciness: 10 }).start();
225
+ }
226
+ prev.current = status;
227
+ }, [status, animate, pop]);
228
+ if (status === "running") {
229
+ return /* @__PURE__ */ jsx(Thinking, { label: "", size: "sm", announce: false, motion: animate ? "full" : "none" });
230
+ }
231
+ return /* @__PURE__ */ jsx(Animated.View, { style: { opacity: pop, transform: [{ scale: pop.interpolate({ inputRange: [0, 1], outputRange: [0.6, 1] }) }] }, children: /* @__PURE__ */ jsx(
232
+ Icon,
233
+ {
234
+ name: status === "error" ? "x" : "check",
235
+ size: theme.icon.xs,
236
+ color: status === "error" ? theme.color.danger : theme.color.good
237
+ }
238
+ ) });
239
+ }
240
+ function ToolGroupView({
241
+ tools,
242
+ animate,
243
+ labels: labelsProp
244
+ }) {
245
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
246
+ const { styles } = useStyles(sheet);
247
+ const theme = useLoamTheme("chat");
248
+ const running = tools.some((t) => t.status === "running");
249
+ const failed = tools.filter((t) => t.status === "error").length;
250
+ const [open, toggle] = useAutoDisclosure(running, animate);
251
+ const n = tools.length;
252
+ const summary = running ? labels.working(n) : labels.usedTools(n, failed);
253
+ return /* @__PURE__ */ jsx(
254
+ Disclosure,
255
+ {
256
+ open,
257
+ onToggle: toggle,
258
+ summary,
259
+ shimmer: running && animate,
260
+ icon: /* @__PURE__ */ jsx(Icon, { name: "wrench", size: theme.icon.sm, color: theme.color.text3 }),
261
+ children: /* @__PURE__ */ jsx(View, { accessibilityRole: "list", children: tools.map((tool, i) => /* @__PURE__ */ jsxs(View, { role: "listitem", style: styles.step, children: [
262
+ /* @__PURE__ */ jsxs(View, { style: styles.nodeCol, children: [
263
+ i > 0 ? /* @__PURE__ */ jsx(View, { style: [styles.line, styles.lineTop] }) : null,
264
+ i < n - 1 ? /* @__PURE__ */ jsx(View, { style: [styles.line, styles.lineBottom] }) : null,
265
+ /* @__PURE__ */ jsx(View, { style: styles.node, children: /* @__PURE__ */ jsx(Icon, { name: tool.icon ?? "wrench", size: theme.icon.xs, color: theme.color.text3 }) })
266
+ ] }),
267
+ /* @__PURE__ */ jsxs(View, { style: styles.stepBody, children: [
268
+ /* @__PURE__ */ jsxs(View, { style: styles.stepHead, children: [
269
+ /* @__PURE__ */ jsx(Text, { style: styles.stepTitle, numberOfLines: 2, children: tool.title }),
270
+ /* @__PURE__ */ jsx(ToolMark, { status: tool.status, animate }),
271
+ tool.durationMs != null && tool.status !== "running" ? /* @__PURE__ */ jsx(Text, { style: styles.meta, children: labels.duration(tool.durationMs) }) : null
272
+ ] }),
273
+ tool.detail ? /* @__PURE__ */ jsx(Text, { style: styles.meta, numberOfLines: 2, children: tool.detail }) : null
274
+ ] })
275
+ ] }, tool.id)) })
276
+ }
277
+ );
278
+ }
279
+ function WorkingIndicator({
280
+ phase: phaseProp,
281
+ startedAt,
282
+ animate,
283
+ labels: labelsProp
284
+ }) {
285
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
286
+ const phase = phaseProp ?? labels.thinking;
287
+ const { styles } = useStyles(sheet);
288
+ const pulse = useThinkingPulse(animate);
289
+ const [now, setNow] = React6.useState(() => Date.now());
290
+ React6.useEffect(() => {
291
+ if (startedAt == null) return;
292
+ const id = setInterval(() => setNow(Date.now()), 1e3);
293
+ return () => clearInterval(id);
294
+ }, [startedAt]);
295
+ const elapsed = startedAt != null ? Math.max(0, Math.floor((now - startedAt) / 1e3)) : null;
296
+ return /* @__PURE__ */ jsxs(View, { style: styles.working, role: "status", accessibilityLabel: phase, children: [
297
+ /* @__PURE__ */ jsx(Thinking, { label: "", size: "sm", announce: false, motion: animate ? "full" : "none" }),
298
+ /* @__PURE__ */ jsx(Animated.Text, { style: [styles.summaryText, pulse], children: phase }),
299
+ elapsed != null && elapsed > 0 ? /* @__PURE__ */ jsxs(Text, { style: styles.meta, children: [
300
+ "\xB7 ",
301
+ labels.elapsed(elapsed)
302
+ ] }) : null
303
+ ] });
304
+ }
305
+ function AttachmentChip({
306
+ attachment,
307
+ onRemove,
308
+ labels: labelsProp
309
+ }) {
310
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
311
+ const { styles } = useStyles(sheet);
312
+ const theme = useLoamTheme("chat");
313
+ const status = attachment.status ?? "done";
314
+ const image = attachment.type?.startsWith("image/") && attachment.previewUrl;
315
+ const sub = status === "uploading" ? labels.uploading : status === "error" ? labels.uploadFailed : attachment.size != null ? labels.bytes(attachment.size) : void 0;
316
+ return /* @__PURE__ */ jsxs(View, { style: [styles.chip, status === "error" && styles.chipError], children: [
317
+ image ? /* @__PURE__ */ jsx(Image, { source: { uri: attachment.previewUrl }, style: styles.thumb, accessibilityIgnoresInvertColors: true }) : /* @__PURE__ */ jsx(View, { style: styles.fileIcon, children: /* @__PURE__ */ jsx(
318
+ Icon,
319
+ {
320
+ name: status === "uploading" ? "loader" : "file",
321
+ size: theme.icon.sm,
322
+ color: theme.color.text2
323
+ }
324
+ ) }),
325
+ /* @__PURE__ */ jsxs(View, { style: styles.chipText, children: [
326
+ /* @__PURE__ */ jsx(Text, { style: styles.chipName, numberOfLines: 1, children: attachment.name }),
327
+ sub ? /* @__PURE__ */ jsx(Text, { style: [styles.meta, status === "error" && { color: theme.color.danger }], children: sub }) : null
328
+ ] }),
329
+ onRemove ? /* @__PURE__ */ jsx(
330
+ Pressable,
331
+ {
332
+ accessibilityRole: "button",
333
+ accessibilityLabel: labels.removeAttachment(attachment.name),
334
+ onPress: onRemove,
335
+ hitSlop: theme.space[3],
336
+ style: styles.remove,
337
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.xs, color: theme.color.text3 })
338
+ }
339
+ ) : null
340
+ ] });
341
+ }
342
+ var NODE = 24;
343
+ var sheet = createStyleSheet((theme) => ({
344
+ summary: {
345
+ flexDirection: "row",
346
+ alignItems: "center",
347
+ alignSelf: "flex-start",
348
+ gap: theme.space.s2,
349
+ paddingVertical: theme.space[2]
350
+ },
351
+ summaryText: {
352
+ fontFamily: theme.font.sans,
353
+ fontSize: theme.type.footnote.fontSize,
354
+ lineHeight: theme.type.footnote.lineHeight,
355
+ color: theme.color.text2
356
+ },
357
+ panel: { paddingTop: theme.space.s2 },
358
+ rail: {
359
+ borderLeftWidth: theme.borderWidth.base,
360
+ borderLeftColor: theme.color.border,
361
+ paddingLeft: theme.space.s3,
362
+ marginLeft: theme.space[6]
363
+ },
364
+ reasoning: {
365
+ fontFamily: theme.font.sans,
366
+ fontSize: theme.type.footnote.fontSize,
367
+ lineHeight: theme.type.footnote.lineHeight,
368
+ color: theme.color.text2
369
+ },
370
+ step: { flexDirection: "row", gap: theme.space.s3, paddingVertical: theme.space[6] },
371
+ nodeCol: { width: NODE, alignItems: "center" },
372
+ line: {
373
+ position: "absolute",
374
+ width: theme.borderWidth.base,
375
+ backgroundColor: theme.color.border,
376
+ left: (NODE - theme.borderWidth.base) / 2
377
+ },
378
+ lineTop: { top: -theme.space[6], height: theme.space[6] + theme.space[2] },
379
+ lineBottom: { top: theme.space[2] + NODE, bottom: -theme.space[6] },
380
+ node: {
381
+ marginTop: theme.space[2],
382
+ width: NODE,
383
+ height: NODE,
384
+ borderRadius: theme.radius.full,
385
+ borderWidth: theme.borderWidth.base,
386
+ borderColor: theme.color.border,
387
+ backgroundColor: theme.color.surface,
388
+ alignItems: "center",
389
+ justifyContent: "center"
390
+ },
391
+ stepBody: { flex: 1, minWidth: 0, gap: theme.space[2], paddingTop: theme.space[4] },
392
+ stepHead: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
393
+ stepTitle: {
394
+ flexShrink: 1,
395
+ fontFamily: theme.font.sans,
396
+ fontSize: theme.type.footnote.fontSize,
397
+ lineHeight: theme.type.footnote.lineHeight,
398
+ color: theme.color.text
399
+ },
400
+ meta: {
401
+ fontFamily: theme.font.sans,
402
+ fontSize: theme.type.caption1.fontSize,
403
+ lineHeight: theme.type.caption1.lineHeight,
404
+ color: theme.color.text3
405
+ },
406
+ working: { flexDirection: "row", alignItems: "center", gap: theme.space.s2, minHeight: theme.ctl.sm },
407
+ chip: {
408
+ flexDirection: "row",
409
+ alignItems: "center",
410
+ gap: theme.space.s2,
411
+ maxWidth: theme.ctl.sm * 7,
412
+ padding: theme.space.s1,
413
+ paddingRight: theme.space.s2,
414
+ borderRadius: theme.radius.md,
415
+ borderWidth: theme.borderWidth.base,
416
+ borderColor: theme.color.border,
417
+ backgroundColor: theme.color.surface
418
+ },
419
+ chipError: { borderColor: theme.color.danger },
420
+ thumb: { width: theme.ctl.sm, height: theme.ctl.sm, borderRadius: theme.radius.sm },
421
+ fileIcon: {
422
+ width: theme.ctl.sm,
423
+ height: theme.ctl.sm,
424
+ borderRadius: theme.radius.sm,
425
+ backgroundColor: theme.color.surface2,
426
+ alignItems: "center",
427
+ justifyContent: "center"
428
+ },
429
+ chipText: { flexShrink: 1, minWidth: 0 },
430
+ chipName: {
431
+ fontFamily: theme.font.sans,
432
+ fontSize: theme.type.footnote.fontSize,
433
+ lineHeight: theme.type.footnote.lineHeight,
434
+ fontWeight: theme.fontWeight.medium,
435
+ color: theme.color.text
436
+ },
437
+ remove: { padding: theme.space[4], borderRadius: theme.radius.full }
438
+ }));
439
+ function ChatComposer({
440
+ onSend,
441
+ replyTo,
442
+ onCancelReply,
443
+ running = false,
444
+ onStop,
445
+ value: valueProp,
446
+ defaultValue = "",
447
+ onValueChange,
448
+ placeholder: placeholderProp,
449
+ label: labelProp,
450
+ attachments = [],
451
+ onAttachPress,
452
+ onRemoveAttachment,
453
+ toolbar,
454
+ hint,
455
+ disabled = false,
456
+ inputRef,
457
+ motion = "full",
458
+ style,
459
+ labels: labelsProp
460
+ }) {
461
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
462
+ const placeholder = placeholderProp ?? labels.placeholder;
463
+ const label = labelProp ?? labels.composer;
464
+ const { styles } = useStyles(sheet2);
465
+ const theme = useLoamTheme("chat");
466
+ const animate = useResolvedMotion(motion, "chat") === "full";
467
+ const [inner, setInner] = React6.useState(defaultValue);
468
+ const value = valueProp ?? inner;
469
+ const setValue = (next) => {
470
+ if (valueProp === void 0) setInner(next);
471
+ onValueChange?.(next);
472
+ };
473
+ const [focused, setFocused] = React6.useState(false);
474
+ const uploading = attachments.some((a) => a.status === "uploading");
475
+ const canSend = !disabled && !running && !uploading && (value.trim() !== "" || attachments.length > 0);
476
+ const showStop = running && !!onStop;
477
+ const swap = React6.useRef(new Animated.Value(showStop ? 1 : 0)).current;
478
+ React6.useEffect(() => {
479
+ Animated.timing(swap, {
480
+ toValue: showStop ? 1 : 0,
481
+ duration: animate ? theme.motion.swap.duration : 0,
482
+ easing: Easing.bezier(...theme.motion.swap.easing),
483
+ useNativeDriver: true
484
+ }).start();
485
+ }, [showStop, animate, swap, theme.motion.swap]);
486
+ const send = () => {
487
+ if (!canSend) return;
488
+ onSend(value.trim(), attachments);
489
+ setValue("");
490
+ };
491
+ const active = showStop || canSend;
492
+ return /* @__PURE__ */ jsxs(View, { style: [styles.root, style], children: [
493
+ /* @__PURE__ */ jsxs(View, { style: [styles.box, focused && styles.boxFocused, disabled && styles.disabled], children: [
494
+ replyTo ? /* @__PURE__ */ jsx(
495
+ ReplyBar,
496
+ {
497
+ reply: replyTo,
498
+ animate,
499
+ onCancel: onCancelReply,
500
+ labels
501
+ },
502
+ replyTo.text
503
+ ) : null,
504
+ attachments.length > 0 ? /* @__PURE__ */ jsx(
505
+ ScrollView,
506
+ {
507
+ horizontal: true,
508
+ showsHorizontalScrollIndicator: false,
509
+ contentContainerStyle: styles.chips,
510
+ children: attachments.map((a) => /* @__PURE__ */ jsx(
511
+ AttachmentChip,
512
+ {
513
+ attachment: a,
514
+ onRemove: onRemoveAttachment ? () => onRemoveAttachment(a.id) : void 0,
515
+ labels
516
+ },
517
+ a.id
518
+ ))
519
+ }
520
+ ) : null,
521
+ /* @__PURE__ */ jsx(
522
+ TextInput,
523
+ {
524
+ ref: inputRef,
525
+ accessibilityLabel: label,
526
+ placeholder,
527
+ placeholderTextColor: theme.color.text3,
528
+ editable: !disabled,
529
+ accessibilityState: { disabled: !!disabled },
530
+ "aria-disabled": disabled || void 0,
531
+ multiline: true,
532
+ value,
533
+ onChangeText: setValue,
534
+ onFocus: () => setFocused(true),
535
+ onBlur: () => setFocused(false),
536
+ style: styles.input
537
+ }
538
+ ),
539
+ /* @__PURE__ */ jsxs(View, { style: styles.bar, children: [
540
+ onAttachPress ? /* @__PURE__ */ jsx(
541
+ Pressable,
542
+ {
543
+ accessibilityRole: "button",
544
+ accessibilityLabel: labels.attachFiles,
545
+ disabled,
546
+ onPress: onAttachPress,
547
+ hitSlop: theme.space[6],
548
+ style: ({ pressed }) => [styles.tool, pressed && styles.toolDown],
549
+ children: /* @__PURE__ */ jsx(Icon, { name: "paperclip", size: theme.icon.sm, color: theme.color.text2 })
550
+ }
551
+ ) : null,
552
+ toolbar,
553
+ /* @__PURE__ */ jsx(View, { style: styles.spacer }),
554
+ /* @__PURE__ */ jsxs(
555
+ Pressable,
556
+ {
557
+ accessibilityRole: "button",
558
+ accessibilityLabel: showStop ? labels.stop : labels.send,
559
+ accessibilityState: { disabled: !active },
560
+ disabled: !active,
561
+ onPress: showStop ? onStop : send,
562
+ hitSlop: theme.space[6],
563
+ style: [styles.send, !active && styles.sendIdle],
564
+ children: [
565
+ /* @__PURE__ */ jsx(
566
+ Animated.View,
567
+ {
568
+ style: [
569
+ styles.glyph,
570
+ {
571
+ opacity: swap.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),
572
+ transform: [
573
+ { scale: swap.interpolate({ inputRange: [0, 1], outputRange: [1, 0.5] }) }
574
+ ]
575
+ }
576
+ ],
577
+ children: /* @__PURE__ */ jsx(
578
+ Icon,
579
+ {
580
+ name: "arrow-up",
581
+ size: theme.icon.sm,
582
+ color: active ? theme.color.accentInk : theme.color.text3
583
+ }
584
+ )
585
+ }
586
+ ),
587
+ /* @__PURE__ */ jsx(
588
+ Animated.View,
589
+ {
590
+ style: [
591
+ styles.glyph,
592
+ {
593
+ opacity: swap,
594
+ transform: [
595
+ { scale: swap.interpolate({ inputRange: [0, 1], outputRange: [0.5, 1] }) }
596
+ ]
597
+ }
598
+ ],
599
+ children: /* @__PURE__ */ jsx(Icon, { name: "stop", size: theme.icon.xs, color: theme.color.accentInk })
600
+ }
601
+ )
602
+ ]
603
+ }
604
+ )
605
+ ] })
606
+ ] }),
607
+ hint ? /* @__PURE__ */ jsx(Text, { style: styles.hint, children: hint }) : null
608
+ ] });
609
+ }
610
+ function ReplyBar({
611
+ reply,
612
+ animate,
613
+ onCancel,
614
+ labels
615
+ }) {
616
+ const { styles } = useStyles(sheet2);
617
+ const theme = useLoamTheme("chat");
618
+ const arrival = useArrival(animate, "card");
619
+ return /* @__PURE__ */ jsxs(
620
+ Animated.View,
621
+ {
622
+ style: [styles.reply, arrival],
623
+ role: "group",
624
+ accessibilityLabel: labels.replyingTo(reply.author),
625
+ children: [
626
+ /* @__PURE__ */ jsx(Icon, { name: "undo", size: theme.icon.xs, color: theme.color.text3 }),
627
+ /* @__PURE__ */ jsxs(View, { style: styles.replyBody, children: [
628
+ /* @__PURE__ */ jsx(Text, { style: styles.replyAuthor, children: labels.replyingTo(reply.author) }),
629
+ /* @__PURE__ */ jsx(Text, { style: styles.replyText, numberOfLines: 1, children: reply.text })
630
+ ] }),
631
+ onCancel ? /* @__PURE__ */ jsx(
632
+ Pressable,
633
+ {
634
+ accessibilityRole: "button",
635
+ accessibilityLabel: labels.cancelReply,
636
+ onPress: onCancel,
637
+ hitSlop: theme.space[8],
638
+ style: styles.tool,
639
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.sm, color: theme.color.text3 })
640
+ }
641
+ ) : null
642
+ ]
643
+ }
644
+ );
645
+ }
646
+ var sheet2 = createStyleSheet((theme) => ({
647
+ root: { gap: theme.space.s2 },
648
+ reply: {
649
+ flexDirection: "row",
650
+ alignItems: "center",
651
+ gap: theme.space.s2,
652
+ marginHorizontal: theme.space.s2,
653
+ marginBottom: theme.space.s1,
654
+ paddingStart: theme.space.s3,
655
+ paddingVertical: theme.space[6],
656
+ borderRadius: theme.radius.lg,
657
+ borderStartWidth: theme.borderWidth.strong,
658
+ borderStartColor: theme.color.accent,
659
+ backgroundColor: theme.color.surface3
660
+ },
661
+ replyBody: { flex: 1, minWidth: 0 },
662
+ replyAuthor: {
663
+ fontFamily: theme.font.sans,
664
+ fontSize: theme.type.caption1.fontSize,
665
+ lineHeight: theme.type.caption1.lineHeight,
666
+ color: theme.color.text2
667
+ },
668
+ replyText: {
669
+ fontFamily: theme.font.sans,
670
+ fontSize: theme.type.footnote.fontSize,
671
+ lineHeight: theme.type.footnote.lineHeight,
672
+ color: theme.color.text
673
+ },
674
+ box: {
675
+ borderRadius: theme.radius.xl,
676
+ borderWidth: theme.borderWidth.strong,
677
+ borderColor: theme.color.borderStrong,
678
+ backgroundColor: theme.color.surface2,
679
+ paddingTop: theme.space.s2
680
+ },
681
+ boxFocused: { borderColor: theme.focus.ringColor, backgroundColor: theme.color.surface },
682
+ disabled: { opacity: theme.state.disabledOpacity },
683
+ chips: { gap: theme.space.s2, paddingHorizontal: theme.space.s3, paddingBottom: theme.space.s1 },
684
+ input: {
685
+ minHeight: theme.ctl.md,
686
+ maxHeight: theme.type.body.lineHeight * 8,
687
+ paddingHorizontal: theme.space.s4,
688
+ paddingVertical: theme.space.s1,
689
+ fontFamily: theme.font.sans,
690
+ fontSize: theme.type.body.fontSize,
691
+ lineHeight: theme.type.body.lineHeight,
692
+ color: theme.color.text,
693
+ textAlignVertical: "top"
694
+ },
695
+ bar: { flexDirection: "row", alignItems: "center", gap: theme.space.s1, padding: theme.space.s2 },
696
+ spacer: { flex: 1 },
697
+ tool: {
698
+ width: theme.ctl.iconBtnSm,
699
+ height: theme.ctl.iconBtnSm,
700
+ borderRadius: theme.radius.md,
701
+ alignItems: "center",
702
+ justifyContent: "center"
703
+ },
704
+ toolDown: { backgroundColor: theme.state.pressed },
705
+ send: {
706
+ width: theme.ctl.iconBtnSm,
707
+ height: theme.ctl.iconBtnSm,
708
+ borderRadius: theme.radius.full,
709
+ backgroundColor: theme.color.accent,
710
+ alignItems: "center",
711
+ justifyContent: "center"
712
+ },
713
+ sendIdle: { backgroundColor: theme.color.surface3 },
714
+ glyph: { position: "absolute" },
715
+ hint: {
716
+ textAlign: "center",
717
+ fontFamily: theme.font.sans,
718
+ fontSize: theme.type.caption1.fontSize,
719
+ lineHeight: theme.type.caption1.lineHeight,
720
+ color: theme.color.text3
721
+ }
722
+ }));
723
+
724
+ // src/chat/chat-types.ts
725
+ function chatMessageText(message) {
726
+ return message.parts.filter((p) => p.kind === "text").map((p) => p.text).join("\n\n").trim();
727
+ }
728
+ function AssistantMark() {
729
+ const { styles } = useStyles(sheet3);
730
+ const theme = useLoamTheme("chat");
731
+ return /* @__PURE__ */ jsx(View, { style: styles.mark, children: /* @__PURE__ */ jsx(Icon, { name: "sparkle", size: theme.icon.sm, color: theme.color.accentOnSoft }) });
732
+ }
733
+ function segment(parts) {
734
+ const out = [];
735
+ for (const part of parts) {
736
+ const last = out[out.length - 1];
737
+ if (part.kind === "tool") {
738
+ if (last?.kind === "tools") last.tools.push(part);
739
+ else out.push({ kind: "tools", id: part.id, tools: [part] });
740
+ } else out.push({ kind: "part", part });
741
+ }
742
+ return out;
743
+ }
744
+ function Action({
745
+ label,
746
+ icon,
747
+ onPress,
748
+ pressed
749
+ }) {
750
+ const { styles } = useStyles(sheet3);
751
+ const theme = useLoamTheme("chat");
752
+ return /* @__PURE__ */ jsx(
753
+ Pressable,
754
+ {
755
+ accessibilityRole: "button",
756
+ accessibilityLabel: label,
757
+ accessibilityState: pressed != null ? { selected: pressed } : void 0,
758
+ "aria-pressed": pressed,
759
+ onPress,
760
+ hitSlop: theme.space[6],
761
+ style: ({ pressed: down }) => [styles.action, down && styles.actionDown],
762
+ children: /* @__PURE__ */ jsx(
763
+ Icon,
764
+ {
765
+ name: icon,
766
+ size: theme.icon.sm,
767
+ color: pressed ? theme.color.text : theme.color.text3
768
+ }
769
+ )
770
+ }
771
+ );
772
+ }
773
+ var REPLY_ARM = 56;
774
+ function useSwipeReply(onReply, animate) {
775
+ const theme = useLoamTheme("chat");
776
+ const tx = React6.useRef(new Animated.Value(0)).current;
777
+ const live = React6.useRef({ onReply, animate, theme });
778
+ live.current = { onReply, animate, theme };
779
+ const state = React6.useRef({ armed: false, broke: false });
780
+ const sign = I18nManager.isRTL ? -1 : 1;
781
+ const pan = React6.useRef(
782
+ // One horizontal pull on a bubble inside a vertical list; no gesture
783
+ // library is allowed on native, so `PanResponder`, as `Toast` does.
784
+ // eslint-disable-next-line loam/no-pan-responder
785
+ PanResponder.create({
786
+ onMoveShouldSetPanResponder: (_e, g) => !!live.current.onReply && g.dx * sign > 8 && Math.abs(g.dx) > Math.abs(g.dy) * 1.5,
787
+ onPanResponderGrant: () => {
788
+ tx.stopAnimation();
789
+ state.current = { armed: false, broke: false };
790
+ },
791
+ onPanResponderMove: (_e, g) => {
792
+ const a = Math.max(0, g.dx * sign);
793
+ const s = state.current;
794
+ const armed = a >= REPLY_ARM;
795
+ if (armed && !s.armed) haptics.selection();
796
+ s.armed = armed;
797
+ if (a < BREAKAWAY && !s.broke) return tx.setValue(sign * resist(a));
798
+ const target = sign * (armed ? REPLY_ARM + rubberband(a - REPLY_ARM, REPLY_ARM * 4) : a);
799
+ if (!s.broke && live.current.animate) {
800
+ s.broke = true;
801
+ Animated.spring(tx, {
802
+ toValue: target,
803
+ ...springConfig(live.current.theme, "fs"),
804
+ useNativeDriver: true
805
+ }).start();
806
+ return;
807
+ }
808
+ s.broke = true;
809
+ tx.setValue(target);
810
+ },
811
+ onPanResponderRelease: (_e, g) => {
812
+ const { onReply: reply, animate: anim, theme: th } = live.current;
813
+ if (anim) {
814
+ Animated.spring(tx, {
815
+ toValue: 0,
816
+ ...springConfig(th, "fs", { velocity: g.vx }),
817
+ useNativeDriver: true
818
+ }).start();
819
+ } else tx.setValue(0);
820
+ if (state.current.armed) reply?.();
821
+ state.current.armed = false;
822
+ },
823
+ onPanResponderTerminate: () => {
824
+ state.current.armed = false;
825
+ tx.setValue(0);
826
+ }
827
+ })
828
+ ).current;
829
+ const reveal = tx.interpolate({
830
+ inputRange: sign > 0 ? [0, REPLY_ARM] : [-REPLY_ARM, 0],
831
+ outputRange: sign > 0 ? [0, 1] : [1, 0],
832
+ extrapolate: "clamp"
833
+ });
834
+ const cueScale = tx.interpolate({
835
+ inputRange: sign > 0 ? [0, REPLY_ARM - 0.01, REPLY_ARM] : [-REPLY_ARM, -REPLY_ARM + 0.01, 0],
836
+ outputRange: sign > 0 ? [0.5, 0.9, 1] : [1, 0.9, 0.5],
837
+ extrapolate: "clamp"
838
+ });
839
+ return { tx, panHandlers: onReply ? pan.panHandlers : {}, reveal, cueScale };
840
+ }
841
+ function SwipeReply({
842
+ onReply,
843
+ animate,
844
+ style,
845
+ children
846
+ }) {
847
+ const { styles } = useStyles(sheet3);
848
+ const theme = useLoamTheme("chat");
849
+ const swipe = useSwipeReply(onReply, animate);
850
+ return /* @__PURE__ */ jsxs(View, { style, children: [
851
+ onReply ? /* @__PURE__ */ jsx(
852
+ Animated.View,
853
+ {
854
+ pointerEvents: "none",
855
+ importantForAccessibility: "no-hide-descendants",
856
+ accessibilityElementsHidden: true,
857
+ style: [
858
+ styles.replyCue,
859
+ { opacity: swipe.reveal, transform: [{ scale: swipe.cueScale }] }
860
+ ],
861
+ children: /* @__PURE__ */ jsx(Icon, { name: "undo", size: theme.icon.xs, color: theme.color.text2 })
862
+ }
863
+ ) : null,
864
+ /* @__PURE__ */ jsx(
865
+ Animated.View,
866
+ {
867
+ testID: "chat-swipe-reply",
868
+ style: { transform: [{ translateX: swipe.tx }] },
869
+ ...swipe.panHandlers,
870
+ children
871
+ }
872
+ )
873
+ ] });
874
+ }
875
+ var ChatMessageRow = React6.memo(function ChatMessageRow2(props) {
876
+ return props.message.role === "user" ? /* @__PURE__ */ jsx(UserRow, { ...props }) : /* @__PURE__ */ jsx(AssistantRow, { ...props });
877
+ });
878
+ function UserRow({
879
+ message,
880
+ appearing,
881
+ animate,
882
+ user,
883
+ running,
884
+ onEdit,
885
+ onRetry,
886
+ onCopy,
887
+ onReply,
888
+ labels: labelsProp
889
+ }) {
890
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
891
+ const { styles } = useStyles(sheet3);
892
+ const theme = useLoamTheme("chat");
893
+ const arrival = useArrival(appearing && animate, "sent");
894
+ const [editing, setEditing] = React6.useState(false);
895
+ const text = chatMessageText(message);
896
+ const [draft, setDraft] = React6.useState(text);
897
+ const status = message.status ?? "done";
898
+ const failed = status === "failed";
899
+ const canEdit = !!onEdit && !running && status === "done" && !!text;
900
+ const reply = onReply && status === "done" && !editing ? () => onReply(message) : void 0;
901
+ const save = () => {
902
+ const t = draft.trim();
903
+ setEditing(false);
904
+ if (t && t !== text) onEdit?.(message, t);
905
+ };
906
+ return /* @__PURE__ */ jsxs(
907
+ Animated.View,
908
+ {
909
+ role: "group",
910
+ accessibilityLabel: labels.saidBy(user?.name ?? labels.you),
911
+ accessibilityActions: reply ? [{ name: "reply", label: labels.reply }] : void 0,
912
+ onAccessibilityAction: reply ? (e) => e.nativeEvent.actionName === "reply" && reply() : void 0,
913
+ style: [styles.userRow, arrival],
914
+ children: [
915
+ message.attachments?.length ? /* @__PURE__ */ jsx(View, { style: styles.userAttachments, children: message.attachments.map((a) => /* @__PURE__ */ jsx(AttachmentChip, { attachment: a, labels }, a.id)) }) : null,
916
+ editing ? /* @__PURE__ */ jsxs(View, { style: styles.edit, children: [
917
+ /* @__PURE__ */ jsx(
918
+ TextInput,
919
+ {
920
+ accessibilityLabel: labels.editMessage,
921
+ value: draft,
922
+ onChangeText: setDraft,
923
+ multiline: true,
924
+ autoFocus: true,
925
+ style: styles.editInput,
926
+ placeholderTextColor: theme.color.text3
927
+ }
928
+ ),
929
+ /* @__PURE__ */ jsx(Text, { style: styles.meta, children: labels.editNote }),
930
+ /* @__PURE__ */ jsxs(View, { style: styles.editActions, children: [
931
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onPress: () => setEditing(false), children: labels.cancel }),
932
+ /* @__PURE__ */ jsx(Button, { size: "sm", onPress: save, disabled: !draft.trim(), children: labels.send })
933
+ ] })
934
+ ] }) : text ? /* @__PURE__ */ jsx(SwipeReply, { onReply: reply, animate, style: styles.userSwipe, children: /* @__PURE__ */ jsx(
935
+ Pressable,
936
+ {
937
+ accessibilityHint: canEdit ? labels.editHint : void 0,
938
+ accessibilityActions: canEdit ? [{ name: "edit", label: labels.editMessage }] : void 0,
939
+ onAccessibilityAction: canEdit ? (e) => {
940
+ if (e.nativeEvent.actionName !== "edit") return;
941
+ setDraft(text);
942
+ setEditing(true);
943
+ } : void 0,
944
+ onLongPress: canEdit ? () => {
945
+ setDraft(text);
946
+ setEditing(true);
947
+ } : void 0,
948
+ style: [
949
+ styles.bubble,
950
+ styles.bubbleFill,
951
+ (failed || status === "sending") && styles.faded
952
+ ],
953
+ children: /* @__PURE__ */ jsx(Text, { style: styles.bubbleText, selectable: true, children: text })
954
+ }
955
+ ) }) : null,
956
+ failed ? /* @__PURE__ */ jsxs(View, { style: styles.failed, accessibilityRole: "alert", children: [
957
+ /* @__PURE__ */ jsx(Icon, { name: "alert-circle", size: theme.icon.xs, color: theme.color.danger }),
958
+ /* @__PURE__ */ jsx(Text, { style: [styles.meta, { color: theme.color.danger }], children: labels.notSent }),
959
+ onRetry ? /* @__PURE__ */ jsx(
960
+ Pressable,
961
+ {
962
+ accessibilityRole: "button",
963
+ onPress: () => onRetry(message),
964
+ hitSlop: theme.space[8],
965
+ children: /* @__PURE__ */ jsx(Text, { style: styles.retry, children: labels.retry })
966
+ }
967
+ ) : null
968
+ ] }) : !editing ? /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
969
+ message.time ? /* @__PURE__ */ jsx(Text, { style: styles.meta, children: message.time }) : null,
970
+ text && onCopy ? /* @__PURE__ */ jsx(
971
+ CopyButton,
972
+ {
973
+ value: text,
974
+ size: "sm",
975
+ accessibilityLabel: labels.copyMessage,
976
+ onCopy: () => onCopy(message, text)
977
+ }
978
+ ) : null,
979
+ canEdit ? /* @__PURE__ */ jsx(
980
+ Action,
981
+ {
982
+ label: labels.editMessage,
983
+ icon: "edit",
984
+ onPress: () => {
985
+ setDraft(text);
986
+ setEditing(true);
987
+ }
988
+ }
989
+ ) : null,
990
+ reply ? /* @__PURE__ */ jsx(Action, { label: labels.reply, icon: "undo", onPress: reply }) : null
991
+ ] }) : null
992
+ ]
993
+ }
994
+ );
995
+ }
996
+ function FeedbackPanel({
997
+ reasons,
998
+ animate,
999
+ onSubmit,
1000
+ onCancel,
1001
+ labels
1002
+ }) {
1003
+ const { styles } = useStyles(sheet3);
1004
+ const theme = useLoamTheme("chat");
1005
+ const arrival = useArrival(animate, "card");
1006
+ const [reason, setReason] = React6.useState();
1007
+ const [note, setNote] = React6.useState("");
1008
+ return /* @__PURE__ */ jsxs(
1009
+ Animated.View,
1010
+ {
1011
+ style: [styles.feedback, arrival],
1012
+ role: "group",
1013
+ accessibilityLabel: labels.feedbackTitle,
1014
+ children: [
1015
+ /* @__PURE__ */ jsx(Text, { style: styles.feedbackTitle, children: labels.feedbackTitle }),
1016
+ /* @__PURE__ */ jsx(View, { style: styles.chips, children: reasons.map((r) => {
1017
+ const on = reason === r;
1018
+ return /* @__PURE__ */ jsx(
1019
+ Pressable,
1020
+ {
1021
+ accessibilityRole: "button",
1022
+ accessibilityState: { selected: on },
1023
+ "aria-pressed": on,
1024
+ onPress: () => setReason(on ? void 0 : r),
1025
+ style: [styles.reason, on && styles.reasonOn],
1026
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.reasonText, on && { color: theme.color.accentOnSoft }], children: r })
1027
+ },
1028
+ r
1029
+ );
1030
+ }) }),
1031
+ /* @__PURE__ */ jsx(
1032
+ TextInput,
1033
+ {
1034
+ accessibilityLabel: labels.feedbackNote,
1035
+ placeholder: labels.feedbackNote,
1036
+ placeholderTextColor: theme.color.text3,
1037
+ value: note,
1038
+ onChangeText: setNote,
1039
+ multiline: true,
1040
+ style: styles.note
1041
+ }
1042
+ ),
1043
+ /* @__PURE__ */ jsxs(View, { style: styles.editActions, children: [
1044
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onPress: onCancel, children: labels.cancel }),
1045
+ /* @__PURE__ */ jsx(
1046
+ Button,
1047
+ {
1048
+ size: "sm",
1049
+ disabled: !reason && !note.trim(),
1050
+ onPress: () => onSubmit(reason, note.trim()),
1051
+ children: labels.sendFeedback
1052
+ }
1053
+ )
1054
+ ] })
1055
+ ]
1056
+ }
1057
+ );
1058
+ }
1059
+ function AssistantRow({
1060
+ message,
1061
+ latest,
1062
+ appearing,
1063
+ animate,
1064
+ assistant,
1065
+ feedbackReasons,
1066
+ running,
1067
+ onRegenerate,
1068
+ onFeedback,
1069
+ onCopy,
1070
+ onReply,
1071
+ onLinkPress,
1072
+ labels: labelsProp
1073
+ }) {
1074
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
1075
+ const { styles } = useStyles(sheet3);
1076
+ const theme = useLoamTheme("chat");
1077
+ const arrival = useArrival(appearing && animate, "in");
1078
+ const status = message.status ?? "done";
1079
+ const streaming = status === "streaming";
1080
+ const text = chatMessageText(message);
1081
+ const reply = onReply && !streaming && text ? () => onReply(message) : void 0;
1082
+ const [feedback, setFeedback] = React6.useState(message.feedback);
1083
+ const [asking, setAsking] = React6.useState(false);
1084
+ const [thanked, setThanked] = React6.useState(false);
1085
+ React6.useEffect(() => setFeedback(message.feedback), [message.feedback]);
1086
+ const lastPart = message.parts[message.parts.length - 1];
1087
+ const hasVisible = message.parts.some((p) => p.kind !== "text" || p.text.trim() !== "");
1088
+ const showWorking = streaming && (!hasVisible || lastPart?.kind === "tool" && lastPart.status !== "running");
1089
+ const rate = (value) => {
1090
+ if (feedback === value) return;
1091
+ setFeedback(value);
1092
+ if (value === "up") {
1093
+ setAsking(false);
1094
+ setThanked(true);
1095
+ onFeedback?.(message, { value });
1096
+ } else {
1097
+ setThanked(false);
1098
+ setAsking(true);
1099
+ }
1100
+ };
1101
+ return /* @__PURE__ */ jsxs(
1102
+ Animated.View,
1103
+ {
1104
+ role: "group",
1105
+ accessibilityLabel: labels.saidBy(assistant.name),
1106
+ "aria-busy": streaming || void 0,
1107
+ accessibilityActions: reply ? [{ name: "reply", label: labels.reply }] : void 0,
1108
+ onAccessibilityAction: reply ? (e) => e.nativeEvent.actionName === "reply" && reply() : void 0,
1109
+ style: [styles.assistantRow, arrival],
1110
+ children: [
1111
+ /* @__PURE__ */ jsx(View, { importantForAccessibility: "no-hide-descendants", accessibilityElementsHidden: true, children: assistant.avatar ?? /* @__PURE__ */ jsx(AssistantMark, {}) }),
1112
+ /* @__PURE__ */ jsxs(View, { style: styles.assistantBody, children: [
1113
+ /* @__PURE__ */ jsx(SwipeReply, { onReply: reply, animate, children: /* @__PURE__ */ jsxs(View, { style: styles.assistantParts, children: [
1114
+ segment(message.parts).map((seg) => {
1115
+ if (seg.kind === "tools")
1116
+ return /* @__PURE__ */ jsx(ToolGroupView, { tools: seg.tools, animate, labels }, seg.id);
1117
+ const part = seg.part;
1118
+ switch (part.kind) {
1119
+ case "text":
1120
+ return /* @__PURE__ */ jsx(
1121
+ Markdown,
1122
+ {
1123
+ content: part.text,
1124
+ streaming: streaming && part === lastPart,
1125
+ motion: animate ? "full" : "none",
1126
+ onLinkPress
1127
+ },
1128
+ part.id
1129
+ );
1130
+ case "reasoning":
1131
+ return /* @__PURE__ */ jsx(
1132
+ ReasoningView,
1133
+ {
1134
+ part,
1135
+ live: streaming && part === lastPart,
1136
+ animate,
1137
+ labels
1138
+ },
1139
+ part.id
1140
+ );
1141
+ case "error":
1142
+ return /* @__PURE__ */ jsxs(View, { style: styles.error, accessibilityRole: "alert", children: [
1143
+ /* @__PURE__ */ jsx(
1144
+ Icon,
1145
+ {
1146
+ name: "alert-circle",
1147
+ size: theme.icon.sm,
1148
+ color: theme.color.dangerOnSoft
1149
+ }
1150
+ ),
1151
+ /* @__PURE__ */ jsx(Text, { style: styles.errorText, children: part.message })
1152
+ ] }, part.id);
1153
+ case "custom":
1154
+ return /* @__PURE__ */ jsx(CustomPart, { animate, children: part.content }, part.id);
1155
+ default:
1156
+ return null;
1157
+ }
1158
+ }),
1159
+ showWorking ? /* @__PURE__ */ jsx(
1160
+ WorkingIndicator,
1161
+ {
1162
+ phase: message.phase,
1163
+ startedAt: message.startedAt,
1164
+ animate,
1165
+ labels
1166
+ }
1167
+ ) : null
1168
+ ] }) }),
1169
+ !streaming ? /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
1170
+ status === "stopped" ? /* @__PURE__ */ jsxs(View, { style: styles.stopped, children: [
1171
+ /* @__PURE__ */ jsx(Icon, { name: "stop", size: theme.icon.xs, color: theme.color.text3 }),
1172
+ /* @__PURE__ */ jsx(Text, { style: styles.meta, children: labels.stopped })
1173
+ ] }) : null,
1174
+ text && onCopy ? /* @__PURE__ */ jsx(
1175
+ CopyButton,
1176
+ {
1177
+ value: text,
1178
+ size: "sm",
1179
+ accessibilityLabel: labels.copyResponse,
1180
+ onCopy: () => onCopy(message, text)
1181
+ }
1182
+ ) : null,
1183
+ onFeedback && text ? /* @__PURE__ */ jsxs(Fragment, { children: [
1184
+ /* @__PURE__ */ jsx(
1185
+ Action,
1186
+ {
1187
+ label: labels.goodResponse,
1188
+ icon: "thumbs-up",
1189
+ pressed: feedback === "up",
1190
+ onPress: () => rate("up")
1191
+ }
1192
+ ),
1193
+ /* @__PURE__ */ jsx(
1194
+ Action,
1195
+ {
1196
+ label: labels.badResponse,
1197
+ icon: "thumbs-down",
1198
+ pressed: feedback === "down",
1199
+ onPress: () => rate("down")
1200
+ }
1201
+ )
1202
+ ] }) : null,
1203
+ latest && onRegenerate && !running ? /* @__PURE__ */ jsx(Action, { label: labels.regenerate, icon: "refresh", onPress: () => onRegenerate(message) }) : null,
1204
+ reply ? /* @__PURE__ */ jsx(Action, { label: labels.reply, icon: "undo", onPress: reply }) : null,
1205
+ message.meta || message.time ? /* @__PURE__ */ jsxs(Text, { style: styles.meta, children: [
1206
+ message.meta,
1207
+ message.meta && message.time ? " \xB7 " : null,
1208
+ message.time
1209
+ ] }) : null,
1210
+ thanked ? /* @__PURE__ */ jsx(Text, { style: styles.meta, accessibilityLiveRegion: "polite", role: "status", children: labels.thanks }) : null
1211
+ ] }) : null,
1212
+ asking ? /* @__PURE__ */ jsx(
1213
+ FeedbackPanel,
1214
+ {
1215
+ reasons: feedbackReasons,
1216
+ animate,
1217
+ labels,
1218
+ onCancel: () => {
1219
+ setAsking(false);
1220
+ setFeedback(message.feedback);
1221
+ },
1222
+ onSubmit: (reason, note) => {
1223
+ setAsking(false);
1224
+ setThanked(true);
1225
+ onFeedback?.(message, { value: "down", reason, note: note || void 0 });
1226
+ }
1227
+ }
1228
+ ) : null
1229
+ ] })
1230
+ ]
1231
+ }
1232
+ );
1233
+ }
1234
+ function CustomPart({ animate, children }) {
1235
+ const arrival = useArrival(animate, "card");
1236
+ return /* @__PURE__ */ jsx(Animated.View, { style: arrival, children });
1237
+ }
1238
+ var sheet3 = createStyleSheet((theme) => ({
1239
+ mark: {
1240
+ width: theme.ctl.sm,
1241
+ height: theme.ctl.sm,
1242
+ borderRadius: theme.radius.md,
1243
+ backgroundColor: theme.color.accentSoft,
1244
+ borderWidth: theme.borderWidth.base,
1245
+ borderColor: theme.edge.accent,
1246
+ alignItems: "center",
1247
+ justifyContent: "center"
1248
+ },
1249
+ userRow: { alignItems: "flex-end", gap: theme.space[6] },
1250
+ userAttachments: {
1251
+ flexDirection: "row",
1252
+ flexWrap: "wrap",
1253
+ justifyContent: "flex-end",
1254
+ gap: theme.space.s2
1255
+ },
1256
+ bubble: {
1257
+ maxWidth: "85%",
1258
+ backgroundColor: theme.color.surface3,
1259
+ borderRadius: theme.radius.xl,
1260
+ borderBottomRightRadius: theme.radius.xs,
1261
+ paddingHorizontal: theme.space.s4,
1262
+ paddingVertical: theme.space[10]
1263
+ },
1264
+ // Sending / failed: a hint of "not yet delivered", never `disabledOpacity`
1265
+ // — at 0.38 the message itself fell to ~2:1 and could not be read back.
1266
+ // Same 0.72 as the web bubble.
1267
+ faded: { opacity: theme.opacity[72] },
1268
+ bubbleText: {
1269
+ fontFamily: theme.font.sans,
1270
+ fontSize: theme.type.body.fontSize,
1271
+ lineHeight: theme.type.body.lineHeight,
1272
+ color: theme.color.text
1273
+ },
1274
+ failed: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
1275
+ retry: {
1276
+ fontFamily: theme.font.sans,
1277
+ fontSize: theme.type.caption1.fontSize,
1278
+ fontWeight: theme.fontWeight.bold,
1279
+ color: theme.color.danger,
1280
+ textDecorationLine: "underline"
1281
+ },
1282
+ edit: { alignSelf: "stretch", gap: theme.space.s2 },
1283
+ editInput: {
1284
+ minHeight: theme.ctl.lg * 2,
1285
+ fontFamily: theme.font.sans,
1286
+ fontSize: theme.type.body.fontSize,
1287
+ lineHeight: theme.type.body.lineHeight,
1288
+ color: theme.color.text,
1289
+ backgroundColor: theme.color.surface2,
1290
+ borderWidth: theme.borderWidth.strong,
1291
+ borderColor: theme.focus.ringColor,
1292
+ borderRadius: theme.radius.md,
1293
+ padding: theme.space.s3,
1294
+ textAlignVertical: "top"
1295
+ },
1296
+ editActions: { flexDirection: "row", justifyContent: "flex-end", gap: theme.space.s2 },
1297
+ actions: { flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: theme.space.s1 },
1298
+ action: {
1299
+ width: theme.ctl.iconBtnSm,
1300
+ height: theme.ctl.iconBtnSm,
1301
+ borderRadius: theme.radius.md,
1302
+ alignItems: "center",
1303
+ justifyContent: "center"
1304
+ },
1305
+ actionDown: { backgroundColor: theme.state.pressed },
1306
+ meta: {
1307
+ fontFamily: theme.font.sans,
1308
+ fontSize: theme.type.caption1.fontSize,
1309
+ lineHeight: theme.type.caption1.lineHeight,
1310
+ color: theme.color.text3
1311
+ },
1312
+ stopped: {
1313
+ flexDirection: "row",
1314
+ alignItems: "center",
1315
+ gap: theme.space[6],
1316
+ marginRight: theme.space.s2
1317
+ },
1318
+ assistantRow: { flexDirection: "row", gap: theme.space.s3 },
1319
+ assistantBody: { flex: 1, minWidth: 0, gap: theme.space.s2 },
1320
+ assistantParts: { gap: theme.space.s2 },
1321
+ userSwipe: { maxWidth: "85%" },
1322
+ bubbleFill: { maxWidth: "100%" },
1323
+ replyCue: {
1324
+ position: "absolute",
1325
+ start: -theme.ctl.sm,
1326
+ top: "50%",
1327
+ marginTop: -theme.icon.sm,
1328
+ width: theme.icon.sm * 2,
1329
+ height: theme.icon.sm * 2,
1330
+ borderRadius: theme.radius.full,
1331
+ backgroundColor: theme.color.surface3,
1332
+ alignItems: "center",
1333
+ justifyContent: "center"
1334
+ },
1335
+ error: {
1336
+ flexDirection: "row",
1337
+ gap: theme.space.s2,
1338
+ padding: theme.space.s3,
1339
+ borderRadius: theme.radius.md,
1340
+ borderWidth: theme.borderWidth.base,
1341
+ borderColor: theme.edge.danger,
1342
+ backgroundColor: theme.color.dangerSoft
1343
+ },
1344
+ errorText: {
1345
+ flex: 1,
1346
+ fontFamily: theme.font.sans,
1347
+ fontSize: theme.type.footnote.fontSize,
1348
+ lineHeight: theme.type.footnote.lineHeight,
1349
+ color: theme.color.dangerOnSoft
1350
+ },
1351
+ feedback: {
1352
+ gap: theme.space.s3,
1353
+ padding: theme.space.s3,
1354
+ borderRadius: theme.radius.lg,
1355
+ borderWidth: theme.borderWidth.base,
1356
+ borderColor: theme.color.border,
1357
+ backgroundColor: theme.color.surface
1358
+ },
1359
+ feedbackTitle: {
1360
+ fontFamily: theme.font.sans,
1361
+ fontSize: theme.type.footnote.fontSize,
1362
+ fontWeight: theme.fontWeight.bold,
1363
+ color: theme.color.text
1364
+ },
1365
+ chips: { flexDirection: "row", flexWrap: "wrap", gap: theme.space.s2 },
1366
+ reason: {
1367
+ paddingHorizontal: theme.space.s3,
1368
+ paddingVertical: theme.space[6],
1369
+ borderRadius: theme.radius.full,
1370
+ borderWidth: theme.borderWidth.base,
1371
+ borderColor: theme.color.border
1372
+ },
1373
+ reasonOn: { backgroundColor: theme.color.accentSoft, borderColor: theme.edge.accent },
1374
+ reasonText: {
1375
+ fontFamily: theme.font.sans,
1376
+ fontSize: theme.type.caption1.fontSize,
1377
+ color: theme.color.text2
1378
+ },
1379
+ note: {
1380
+ minHeight: theme.ctl.lg * 1.5,
1381
+ fontFamily: theme.font.sans,
1382
+ fontSize: theme.type.footnote.fontSize,
1383
+ color: theme.color.text,
1384
+ backgroundColor: theme.color.surface2,
1385
+ borderWidth: theme.borderWidth.base,
1386
+ borderColor: theme.color.borderStrong,
1387
+ borderRadius: theme.radius.md,
1388
+ padding: theme.space.s3,
1389
+ textAlignVertical: "top"
1390
+ }
1391
+ }));
1392
+ var keyOf = (s) => s.id ?? s.label;
1393
+ function paginate(list, perPage) {
1394
+ if (list.some((s) => s.page !== void 0)) {
1395
+ const by = /* @__PURE__ */ new Map();
1396
+ for (const s of list) {
1397
+ const p = s.page ?? 0;
1398
+ by.set(p, [...by.get(p) ?? [], s]);
1399
+ }
1400
+ return [...by.keys()].sort((a, b) => a - b).map((p) => by.get(p));
1401
+ }
1402
+ const size = Math.max(1, perPage);
1403
+ const out = [];
1404
+ for (let i = 0; i < list.length; i += size) out.push(list.slice(i, i + size));
1405
+ return out;
1406
+ }
1407
+ function sectionsOf(page) {
1408
+ const out = [];
1409
+ for (const s of page) {
1410
+ const hit = out.find((g) => g.title === s.group);
1411
+ if (hit) hit.items.push(s);
1412
+ else out.push({ title: s.group, items: [s] });
1413
+ }
1414
+ return out;
1415
+ }
1416
+ function ChatSuggestions({
1417
+ suggestions,
1418
+ perPage,
1419
+ onPick,
1420
+ onDismiss,
1421
+ loading = false,
1422
+ animate,
1423
+ labels: labelsProp
1424
+ }) {
1425
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
1426
+ const { styles } = useStyles(sheet4);
1427
+ const theme = useLoamTheme("chat");
1428
+ const [hidden, setHidden] = React6.useState(() => /* @__PURE__ */ new Set());
1429
+ const pages = React6.useMemo(
1430
+ () => paginate(suggestions.filter((s) => !hidden.has(keyOf(s))), perPage),
1431
+ [suggestions, hidden, perPage]
1432
+ );
1433
+ const [index, setIndex] = React6.useState(0);
1434
+ const [visit, setVisit] = React6.useState(0);
1435
+ const current = Math.min(index, Math.max(0, pages.length - 1));
1436
+ const [width, setWidth] = React6.useState(0);
1437
+ const scroller = React6.useRef(null);
1438
+ const settle = (next) => {
1439
+ if (next === current) return;
1440
+ setIndex(next);
1441
+ setVisit((v) => v + 1);
1442
+ };
1443
+ const goTo = (next) => {
1444
+ const clamped = Math.max(0, Math.min(pages.length - 1, next));
1445
+ scroller.current?.scrollTo({ x: clamped * width, animated: animate });
1446
+ settle(clamped);
1447
+ };
1448
+ const onScrollEnd = (e) => {
1449
+ if (width > 0) settle(Math.round(e.nativeEvent.contentOffset.x / width));
1450
+ };
1451
+ const dismiss = (s) => {
1452
+ if (animate) {
1453
+ LayoutAnimation.configureNext(
1454
+ LayoutAnimation.create(theme.motion.collapse.duration, LayoutAnimation.Types.easeInEaseOut, LayoutAnimation.Properties.opacity)
1455
+ );
1456
+ }
1457
+ setHidden((prev) => new Set(prev).add(keyOf(s)));
1458
+ onDismiss?.(s);
1459
+ };
1460
+ if (loading) {
1461
+ return (
1462
+ // One "Loading" for the whole card (the SkeletonText pattern); the bones
1463
+ // are decorative, or a screen reader says "Loading" a dozen times.
1464
+ /* @__PURE__ */ jsx(
1465
+ View,
1466
+ {
1467
+ style: styles.card,
1468
+ accessible: true,
1469
+ accessibilityRole: "image",
1470
+ accessibilityLabel: labels.loadingSuggestions,
1471
+ accessibilityState: { busy: true },
1472
+ children: Array.from({ length: Math.min(perPage, 4) }, (_, i) => /* @__PURE__ */ jsxs(View, { style: styles.skeletonRow, children: [
1473
+ /* @__PURE__ */ jsx(
1474
+ Skeleton,
1475
+ {
1476
+ variant: "rect",
1477
+ width: theme.icon.sm,
1478
+ height: theme.icon.sm,
1479
+ radius: "sm",
1480
+ decorative: true
1481
+ }
1482
+ ),
1483
+ /* @__PURE__ */ jsxs(View, { style: styles.rowText, children: [
1484
+ /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: theme.ctl.md * 4, decorative: true }),
1485
+ /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: theme.ctl.md * 2.5, decorative: true })
1486
+ ] })
1487
+ ] }, i))
1488
+ }
1489
+ )
1490
+ );
1491
+ }
1492
+ if (pages.length === 0) return null;
1493
+ return /* @__PURE__ */ jsxs(View, { style: styles.card, onLayout: (e) => setWidth(e.nativeEvent.layout.width - theme.space.s2 * 2), children: [
1494
+ /* @__PURE__ */ jsx(
1495
+ ScrollView,
1496
+ {
1497
+ ref: scroller,
1498
+ horizontal: true,
1499
+ pagingEnabled: true,
1500
+ scrollEnabled: pages.length > 1,
1501
+ showsHorizontalScrollIndicator: false,
1502
+ onMomentumScrollEnd: onScrollEnd,
1503
+ children: pages.map((page, p) => /* @__PURE__ */ jsx(
1504
+ View,
1505
+ {
1506
+ style: width ? { width } : styles.fill,
1507
+ accessibilityElementsHidden: p !== current,
1508
+ importantForAccessibility: p === current ? "auto" : "no-hide-descendants",
1509
+ children: /* @__PURE__ */ jsx(
1510
+ Page,
1511
+ {
1512
+ page,
1513
+ animate: animate && p === current,
1514
+ onPick,
1515
+ onDismiss: onDismiss ? dismiss : void 0,
1516
+ labels
1517
+ }
1518
+ )
1519
+ },
1520
+ p === current ? `on-${visit}` : `off-${p}`
1521
+ ))
1522
+ }
1523
+ ),
1524
+ pages.length > 1 ? /* @__PURE__ */ jsxs(View, { style: styles.pager, role: "group", accessibilityLabel: labels.moreSuggestions, children: [
1525
+ /* @__PURE__ */ jsx(PagerButton, { icon: "chevron-left", label: labels.previousSuggestions, disabled: current === 0, onPress: () => goTo(current - 1) }),
1526
+ /* @__PURE__ */ jsx(View, { style: styles.dots, children: pages.map((_, p) => /* @__PURE__ */ jsx(
1527
+ Dot,
1528
+ {
1529
+ index: p,
1530
+ count: pages.length,
1531
+ active: p === current,
1532
+ animate,
1533
+ onPress: () => goTo(p),
1534
+ labels
1535
+ },
1536
+ p
1537
+ )) }),
1538
+ /* @__PURE__ */ jsx(
1539
+ PagerButton,
1540
+ {
1541
+ icon: "chevron-right",
1542
+ label: labels.moreSuggestions,
1543
+ disabled: current === pages.length - 1,
1544
+ onPress: () => goTo(current + 1)
1545
+ }
1546
+ )
1547
+ ] }) : null
1548
+ ] });
1549
+ }
1550
+ function Page({
1551
+ page,
1552
+ animate,
1553
+ onPick,
1554
+ onDismiss,
1555
+ labels
1556
+ }) {
1557
+ const { styles } = useStyles(sheet4);
1558
+ let n = 0;
1559
+ return /* @__PURE__ */ jsx(View, { children: sectionsOf(page).map((section, si) => /* @__PURE__ */ jsxs(View, { style: si > 0 ? styles.sectionRule : void 0, children: [
1560
+ section.title ? /* @__PURE__ */ jsx(Rise, { i: n, animate, children: /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: styles.sectionTitle, children: section.title }) }) : null,
1561
+ section.items.map((s) => /* @__PURE__ */ jsx(Rise, { i: ++n, animate, children: /* @__PURE__ */ jsx(Row, { suggestion: s, onPick, onDismiss, labels }) }, keyOf(s)))
1562
+ ] }, section.title ?? `untitled-${si}`)) });
1563
+ }
1564
+ function Rise({ i, animate, children }) {
1565
+ const theme = useLoamTheme("chat");
1566
+ const style = useArrival(animate, "in", i * theme.duration.fast * 0.27);
1567
+ return /* @__PURE__ */ jsx(Animated.View, { style, children });
1568
+ }
1569
+ function Row({
1570
+ suggestion: s,
1571
+ onPick,
1572
+ onDismiss,
1573
+ labels
1574
+ }) {
1575
+ const { styles } = useStyles(sheet4);
1576
+ const theme = useLoamTheme("chat");
1577
+ const tone = {
1578
+ neutral: theme.color.text3,
1579
+ accent: theme.color.accent,
1580
+ good: theme.color.good,
1581
+ warn: theme.color.warn,
1582
+ danger: theme.color.danger
1583
+ };
1584
+ const swap = React6.useRef(new Animated.Value(0)).current;
1585
+ const fade = React6.useRef(new Animated.Value(1)).current;
1586
+ const to = (v) => Animated.timing(swap, {
1587
+ toValue: v,
1588
+ duration: theme.motion.swap.duration,
1589
+ easing: Easing.bezier(...theme.motion.swap.easing),
1590
+ useNativeDriver: true
1591
+ }).start();
1592
+ const lift = theme.space[3];
1593
+ const leave = () => {
1594
+ Animated.timing(fade, {
1595
+ toValue: 0,
1596
+ duration: theme.motion.exit.duration,
1597
+ easing: Easing.bezier(...theme.motion.exit.easing),
1598
+ useNativeDriver: true
1599
+ }).start(() => onDismiss?.(s));
1600
+ };
1601
+ return /* @__PURE__ */ jsxs(Animated.View, { style: [styles.row, { opacity: fade }], children: [
1602
+ /* @__PURE__ */ jsxs(
1603
+ Pressable,
1604
+ {
1605
+ accessibilityRole: "button",
1606
+ accessibilityLabel: s.label,
1607
+ accessibilityHint: [s.description, s.reason && labels.why(s.reason)].filter(Boolean).join(". ") || void 0,
1608
+ onPress: () => onPick(s),
1609
+ onPressIn: s.reason ? () => to(1) : void 0,
1610
+ onPressOut: s.reason ? () => to(0) : void 0,
1611
+ style: ({ pressed }) => [styles.rowPress, pressed && styles.rowPressed],
1612
+ children: [
1613
+ /* @__PURE__ */ jsx(Icon, { name: s.icon ?? "sparkle", size: theme.icon.xs, color: tone[s.tone ?? "neutral"] }),
1614
+ /* @__PURE__ */ jsxs(View, { style: styles.rowText, children: [
1615
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: styles.label, children: s.label }),
1616
+ s.description || s.reason ? /* @__PURE__ */ jsxs(View, { children: [
1617
+ /* @__PURE__ */ jsx(
1618
+ Animated.Text,
1619
+ {
1620
+ numberOfLines: 1,
1621
+ style: [
1622
+ styles.meta,
1623
+ s.reason && {
1624
+ opacity: swap.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),
1625
+ transform: [{ translateY: swap.interpolate({ inputRange: [0, 1], outputRange: [0, -lift] }) }]
1626
+ }
1627
+ ],
1628
+ children: s.description ?? " "
1629
+ }
1630
+ ),
1631
+ s.reason ? /* @__PURE__ */ jsx(
1632
+ Animated.Text,
1633
+ {
1634
+ numberOfLines: 1,
1635
+ importantForAccessibility: "no",
1636
+ accessibilityElementsHidden: true,
1637
+ style: [
1638
+ styles.meta,
1639
+ styles.reason,
1640
+ {
1641
+ opacity: swap,
1642
+ transform: [{ translateY: swap.interpolate({ inputRange: [0, 1], outputRange: [lift, 0] }) }]
1643
+ }
1644
+ ],
1645
+ children: s.reason
1646
+ }
1647
+ ) : null
1648
+ ] }) : null
1649
+ ] })
1650
+ ]
1651
+ }
1652
+ ),
1653
+ onDismiss ? /* @__PURE__ */ jsx(
1654
+ Pressable,
1655
+ {
1656
+ accessibilityRole: "button",
1657
+ accessibilityLabel: labels.notUsefulFor(s.label),
1658
+ onPress: leave,
1659
+ hitSlop: theme.space[8],
1660
+ style: ({ pressed }) => [styles.dismiss, pressed && styles.rowPressed],
1661
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.xs, color: theme.color.text3 })
1662
+ }
1663
+ ) : null
1664
+ ] });
1665
+ }
1666
+ function Dot({
1667
+ index,
1668
+ count,
1669
+ active,
1670
+ animate,
1671
+ onPress,
1672
+ labels
1673
+ }) {
1674
+ const { styles } = useStyles(sheet4);
1675
+ const theme = useLoamTheme("chat");
1676
+ const p = React6.useRef(new Animated.Value(active ? 1 : 0)).current;
1677
+ React6.useEffect(() => {
1678
+ Animated.timing(p, {
1679
+ toValue: active ? 1 : 0,
1680
+ duration: animate ? theme.motion.indicator.duration : 0,
1681
+ easing: Easing.bezier(...theme.motion.indicator.easing),
1682
+ useNativeDriver: false
1683
+ }).start();
1684
+ }, [active, animate, p, theme.motion.indicator]);
1685
+ const dot = theme.space[6];
1686
+ return /* @__PURE__ */ jsx(
1687
+ Pressable,
1688
+ {
1689
+ accessibilityRole: "button",
1690
+ accessibilityLabel: labels.suggestionsPage(index + 1, count),
1691
+ accessibilityState: { selected: active },
1692
+ onPress,
1693
+ hitSlop: theme.space[8],
1694
+ children: /* @__PURE__ */ jsx(
1695
+ Animated.View,
1696
+ {
1697
+ style: [
1698
+ styles.dot,
1699
+ {
1700
+ width: p.interpolate({ inputRange: [0, 1], outputRange: [dot, dot * 2.67] }),
1701
+ backgroundColor: p.interpolate({ inputRange: [0, 1], outputRange: [theme.color.borderStrong, theme.color.accent] })
1702
+ }
1703
+ ]
1704
+ }
1705
+ )
1706
+ }
1707
+ );
1708
+ }
1709
+ function PagerButton({
1710
+ icon,
1711
+ label,
1712
+ disabled,
1713
+ onPress
1714
+ }) {
1715
+ const { styles } = useStyles(sheet4);
1716
+ const theme = useLoamTheme("chat");
1717
+ return /* @__PURE__ */ jsx(
1718
+ Pressable,
1719
+ {
1720
+ accessibilityRole: "button",
1721
+ accessibilityLabel: label,
1722
+ accessibilityState: { disabled },
1723
+ disabled,
1724
+ onPress,
1725
+ hitSlop: theme.space[8],
1726
+ style: ({ pressed }) => [styles.pagerButton, pressed && styles.rowPressed, disabled && styles.disabled],
1727
+ children: /* @__PURE__ */ jsx(Icon, { name: icon, size: theme.icon.sm, color: theme.color.text3 })
1728
+ }
1729
+ );
1730
+ }
1731
+ var sheet4 = createStyleSheet((theme) => ({
1732
+ card: {
1733
+ alignSelf: "stretch",
1734
+ padding: theme.space.s2,
1735
+ borderRadius: theme.radius.xl,
1736
+ borderWidth: theme.borderWidth.base,
1737
+ borderColor: theme.color.border,
1738
+ backgroundColor: theme.color.surface,
1739
+ overflow: "hidden"
1740
+ },
1741
+ fill: { flex: 1 },
1742
+ sectionRule: {
1743
+ marginTop: theme.space.s1,
1744
+ paddingTop: theme.space.s1,
1745
+ borderTopWidth: theme.borderWidth.base,
1746
+ borderTopColor: theme.color.border
1747
+ },
1748
+ sectionTitle: {
1749
+ paddingHorizontal: theme.space.s3,
1750
+ paddingTop: theme.space.s2,
1751
+ paddingBottom: theme.space.s1,
1752
+ fontFamily: theme.font.sans,
1753
+ fontSize: theme.type.caption1.fontSize,
1754
+ lineHeight: theme.type.caption1.lineHeight,
1755
+ fontWeight: theme.fontWeight.bold,
1756
+ color: theme.color.text3
1757
+ },
1758
+ row: { flexDirection: "row", alignItems: "center", borderRadius: theme.radius.lg },
1759
+ rowPress: {
1760
+ flex: 1,
1761
+ flexDirection: "row",
1762
+ alignItems: "center",
1763
+ gap: theme.space.s3,
1764
+ minHeight: theme.ctl.md,
1765
+ paddingHorizontal: theme.space.s3,
1766
+ paddingVertical: theme.space.s2,
1767
+ borderRadius: theme.radius.lg,
1768
+ borderWidth: theme.borderWidth.base,
1769
+ borderColor: "transparent"
1770
+ },
1771
+ rowPressed: { backgroundColor: theme.color.accentSoft, borderColor: theme.edge.accent },
1772
+ rowText: { flex: 1, gap: theme.space[2] },
1773
+ label: {
1774
+ fontFamily: theme.font.sans,
1775
+ fontSize: theme.type.footnote.fontSize,
1776
+ lineHeight: theme.type.footnote.lineHeight,
1777
+ fontWeight: theme.fontWeight.bold,
1778
+ color: theme.color.text
1779
+ },
1780
+ meta: {
1781
+ fontFamily: theme.font.sans,
1782
+ fontSize: theme.type.caption1.fontSize,
1783
+ lineHeight: theme.type.caption1.lineHeight,
1784
+ color: theme.color.text3
1785
+ },
1786
+ reason: { position: "absolute", left: 0, right: 0, top: 0, color: theme.color.accent },
1787
+ dismiss: {
1788
+ width: theme.ctl.iconBtnSm,
1789
+ height: theme.ctl.iconBtnSm,
1790
+ borderRadius: theme.radius.md,
1791
+ alignItems: "center",
1792
+ justifyContent: "center"
1793
+ },
1794
+ skeletonRow: { flexDirection: "row", alignItems: "center", gap: theme.space.s3, padding: theme.space.s3 },
1795
+ pager: {
1796
+ flexDirection: "row",
1797
+ alignItems: "center",
1798
+ justifyContent: "space-between",
1799
+ marginTop: theme.space.s1,
1800
+ paddingTop: theme.space.s1,
1801
+ borderTopWidth: theme.borderWidth.base,
1802
+ borderTopColor: theme.color.border
1803
+ },
1804
+ pagerButton: {
1805
+ width: theme.ctl.iconBtnSm,
1806
+ height: theme.ctl.iconBtnSm,
1807
+ borderRadius: theme.radius.md,
1808
+ alignItems: "center",
1809
+ justifyContent: "center"
1810
+ },
1811
+ disabled: { opacity: theme.state.disabledOpacity },
1812
+ dots: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
1813
+ dot: { height: theme.space[6], borderRadius: theme.radius.full }
1814
+ }));
1815
+ function useChatScroll({ threshold = 32 } = {}) {
1816
+ const ref = React6.useRef(null);
1817
+ const pinnedRef = React6.useRef(true);
1818
+ const [pinned, setPinnedState] = React6.useState(true);
1819
+ const driving = React6.useRef({ drag: false, momentum: false });
1820
+ const setPinned = React6.useCallback((next) => {
1821
+ if (pinnedRef.current === next) return;
1822
+ pinnedRef.current = next;
1823
+ setPinnedState(next);
1824
+ }, []);
1825
+ const scrollToBottom = React6.useCallback(
1826
+ (animated = true) => {
1827
+ setPinned(true);
1828
+ ref.current?.scrollToEnd({ animated });
1829
+ },
1830
+ [setPinned]
1831
+ );
1832
+ const follow = React6.useCallback(() => {
1833
+ if (pinnedRef.current) ref.current?.scrollToEnd({ animated: false });
1834
+ }, []);
1835
+ const scrollProps = React6.useMemo(
1836
+ () => ({
1837
+ ref,
1838
+ scrollEventThrottle: 16,
1839
+ onScroll: (e) => {
1840
+ const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
1841
+ const distance = contentSize.height - contentOffset.y - layoutMeasurement.height;
1842
+ if (distance <= threshold) setPinned(true);
1843
+ else if (driving.current.drag || driving.current.momentum) setPinned(false);
1844
+ },
1845
+ onScrollBeginDrag: () => {
1846
+ driving.current.drag = true;
1847
+ },
1848
+ onScrollEndDrag: () => {
1849
+ driving.current.drag = false;
1850
+ },
1851
+ onMomentumScrollBegin: () => {
1852
+ driving.current.momentum = true;
1853
+ },
1854
+ onMomentumScrollEnd: () => {
1855
+ driving.current.momentum = false;
1856
+ },
1857
+ onContentSizeChange: follow,
1858
+ onLayout: follow
1859
+ }),
1860
+ [follow, setPinned, threshold]
1861
+ );
1862
+ return { pinned, scrollToBottom, scrollProps };
1863
+ }
1864
+ var CHAT_FEEDBACK_REASONS = DEFAULT_CHAT_LABELS.feedbackReasons;
1865
+ function useArrivals(messages) {
1866
+ const seen = React6.useRef(null);
1867
+ const prevLast = React6.useRef(void 0);
1868
+ if (seen.current === null) {
1869
+ seen.current = new Set(messages.map((m) => m.id));
1870
+ prevLast.current = messages[messages.length - 1]?.id;
1871
+ }
1872
+ const boundary = prevLast.current ? messages.findIndex((m) => m.id === prevLast.current) : -1;
1873
+ const arriving = /* @__PURE__ */ new Set();
1874
+ messages.forEach((m, i) => {
1875
+ if (!seen.current.has(m.id) && i > boundary) arriving.add(m.id);
1876
+ });
1877
+ React6.useEffect(() => {
1878
+ for (const m of messages) seen.current.add(m.id);
1879
+ prevLast.current = messages[messages.length - 1]?.id;
1880
+ }, [messages]);
1881
+ return (id) => arriving.has(id);
1882
+ }
1883
+ function Staggered({
1884
+ i,
1885
+ animate,
1886
+ children
1887
+ }) {
1888
+ const theme = useLoamTheme("chat");
1889
+ const style = useArrival(animate, "in", i * theme.duration.fast * 0.27);
1890
+ return /* @__PURE__ */ jsx(Animated.View, { style, children });
1891
+ }
1892
+ function Chat({
1893
+ messages,
1894
+ running = false,
1895
+ onSend,
1896
+ onStop,
1897
+ onRegenerate,
1898
+ onEdit,
1899
+ onRetry,
1900
+ onFeedback,
1901
+ onCopy,
1902
+ onReply,
1903
+ feedbackReasons: feedbackReasonsProp,
1904
+ assistant: assistantProp,
1905
+ user,
1906
+ suggestions,
1907
+ suggestionAction = "send",
1908
+ suggestionsPerPage = 4,
1909
+ suggestionsLoading = false,
1910
+ onDismissSuggestion,
1911
+ followUps,
1912
+ emptyTitle: emptyTitleProp,
1913
+ emptyDescription,
1914
+ emptyState,
1915
+ hasOlder = false,
1916
+ loadingOlder = false,
1917
+ onLoadOlder,
1918
+ draft: draftProp,
1919
+ onDraftChange,
1920
+ placeholder,
1921
+ attachments,
1922
+ onAttachPress,
1923
+ onRemoveAttachment,
1924
+ composerToolbar,
1925
+ disclaimer,
1926
+ disabled,
1927
+ header,
1928
+ onLinkPress,
1929
+ keyboardOffset = 0,
1930
+ label: labelProp,
1931
+ motion = "full",
1932
+ style,
1933
+ labels: labelsProp
1934
+ }) {
1935
+ const labels = useLabels("chat", DEFAULT_CHAT_LABELS, labelsProp);
1936
+ const assistant = React6.useMemo(
1937
+ () => assistantProp ?? { name: labels.assistant },
1938
+ [assistantProp, labels.assistant]
1939
+ );
1940
+ const feedbackReasons = feedbackReasonsProp ?? labels.feedbackReasons;
1941
+ const emptyTitle = emptyTitleProp ?? labels.emptyTitle;
1942
+ const label = labelProp ?? labels.chat;
1943
+ const { styles } = useStyles(sheet5);
1944
+ const theme = useLoamTheme("chat");
1945
+ const animate = useResolvedMotion(motion, "chat") === "full";
1946
+ const { pinned, scrollToBottom, scrollProps } = useChatScroll();
1947
+ const { readingMeasure } = useFormFactor();
1948
+ const column = {
1949
+ width: "100%",
1950
+ maxWidth: readingMeasure + theme.ctl.sm + theme.space.s3 + theme.space.s4 * 2,
1951
+ alignSelf: "center"
1952
+ };
1953
+ const isArriving = useArrivals(messages);
1954
+ const inputRef = React6.useRef(null);
1955
+ const [innerDraft, setInnerDraft] = React6.useState("");
1956
+ const draft = draftProp ?? innerDraft;
1957
+ const setDraft = (next) => {
1958
+ if (draftProp === void 0) setInnerDraft(next);
1959
+ onDraftChange?.(next);
1960
+ };
1961
+ const last = messages[messages.length - 1];
1962
+ const latestAssistantId = [...messages].reverse().find((m) => m.role === "assistant")?.id;
1963
+ const [replyingTo, setReplyingTo] = React6.useState(null);
1964
+ const reply = onReply ? (m) => {
1965
+ setReplyingTo(m);
1966
+ onReply(m);
1967
+ inputRef.current?.focus();
1968
+ } : void 0;
1969
+ const send = (text, files) => {
1970
+ if (replyingTo) onSend(text, files, replyingTo);
1971
+ else onSend(text, files);
1972
+ setReplyingTo(null);
1973
+ scrollToBottom();
1974
+ };
1975
+ const pick = (s) => {
1976
+ const prompt = s.prompt ?? s.label;
1977
+ if (suggestionAction === "fill") {
1978
+ setDraft(prompt);
1979
+ inputRef.current?.focus();
1980
+ } else send(prompt, []);
1981
+ };
1982
+ const [unseen, setUnseen] = React6.useState(false);
1983
+ const prevCount = React6.useRef(messages.length);
1984
+ React6.useEffect(() => {
1985
+ if (pinned) setUnseen(false);
1986
+ else if (messages.length > prevCount.current) setUnseen(true);
1987
+ prevCount.current = messages.length;
1988
+ }, [messages.length, pinned]);
1989
+ const pillLabel = running ? labels.writing : unseen ? labels.newMessages : labels.jumpToLatest;
1990
+ const pill = React6.useRef(new Animated.Value(pinned ? 0 : 1)).current;
1991
+ React6.useEffect(() => {
1992
+ Animated.timing(pill, {
1993
+ toValue: pinned ? 0 : 1,
1994
+ duration: animate ? theme.motion.enter.duration : 0,
1995
+ useNativeDriver: true
1996
+ }).start();
1997
+ }, [pinned, animate, pill, theme.motion.enter.duration]);
1998
+ const streamingId = messages.find((m) => m.status === "streaming")?.id;
1999
+ const prevStreaming = React6.useRef(streamingId);
2000
+ const prevRunning = React6.useRef(running);
2001
+ React6.useEffect(() => {
2002
+ if (running && !prevRunning.current) {
2003
+ AccessibilityInfo.announceForAccessibility?.(labels.responding(assistant.name));
2004
+ }
2005
+ if (prevStreaming.current && prevStreaming.current !== streamingId) {
2006
+ const m = messages.find((x) => x.id === prevStreaming.current);
2007
+ if (m)
2008
+ AccessibilityInfo.announceForAccessibility?.(labels.said(assistant.name, chatMessageText(m)));
2009
+ }
2010
+ prevStreaming.current = streamingId;
2011
+ prevRunning.current = running;
2012
+ }, [running, streamingId, messages, assistant.name, labels]);
2013
+ const pendingReply = running && last?.role === "user";
2014
+ const showFollowUps = !running && !!followUps?.length && last?.role === "assistant" && (last.status ?? "done") === "done";
2015
+ return /* @__PURE__ */ jsxs(
2016
+ KeyboardAvoidingView,
2017
+ {
2018
+ accessibilityLabel: label,
2019
+ behavior: Platform.OS === "ios" ? "padding" : void 0,
2020
+ keyboardVerticalOffset: keyboardOffset,
2021
+ style: [styles.root, style],
2022
+ children: [
2023
+ header,
2024
+ /* @__PURE__ */ jsxs(View, { style: styles.stage, children: [
2025
+ /* @__PURE__ */ jsxs(
2026
+ ScrollView,
2027
+ {
2028
+ ...scrollProps,
2029
+ accessibilityLabel: labels.messages,
2030
+ keyboardShouldPersistTaps: "handled",
2031
+ keyboardDismissMode: "interactive",
2032
+ contentContainerStyle: [styles.content, column],
2033
+ onScroll: (e) => {
2034
+ scrollProps.onScroll(e);
2035
+ if (hasOlder && !loadingOlder && e.nativeEvent.contentOffset.y < theme.space.s8)
2036
+ onLoadOlder?.();
2037
+ },
2038
+ children: [
2039
+ hasOlder ? /* @__PURE__ */ jsx(View, { style: styles.older, children: loadingOlder ? /* @__PURE__ */ jsx(Thinking, { label: labels.loadingEarlier, size: "sm", motion }) : /* @__PURE__ */ jsx(
2040
+ Pressable,
2041
+ {
2042
+ accessibilityRole: "button",
2043
+ onPress: onLoadOlder,
2044
+ hitSlop: theme.space[8],
2045
+ children: /* @__PURE__ */ jsx(Text, { style: styles.olderText, children: labels.loadEarlier })
2046
+ }
2047
+ ) }) : null,
2048
+ messages.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs(View, { style: styles.empty, children: [
2049
+ /* @__PURE__ */ jsx(Staggered, { i: 0, animate, children: /* @__PURE__ */ jsxs(View, { style: styles.emptyHead, children: [
2050
+ /* @__PURE__ */ jsx(AssistantMark, {}),
2051
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: styles.emptyTitle, children: emptyTitle }),
2052
+ emptyDescription ? /* @__PURE__ */ jsx(Text, { style: styles.emptyText, children: emptyDescription }) : null
2053
+ ] }) }),
2054
+ suggestionsLoading || suggestions?.length ? /* @__PURE__ */ jsx(Staggered, { i: 2, animate, children: /* @__PURE__ */ jsx(
2055
+ ChatSuggestions,
2056
+ {
2057
+ suggestions: suggestions ?? [],
2058
+ perPage: suggestionsPerPage,
2059
+ loading: suggestionsLoading,
2060
+ onPick: pick,
2061
+ onDismiss: onDismissSuggestion,
2062
+ animate,
2063
+ labels
2064
+ }
2065
+ ) }) : null
2066
+ ] }) : messages.map((m, i) => /* @__PURE__ */ jsxs(React6.Fragment, { children: [
2067
+ m.day && m.day !== messages[i - 1]?.day ? /* @__PURE__ */ jsxs(View, { style: styles.day, accessibilityRole: "header", accessibilityLabel: m.day, children: [
2068
+ /* @__PURE__ */ jsx(View, { style: styles.dayLine }),
2069
+ /* @__PURE__ */ jsx(Text, { style: styles.dayText, children: m.day }),
2070
+ /* @__PURE__ */ jsx(View, { style: styles.dayLine })
2071
+ ] }) : null,
2072
+ /* @__PURE__ */ jsx(
2073
+ ChatMessageRow,
2074
+ {
2075
+ message: m,
2076
+ latest: m.id === latestAssistantId,
2077
+ appearing: isArriving(m.id),
2078
+ animate,
2079
+ assistant,
2080
+ user,
2081
+ feedbackReasons,
2082
+ running,
2083
+ onRegenerate,
2084
+ onEdit,
2085
+ onRetry,
2086
+ onFeedback,
2087
+ onCopy,
2088
+ onReply: reply,
2089
+ onLinkPress,
2090
+ labels
2091
+ }
2092
+ )
2093
+ ] }, m.id)),
2094
+ pendingReply ? /* @__PURE__ */ jsxs(View, { style: styles.pending, children: [
2095
+ assistant.avatar ?? /* @__PURE__ */ jsx(AssistantMark, {}),
2096
+ /* @__PURE__ */ jsx(WorkingIndicator, { animate, labels })
2097
+ ] }) : null,
2098
+ showFollowUps ? /* @__PURE__ */ jsx(View, { style: styles.followUps, role: "group", accessibilityLabel: labels.followUps, children: followUps.map((s, i) => /* @__PURE__ */ jsx(Staggered, { i, animate, children: /* @__PURE__ */ jsxs(
2099
+ Pressable,
2100
+ {
2101
+ accessibilityRole: "button",
2102
+ accessibilityLabel: s.label,
2103
+ onPress: () => send(s.prompt ?? s.label, []),
2104
+ style: ({ pressed }) => [styles.followUp, pressed && styles.pressed],
2105
+ children: [
2106
+ /* @__PURE__ */ jsx(
2107
+ Icon,
2108
+ {
2109
+ name: s.icon ?? "arrow-right",
2110
+ size: theme.icon.xs,
2111
+ color: theme.color.text3
2112
+ }
2113
+ ),
2114
+ /* @__PURE__ */ jsx(Text, { style: styles.followUpText, children: s.label })
2115
+ ]
2116
+ }
2117
+ ) }, s.id ?? s.label)) }) : null
2118
+ ]
2119
+ }
2120
+ ),
2121
+ /* @__PURE__ */ jsx(
2122
+ Animated.View,
2123
+ {
2124
+ pointerEvents: pinned ? "none" : "box-none",
2125
+ importantForAccessibility: pinned ? "no-hide-descendants" : "auto",
2126
+ accessibilityElementsHidden: pinned,
2127
+ style: [
2128
+ styles.pillWrap,
2129
+ {
2130
+ opacity: pill,
2131
+ transform: [
2132
+ {
2133
+ translateY: pill.interpolate({
2134
+ inputRange: [0, 1],
2135
+ outputRange: [theme.space[8], 0]
2136
+ })
2137
+ }
2138
+ ]
2139
+ }
2140
+ ],
2141
+ children: /* @__PURE__ */ jsxs(
2142
+ Pressable,
2143
+ {
2144
+ accessibilityRole: "button",
2145
+ accessibilityLabel: pillLabel,
2146
+ onPress: () => scrollToBottom(),
2147
+ style: styles.pill,
2148
+ children: [
2149
+ running ? /* @__PURE__ */ jsx(Thinking, { label: "", size: "sm", announce: false, motion }) : /* @__PURE__ */ jsx(Icon, { name: "arrow-down", size: theme.icon.xs, color: theme.color.text }),
2150
+ /* @__PURE__ */ jsx(Text, { style: styles.pillText, children: pillLabel })
2151
+ ]
2152
+ }
2153
+ )
2154
+ }
2155
+ )
2156
+ ] }),
2157
+ /* @__PURE__ */ jsx(
2158
+ ChatComposer,
2159
+ {
2160
+ inputRef,
2161
+ value: draft,
2162
+ onValueChange: setDraft,
2163
+ onSend: send,
2164
+ replyTo: replyingTo ? {
2165
+ author: replyingTo.role === "user" ? user?.name ?? labels.you : assistant.name,
2166
+ text: chatMessageText(replyingTo)
2167
+ } : null,
2168
+ onCancelReply: () => setReplyingTo(null),
2169
+ running,
2170
+ onStop,
2171
+ placeholder,
2172
+ attachments,
2173
+ onAttachPress,
2174
+ onRemoveAttachment,
2175
+ toolbar: composerToolbar,
2176
+ hint: disclaimer,
2177
+ disabled,
2178
+ motion,
2179
+ labels,
2180
+ style: [styles.composer, column]
2181
+ }
2182
+ )
2183
+ ]
2184
+ }
2185
+ );
2186
+ }
2187
+ var sheet5 = createStyleSheet((theme) => ({
2188
+ root: { flex: 1, backgroundColor: theme.color.surface },
2189
+ stage: { flex: 1 },
2190
+ content: { flexGrow: 1, gap: theme.space.s6, padding: theme.space.s4 },
2191
+ older: { alignItems: "center" },
2192
+ olderText: {
2193
+ fontFamily: theme.font.sans,
2194
+ fontSize: theme.type.caption1.fontSize,
2195
+ color: theme.color.text3
2196
+ },
2197
+ empty: {
2198
+ flex: 1,
2199
+ justifyContent: "center",
2200
+ gap: theme.space.s2,
2201
+ paddingVertical: theme.space.s8
2202
+ },
2203
+ emptyHead: { alignItems: "center", gap: theme.space.s3, marginBottom: theme.space.s4 },
2204
+ emptyTitle: {
2205
+ fontFamily: theme.font.display,
2206
+ fontSize: theme.type.title3.fontSize,
2207
+ lineHeight: theme.type.title3.lineHeight,
2208
+ fontWeight: theme.fontWeight.bold,
2209
+ color: theme.color.text,
2210
+ textAlign: "center"
2211
+ },
2212
+ emptyText: {
2213
+ fontFamily: theme.font.sans,
2214
+ fontSize: theme.type.footnote.fontSize,
2215
+ lineHeight: theme.type.footnote.lineHeight,
2216
+ color: theme.color.text2,
2217
+ textAlign: "center"
2218
+ },
2219
+ pressed: { backgroundColor: theme.state.pressed },
2220
+ meta: {
2221
+ fontFamily: theme.font.sans,
2222
+ fontSize: theme.type.caption1.fontSize,
2223
+ lineHeight: theme.type.caption1.lineHeight,
2224
+ color: theme.color.text3
2225
+ },
2226
+ day: { flexDirection: "row", alignItems: "center", gap: theme.space.s3 },
2227
+ dayLine: { flex: 1, height: theme.borderWidth.base, backgroundColor: theme.color.border },
2228
+ dayText: {
2229
+ fontFamily: theme.font.sans,
2230
+ fontSize: theme.type.caption1.fontSize,
2231
+ fontWeight: theme.fontWeight.bold,
2232
+ color: theme.color.text3
2233
+ },
2234
+ pending: { flexDirection: "row", gap: theme.space.s3, alignItems: "center" },
2235
+ followUps: {
2236
+ flexDirection: "row",
2237
+ flexWrap: "wrap",
2238
+ gap: theme.space.s2,
2239
+ paddingLeft: theme.ctl.sm + theme.space.s3
2240
+ },
2241
+ followUp: {
2242
+ flexDirection: "row",
2243
+ alignItems: "center",
2244
+ gap: theme.space[6],
2245
+ paddingHorizontal: theme.space.s3,
2246
+ paddingVertical: theme.space.s2,
2247
+ borderRadius: theme.radius.full,
2248
+ borderWidth: theme.borderWidth.base,
2249
+ borderColor: theme.color.border
2250
+ },
2251
+ followUpText: {
2252
+ fontFamily: theme.font.sans,
2253
+ fontSize: theme.type.footnote.fontSize,
2254
+ color: theme.color.text2
2255
+ },
2256
+ pillWrap: {
2257
+ position: "absolute",
2258
+ left: 0,
2259
+ right: 0,
2260
+ bottom: theme.space.s3,
2261
+ alignItems: "center"
2262
+ },
2263
+ pill: {
2264
+ flexDirection: "row",
2265
+ alignItems: "center",
2266
+ gap: theme.space[6],
2267
+ paddingHorizontal: theme.space.s3,
2268
+ paddingVertical: theme.space.s2,
2269
+ borderRadius: theme.radius.full,
2270
+ borderWidth: theme.borderWidth.base,
2271
+ borderColor: theme.color.border,
2272
+ backgroundColor: theme.color.surface,
2273
+ ...theme.shadow.md
2274
+ },
2275
+ pillText: {
2276
+ fontFamily: theme.font.sans,
2277
+ fontSize: theme.type.caption1.fontSize,
2278
+ fontWeight: theme.fontWeight.bold,
2279
+ color: theme.color.text
2280
+ },
2281
+ composer: { paddingHorizontal: theme.space.s4, paddingBottom: theme.space.s4 }
2282
+ }));
2283
+
2284
+ export { CHAT_FEEDBACK_REASONS, Chat, ChatComposer, chatMessageText };