@stigmer/react 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/channel/AgentChannelsPanel.d.ts.map +1 -1
  2. package/channel/AgentChannelsPanel.js +21 -2
  3. package/channel/AgentChannelsPanel.js.map +1 -1
  4. package/channel/ChannelConversationsDialog.d.ts +12 -4
  5. package/channel/ChannelConversationsDialog.d.ts.map +1 -1
  6. package/channel/ChannelConversationsDialog.js +18 -24
  7. package/channel/ChannelConversationsDialog.js.map +1 -1
  8. package/channel/providers.d.ts +23 -0
  9. package/channel/providers.d.ts.map +1 -1
  10. package/channel/providers.js +4 -0
  11. package/channel/providers.js.map +1 -1
  12. package/channel/useSaveAgentChannel.d.ts +5 -4
  13. package/channel/useSaveAgentChannel.d.ts.map +1 -1
  14. package/channel/useSaveAgentChannel.js +18 -4
  15. package/channel/useSaveAgentChannel.js.map +1 -1
  16. package/composer/ComposerToolbar.d.ts +1 -1
  17. package/composer/ComposerToolbar.d.ts.map +1 -1
  18. package/composer/SessionComposer.d.ts +1 -1
  19. package/composer/SessionComposer.js +1 -1
  20. package/composer/SessionComposer.js.map +1 -1
  21. package/conversation/ConversationAttentionBanner.d.ts +22 -0
  22. package/conversation/ConversationAttentionBanner.d.ts.map +1 -0
  23. package/conversation/ConversationAttentionBanner.js +25 -0
  24. package/conversation/ConversationAttentionBanner.js.map +1 -0
  25. package/conversation/ConversationComposer.d.ts +41 -0
  26. package/conversation/ConversationComposer.d.ts.map +1 -0
  27. package/conversation/ConversationComposer.js +57 -0
  28. package/conversation/ConversationComposer.js.map +1 -0
  29. package/conversation/ConversationControlBanner.d.ts +52 -0
  30. package/conversation/ConversationControlBanner.d.ts.map +1 -0
  31. package/conversation/ConversationControlBanner.js +59 -0
  32. package/conversation/ConversationControlBanner.js.map +1 -0
  33. package/conversation/ConversationListPane.d.ts +68 -0
  34. package/conversation/ConversationListPane.d.ts.map +1 -0
  35. package/conversation/ConversationListPane.js +120 -0
  36. package/conversation/ConversationListPane.js.map +1 -0
  37. package/conversation/ConversationTimelineView.d.ts +46 -0
  38. package/conversation/ConversationTimelineView.d.ts.map +1 -0
  39. package/conversation/ConversationTimelineView.js +166 -0
  40. package/conversation/ConversationTimelineView.js.map +1 -0
  41. package/conversation/ConversationsWorkbench.d.ts +63 -0
  42. package/conversation/ConversationsWorkbench.d.ts.map +1 -0
  43. package/conversation/ConversationsWorkbench.js +165 -0
  44. package/conversation/ConversationsWorkbench.js.map +1 -0
  45. package/conversation/conversationPresentation.d.ts +137 -0
  46. package/conversation/conversationPresentation.d.ts.map +1 -0
  47. package/conversation/conversationPresentation.js +230 -0
  48. package/conversation/conversationPresentation.js.map +1 -0
  49. package/conversation/index.d.ts +26 -0
  50. package/conversation/index.d.ts.map +1 -0
  51. package/conversation/index.js +14 -0
  52. package/conversation/index.js.map +1 -0
  53. package/conversation/polling.d.ts +39 -0
  54. package/conversation/polling.d.ts.map +1 -0
  55. package/conversation/polling.js +39 -0
  56. package/conversation/polling.js.map +1 -0
  57. package/conversation/useConversation.d.ts +61 -0
  58. package/conversation/useConversation.d.ts.map +1 -0
  59. package/conversation/useConversation.js +153 -0
  60. package/conversation/useConversation.js.map +1 -0
  61. package/conversation/useConversationList.d.ts +98 -0
  62. package/conversation/useConversationList.d.ts.map +1 -0
  63. package/conversation/useConversationList.js +200 -0
  64. package/conversation/useConversationList.js.map +1 -0
  65. package/conversation/useConversationParticipation.d.ts +63 -0
  66. package/conversation/useConversationParticipation.d.ts.map +1 -0
  67. package/conversation/useConversationParticipation.js +84 -0
  68. package/conversation/useConversationParticipation.js.map +1 -0
  69. package/conversation/useConversationTimeline.d.ts +71 -0
  70. package/conversation/useConversationTimeline.d.ts.map +1 -0
  71. package/conversation/useConversationTimeline.js +152 -0
  72. package/conversation/useConversationTimeline.js.map +1 -0
  73. package/conversation/useConversationsWantsHumanCount.d.ts +42 -0
  74. package/conversation/useConversationsWantsHumanCount.d.ts.map +1 -0
  75. package/conversation/useConversationsWantsHumanCount.js +40 -0
  76. package/conversation/useConversationsWantsHumanCount.js.map +1 -0
  77. package/index.d.ts +4 -0
  78. package/index.d.ts.map +1 -1
  79. package/index.js +8 -0
  80. package/index.js.map +1 -1
  81. package/internal/tooltip.d.ts.map +1 -1
  82. package/internal/tooltip.js +7 -1
  83. package/internal/tooltip.js.map +1 -1
  84. package/internal/useAutoScroll.d.ts +25 -4
  85. package/internal/useAutoScroll.d.ts.map +1 -1
  86. package/internal/useAutoScroll.js +55 -15
  87. package/internal/useAutoScroll.js.map +1 -1
  88. package/internal/useFetch.d.ts.map +1 -1
  89. package/internal/useFetch.js +14 -4
  90. package/internal/useFetch.js.map +1 -1
  91. package/models/ModelSelector.d.ts +24 -5
  92. package/models/ModelSelector.d.ts.map +1 -1
  93. package/models/ModelSelector.js +31 -9
  94. package/models/ModelSelector.js.map +1 -1
  95. package/models/registry.d.ts +3 -2
  96. package/models/registry.d.ts.map +1 -1
  97. package/models/registry.js.map +1 -1
  98. package/models/service-tier.d.ts +1 -1
  99. package/models/service-tier.d.ts.map +1 -1
  100. package/models/service-tier.js +1 -1
  101. package/models/service-tier.js.map +1 -1
  102. package/package.json +4 -4
  103. package/schedule/ScheduleDetailView.js +1 -1
  104. package/schedule/ScheduleDetailView.js.map +1 -1
  105. package/schedule/ScheduleForm.d.ts.map +1 -1
  106. package/schedule/ScheduleForm.js +7 -6
  107. package/schedule/ScheduleForm.js.map +1 -1
  108. package/sharing/useSaveAgentShare.d.ts +10 -1
  109. package/sharing/useSaveAgentShare.d.ts.map +1 -1
  110. package/sharing/useSaveAgentShare.js +9 -0
  111. package/sharing/useSaveAgentShare.js.map +1 -1
  112. package/sidebar/WorkspaceSidebar.d.ts +12 -4
  113. package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
  114. package/sidebar/WorkspaceSidebar.js +19 -7
  115. package/sidebar/WorkspaceSidebar.js.map +1 -1
  116. package/src/channel/AgentChannelsPanel.tsx +33 -2
  117. package/src/channel/ChannelConversationsDialog.tsx +22 -25
  118. package/src/channel/__tests__/AgentChannelsPanel.test.tsx +36 -5
  119. package/src/channel/__tests__/ChannelConversationsDialog.test.tsx +38 -1
  120. package/src/channel/__tests__/useSaveAgentChannel.test.tsx +26 -0
  121. package/src/channel/providers.ts +27 -0
  122. package/src/channel/useSaveAgentChannel.ts +18 -4
  123. package/src/composer/ComposerToolbar.tsx +1 -1
  124. package/src/composer/SessionComposer.tsx +2 -2
  125. package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +4 -3
  126. package/src/conversation/ConversationAttentionBanner.tsx +77 -0
  127. package/src/conversation/ConversationComposer.tsx +156 -0
  128. package/src/conversation/ConversationControlBanner.tsx +193 -0
  129. package/src/conversation/ConversationListPane.tsx +459 -0
  130. package/src/conversation/ConversationTimelineView.tsx +435 -0
  131. package/src/conversation/ConversationsWorkbench.tsx +378 -0
  132. package/src/conversation/__tests__/ConversationAttentionBanner.test.tsx +82 -0
  133. package/src/conversation/__tests__/ConversationComposer.test.tsx +160 -0
  134. package/src/conversation/__tests__/ConversationControlBanner.test.tsx +190 -0
  135. package/src/conversation/__tests__/ConversationListPane.test.tsx +372 -0
  136. package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +160 -0
  137. package/src/conversation/__tests__/ConversationTimelineView.test.tsx +405 -0
  138. package/src/conversation/__tests__/ConversationsWorkbench.test.tsx +860 -0
  139. package/src/conversation/__tests__/a11y/conversation-surfaces.a11y.test.tsx +235 -0
  140. package/src/conversation/__tests__/a11y/harness.tsx +100 -0
  141. package/src/conversation/__tests__/conversation-tooltips.layout.test.tsx +167 -0
  142. package/src/conversation/__tests__/conversationPollingComposition.test.tsx +138 -0
  143. package/src/conversation/__tests__/conversationPresentation.test.ts +289 -0
  144. package/src/conversation/__tests__/useConversation.test.tsx +243 -0
  145. package/src/conversation/__tests__/useConversationList.test.tsx +492 -0
  146. package/src/conversation/__tests__/useConversationParticipation.test.tsx +190 -0
  147. package/src/conversation/__tests__/useConversationTimeline.test.tsx +258 -0
  148. package/src/conversation/__tests__/useConversationsWantsHumanCount.test.tsx +102 -0
  149. package/src/conversation/conversationPresentation.ts +305 -0
  150. package/src/conversation/index.ts +80 -0
  151. package/src/conversation/polling.ts +41 -0
  152. package/src/conversation/useConversation.ts +221 -0
  153. package/src/conversation/useConversationList.ts +328 -0
  154. package/src/conversation/useConversationParticipation.ts +178 -0
  155. package/src/conversation/useConversationTimeline.ts +244 -0
  156. package/src/conversation/useConversationsWantsHumanCount.ts +86 -0
  157. package/src/index.ts +67 -0
  158. package/src/internal/__tests__/fake-timer-slices.ts +29 -0
  159. package/src/internal/__tests__/useAutoScroll.layout.test.tsx +143 -0
  160. package/src/internal/__tests__/useAutoScroll.test.tsx +62 -5
  161. package/src/internal/__tests__/useFetch-poll-under-render-pressure.test.tsx +79 -0
  162. package/src/internal/tooltip.tsx +6 -1
  163. package/src/internal/useAutoScroll.ts +66 -20
  164. package/src/internal/useFetch.ts +15 -4
  165. package/src/models/ModelSelector.tsx +81 -49
  166. package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +105 -21
  167. package/src/models/registry.ts +3 -2
  168. package/src/models/service-tier.ts +1 -1
  169. package/src/schedule/ScheduleDetailView.tsx +1 -1
  170. package/src/schedule/ScheduleForm.tsx +7 -6
  171. package/src/schedule/__tests__/scheduleCreation.test.tsx +2 -2
  172. package/src/sharing/__tests__/useSaveAgentShare.test.tsx +55 -1
  173. package/src/sharing/__tests__/useShareToolReadiness.test.tsx +1 -0
  174. package/src/sharing/useSaveAgentShare.ts +19 -1
  175. package/src/sidebar/WorkspaceSidebar.tsx +62 -3
  176. package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +56 -1
  177. package/styles.css +1 -1
@@ -0,0 +1,230 @@
1
+ import { timestampDate } from "@bufbuild/protobuf/wkt";
2
+ import { ConversationControl, ConversationItemAuthor, ConversationLane, } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
3
+ import { ChannelDeliveryStatus } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/delivery_pb";
4
+ import { ChannelReceiptState } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/outbound_pb";
5
+ /** Map the wire author onto the render vocabulary. */
6
+ export function authorKindOf(item) {
7
+ switch (item.author) {
8
+ case ConversationItemAuthor.author_customer:
9
+ return "customer";
10
+ case ConversationItemAuthor.author_agent:
11
+ return "agent";
12
+ case ConversationItemAuthor.author_teammate:
13
+ return "teammate";
14
+ case ConversationItemAuthor.author_platform:
15
+ return "platform";
16
+ default:
17
+ return "unknown";
18
+ }
19
+ }
20
+ /** True for org-internal items (escalations, attention clears, notes). */
21
+ export function isInternalItem(item) {
22
+ return item.lane === ConversationLane.lane_internal;
23
+ }
24
+ /** Map the wire delivery status onto the render vocabulary. */
25
+ export function sendAttemptOf(item) {
26
+ switch (item.deliveryStatus) {
27
+ case ChannelDeliveryStatus.pending:
28
+ return "pending";
29
+ case ChannelDeliveryStatus.delivering:
30
+ return "delivering";
31
+ case ChannelDeliveryStatus.delivered:
32
+ return "delivered";
33
+ case ChannelDeliveryStatus.failed:
34
+ return "failed";
35
+ case ChannelDeliveryStatus.suppressed:
36
+ return "suppressed";
37
+ default:
38
+ return null;
39
+ }
40
+ }
41
+ /** Map the wire receipt state onto the render vocabulary. */
42
+ export function receiptOf(item) {
43
+ switch (item.receiptState) {
44
+ case ChannelReceiptState.receipt_sent:
45
+ return "sent";
46
+ case ChannelReceiptState.receipt_delivered:
47
+ return "delivered";
48
+ case ChannelReceiptState.receipt_read:
49
+ return "read";
50
+ case ChannelReceiptState.receipt_failed:
51
+ return "failed";
52
+ default:
53
+ return null;
54
+ }
55
+ }
56
+ /**
57
+ * Placeholder copy for inbound items whose body the platform cannot
58
+ * render (`text` empty, `provider_message_type` names the kind). Media
59
+ * retrieval is a named non-goal of this surface — the placeholder tells
60
+ * staff something arrived and what shape it was.
61
+ *
62
+ * Returns `null` when the item needs no placeholder (it has text, or it
63
+ * is not a provider-typed inbound item — e.g. an attention-clear event,
64
+ * whose copy is the renderer's own vocabulary).
65
+ */
66
+ export function inboundPlaceholderOf(item) {
67
+ if (item.text !== "" || item.providerMessageType === "")
68
+ return null;
69
+ switch (item.providerMessageType) {
70
+ case "text":
71
+ return null;
72
+ case "image":
73
+ return "Photo";
74
+ case "video":
75
+ return "Video";
76
+ case "audio":
77
+ return "Voice message";
78
+ case "document":
79
+ return "Document";
80
+ case "sticker":
81
+ return "Sticker";
82
+ case "location":
83
+ return "Location";
84
+ case "contacts":
85
+ return "Contact card";
86
+ default:
87
+ // The provider's vocabulary passes through verbatim (never
88
+ // re-encoded), so an unknown kind still names itself.
89
+ return `Unsupported message (${item.providerMessageType})`;
90
+ }
91
+ }
92
+ /**
93
+ * The list-row label for a conversation: the provider-reported display
94
+ * name when one exists, else a provider-aware fallback.
95
+ *
96
+ * The fallback is provider-aware because conversation keys are not
97
+ * uniformly meaningful: WhatsApp's key is the customer's phone number
98
+ * (a real label), while Slack's key is a thread timestamp like
99
+ * `1723012345.678900` (noise a human cannot use).
100
+ */
101
+ export function conversationLabelOf(conversation, provider) {
102
+ if (conversation.displayName !== "")
103
+ return conversation.displayName;
104
+ if (provider === "slack")
105
+ return "Slack thread";
106
+ return conversation.conversationKey;
107
+ }
108
+ /**
109
+ * The customer's reachable address, when the label hides it: the fact a
110
+ * staffer needs to call or message the customer outside the platform.
111
+ *
112
+ * Provider-aware like {@link conversationLabelOf}, because conversation
113
+ * keys are not uniformly addresses: WhatsApp's key IS the customer's
114
+ * phone number (the wa_id), and when a display name wins the label slot
115
+ * that number would otherwise appear nowhere on the surface. Slack's
116
+ * key is a thread timestamp — not a way to reach anyone — so there is
117
+ * nothing to show. Unknown providers show nothing rather than guess.
118
+ *
119
+ * Returns `null` when the label already shows the key (no display
120
+ * name), so the same fact never renders twice. The key returns
121
+ * verbatim — no fabricated `+` prefix: the wire value is the truth this
122
+ * module reports, and formatting belongs to hosts that know their
123
+ * display rules.
124
+ */
125
+ export function conversationContactOf(conversation, provider) {
126
+ if (provider !== "whatsapp")
127
+ return null;
128
+ if (conversation.displayName === "")
129
+ return null;
130
+ return conversation.conversationKey;
131
+ }
132
+ /**
133
+ * Meta's customer-service window: a business can send free-form WhatsApp
134
+ * messages for 24 hours after the customer's last inbound message; after
135
+ * that, only template messages deliver. The constant lives beside its
136
+ * only consumer and is promoted to `ChannelProviderDescriptor` only if a
137
+ * second windowed provider ever appears (the second-sighting rule,
138
+ * DD-014 D-a).
139
+ */
140
+ const WHATSAPP_SERVICE_WINDOW_MS = 24 * 60 * 60 * 1000;
141
+ /**
142
+ * Estimate whether the provider's free-form reply window is open
143
+ * (channel-conversations DD-014 D-a). Provider-aware like
144
+ * {@link conversationLabelOf}: WhatsApp is the only windowed provider, so
145
+ * every other provider answers `null` — no window, no claim — as does a
146
+ * conversation whose customer has never written
147
+ * (`last_customer_message_at` unset; that conversation takes the
148
+ * composer's disabled branch instead).
149
+ *
150
+ * Client-derived on purpose, and NOT a divergence from DD-011 A-1's
151
+ * "the predicate is expressed once, in SQL" rule: the window is
152
+ * wall-clock-anchored — it flips with NO row change — so a
153
+ * server-computed boolean would be stale by construction between polls.
154
+ * Time-decaying presentation derived from wire instants is this
155
+ * surface's established class (relative timestamps, day grouping).
156
+ *
157
+ * Honesty caveat (DD-014 D-a): the anchor is the platform's receipt
158
+ * instant, minutes AFTER Meta's own clock started the window, so the
159
+ * estimate can claim "open" briefly after Meta closed it — which is why
160
+ * consumers warn and never block (D-b). For the same reason the exact
161
+ * 24-hour tie reads "closed": erring a moment early offsets an anchor
162
+ * that always errs late.
163
+ */
164
+ export function serviceWindowOf(conversation, provider, now) {
165
+ if (provider !== "whatsapp")
166
+ return null;
167
+ const anchor = conversation.lastCustomerMessageAt;
168
+ if (anchor === undefined)
169
+ return null;
170
+ const elapsedMs = now.getTime() - timestampDate(anchor).getTime();
171
+ return elapsedMs >= WHATSAPP_SERVICE_WINDOW_MS ? "closed" : "open";
172
+ }
173
+ /**
174
+ * The awaiting-reply indicator for a conversation (channel-conversations
175
+ * DD-011 D-a): `"strong"` when a human holds it — the agent will not
176
+ * answer, so a person must (F-13's exact case) — and `"muted"` when the
177
+ * agent holds it: the agent is about to answer, and if its turn dies the
178
+ * conversation stays visibly waiting, which is the built-in recovery
179
+ * path (DD-011 D-b). `null` when the customer's last message has been
180
+ * answered (or the customer has not written).
181
+ *
182
+ * The fact itself is server-derived (`awaiting_reply` rides the wire as
183
+ * a boolean precisely so no client re-implements the NULL-and-compare
184
+ * rule); this helper only maps fact × holder onto render strength.
185
+ */
186
+ export function awaitingIndicatorOf(conversation) {
187
+ if (!conversation.awaitingReply)
188
+ return null;
189
+ return conversation.control === ConversationControl.control_human
190
+ ? "strong"
191
+ : "muted";
192
+ }
193
+ /**
194
+ * The timeline item id an accepted staff reply appears under: the
195
+ * outbound ledger's `ob:` namespace over the reply output's
196
+ * `outbound_message_id` — the contract
197
+ * `useConversationParticipation.reply` documents ("the item will
198
+ * appear on the timeline as `ob:<that id>`"). The workbench's
199
+ * post-send busy state watches for exactly this id (F-05).
200
+ */
201
+ export function outboundItemIdOf(outboundMessageId) {
202
+ return `ob:${outboundMessageId}`;
203
+ }
204
+ /**
205
+ * Newest-first timeline order: `(at, item_id)` descending — the exact
206
+ * comparator the server pages with, so client-side merges can never
207
+ * disagree with server pages about order.
208
+ */
209
+ export function compareTimelineItemsNewestFirst(a, b) {
210
+ const byInstant = compareInstants(b.at, a.at);
211
+ if (byInstant !== 0)
212
+ return byInstant;
213
+ return a.itemId < b.itemId ? 1 : a.itemId > b.itemId ? -1 : 0;
214
+ }
215
+ // BigInt() call form, not a 0n literal: the web console compiles SDK
216
+ // sources under a pre-ES2020 target where the literal is a syntax error.
217
+ const ZERO_SECONDS = BigInt(0);
218
+ function compareInstants(a, b) {
219
+ // A missing instant sorts oldest — the server always stamps `at`, so
220
+ // this only orders a defensively-handled corrupt item to the top of
221
+ // history rather than crashing the sort.
222
+ const aSeconds = a?.seconds ?? ZERO_SECONDS;
223
+ const bSeconds = b?.seconds ?? ZERO_SECONDS;
224
+ if (aSeconds !== bSeconds)
225
+ return aSeconds < bSeconds ? -1 : 1;
226
+ const aNanos = a?.nanos ?? 0;
227
+ const bNanos = b?.nanos ?? 0;
228
+ return aNanos - bNanos;
229
+ }
230
+ //# sourceMappingURL=conversationPresentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversationPresentation.js","sourceRoot":"","sources":["../../src/conversation/conversationPresentation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export { ConversationsWorkbench } from "./ConversationsWorkbench.js";
2
+ export type { ConversationHeaderContext, ConversationsWorkbenchProps, } from "./ConversationsWorkbench.js";
3
+ export { ConversationListPane } from "./ConversationListPane.js";
4
+ export type { ConversationIdentity, ConversationListPaneProps, } from "./ConversationListPane.js";
5
+ export { ConversationTimelineView } from "./ConversationTimelineView.js";
6
+ export type { ConversationTimelineViewProps } from "./ConversationTimelineView.js";
7
+ export { ConversationControlBanner } from "./ConversationControlBanner.js";
8
+ export type { ConversationControlBannerProps } from "./ConversationControlBanner.js";
9
+ export { ConversationAttentionBanner } from "./ConversationAttentionBanner.js";
10
+ export type { ConversationAttentionBannerProps } from "./ConversationAttentionBanner.js";
11
+ export { ConversationComposer } from "./ConversationComposer.js";
12
+ export type { ConversationComposerProps } from "./ConversationComposer.js";
13
+ export { useConversation } from "./useConversation.js";
14
+ export type { UseConversationOptions, UseConversationReturn, } from "./useConversation.js";
15
+ export { useConversationList } from "./useConversationList.js";
16
+ export type { UseConversationListOptions, UseConversationListReturn, } from "./useConversationList.js";
17
+ export { useConversationTimeline } from "./useConversationTimeline.js";
18
+ export type { UseConversationTimelineOptions, UseConversationTimelineReturn, } from "./useConversationTimeline.js";
19
+ export { useConversationParticipation } from "./useConversationParticipation.js";
20
+ export type { ConversationCommand, UseConversationParticipationOptions, UseConversationParticipationReturn, } from "./useConversationParticipation.js";
21
+ export { useConversationsWantsHumanCount } from "./useConversationsWantsHumanCount.js";
22
+ export type { UseConversationsWantsHumanCountOptions, UseConversationsWantsHumanCountReturn, } from "./useConversationsWantsHumanCount.js";
23
+ export { authorKindOf, awaitingIndicatorOf, compareTimelineItemsNewestFirst, conversationContactOf, conversationLabelOf, inboundPlaceholderOf, isInternalItem, receiptOf, sendAttemptOf, serviceWindowOf, } from "./conversationPresentation.js";
24
+ export type { AwaitingIndicator, ConversationAuthorKind, ReceiptKind, SendAttemptKind, ServiceWindowState, } from "./conversationPresentation.js";
25
+ export { CONVERSATION_BADGE_POLL_INTERVAL_MS, CONVERSATION_DETAIL_POLL_INTERVAL_MS, CONVERSATION_LIST_POLL_INTERVAL_MS, } from "./polling.js";
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conversation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACV,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EACV,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,YAAY,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAEnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,YAAY,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAErF,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,YAAY,EAAE,gCAAgC,EAAE,MAAM,kCAAkC,CAAC;AAEzF,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAE3E,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,YAAY,EACV,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EACV,8BAA8B,EAC9B,6BAA6B,GAC9B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AACjF,YAAY,EACV,mBAAmB,EACnB,mCAAmC,EACnC,kCAAkC,GACnC,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAsC,CAAC;AACvF,YAAY,EACV,sCAAsC,EACtC,qCAAqC,GACtC,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,+BAA+B,EAC/B,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,aAAa,EACb,eAAe,GAChB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,iBAAiB,EACjB,sBAAsB,EACtB,WAAW,EACX,eAAe,EACf,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,mCAAmC,EACnC,oCAAoC,EACpC,kCAAkC,GACnC,MAAM,cAAc,CAAC"}
@@ -0,0 +1,14 @@
1
+ export { ConversationsWorkbench } from "./ConversationsWorkbench.js";
2
+ export { ConversationListPane } from "./ConversationListPane.js";
3
+ export { ConversationTimelineView } from "./ConversationTimelineView.js";
4
+ export { ConversationControlBanner } from "./ConversationControlBanner.js";
5
+ export { ConversationAttentionBanner } from "./ConversationAttentionBanner.js";
6
+ export { ConversationComposer } from "./ConversationComposer.js";
7
+ export { useConversation } from "./useConversation.js";
8
+ export { useConversationList } from "./useConversationList.js";
9
+ export { useConversationTimeline } from "./useConversationTimeline.js";
10
+ export { useConversationParticipation } from "./useConversationParticipation.js";
11
+ export { useConversationsWantsHumanCount } from "./useConversationsWantsHumanCount.js";
12
+ export { authorKindOf, awaitingIndicatorOf, compareTimelineItemsNewestFirst, conversationContactOf, conversationLabelOf, inboundPlaceholderOf, isInternalItem, receiptOf, sendAttemptOf, serviceWindowOf, } from "./conversationPresentation.js";
13
+ export { CONVERSATION_BADGE_POLL_INTERVAL_MS, CONVERSATION_DETAIL_POLL_INTERVAL_MS, CONVERSATION_LIST_POLL_INTERVAL_MS, } from "./polling.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conversation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAMrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAMjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAGzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAG3E,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAG/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAMvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAMvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAOjF,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAsC,CAAC;AAMvF,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,+BAA+B,EAC/B,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,aAAa,EACb,eAAe,GAChB,MAAM,+BAA+B,CAAC;AASvC,OAAO,EACL,mCAAmC,EACnC,oCAAoC,EACpC,kCAAkC,GACnC,MAAM,cAAc,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Liveness for the conversation surface is HONEST POLLING (channel-
3
+ * conversations DD-012) — no streaming or watch RPC exists on the
4
+ * conversation query controller, and none ships until a revisit trigger
5
+ * fires (a real push consumer, or sub-second liveness becoming a product
6
+ * property). These constants are the deliberate transport seam: hooks
7
+ * default to them, consumers may override per instance, and when
8
+ * push-shaped liveness lands the defaults change here without touching a
9
+ * consumer.
10
+ *
11
+ * The latency budget is a stated contract (DD-012 D-b), not an accident
12
+ * of three numbers:
13
+ *
14
+ * | Surface | Budget | Mechanism |
15
+ * | ---------------------------------------- | ---------------------------- | ------------------------------------------------ |
16
+ * | Your own action (take over, hand back, | immediate | command answer applied to the detail AND list |
17
+ * | dismiss, reply) | | `applyServerState` seams — zero polls involved |
18
+ * | The open conversation (row + timeline) | ≤ 5s | detail polls |
19
+ * | The inbox list | ≤ 20s, immediate on refocus | list poll + focus refetch |
20
+ * | The nav badge | ≤ 60s, immediate on refocus | count poll (the filtered list's `total_count`) |
21
+ */
22
+ /** Default poll interval for the org-wide conversation list. */
23
+ export declare const CONVERSATION_LIST_POLL_INTERVAL_MS = 20000;
24
+ /**
25
+ * Default poll interval for the OPEN conversation (its row and its
26
+ * timeline head). Tighter than the list on purpose: this is where a
27
+ * teammate's takeover or the customer's next message must appear while
28
+ * a human is watching.
29
+ */
30
+ export declare const CONVERSATION_DETAIL_POLL_INTERVAL_MS = 5000;
31
+ /**
32
+ * Default poll interval for the sidebar's wants-human count. Deliberately
33
+ * the slowest budget on the surface: a badge tolerates staleness an open
34
+ * inbox does not, it renders on every console page in every tab, and each
35
+ * tick pays an FGA ListObjects before the count (DD-011 D-g's stated
36
+ * price). Focus refetch keeps it honest at the moment someone returns.
37
+ */
38
+ export declare const CONVERSATION_BADGE_POLL_INTERVAL_MS = 60000;
39
+ //# sourceMappingURL=polling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling.d.ts","sourceRoot":"","sources":["../../src/conversation/polling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,gEAAgE;AAChE,eAAO,MAAM,kCAAkC,QAAS,CAAC;AAEzD;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,OAAQ,CAAC;AAE1D;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAS,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Liveness for the conversation surface is HONEST POLLING (channel-
3
+ * conversations DD-012) — no streaming or watch RPC exists on the
4
+ * conversation query controller, and none ships until a revisit trigger
5
+ * fires (a real push consumer, or sub-second liveness becoming a product
6
+ * property). These constants are the deliberate transport seam: hooks
7
+ * default to them, consumers may override per instance, and when
8
+ * push-shaped liveness lands the defaults change here without touching a
9
+ * consumer.
10
+ *
11
+ * The latency budget is a stated contract (DD-012 D-b), not an accident
12
+ * of three numbers:
13
+ *
14
+ * | Surface | Budget | Mechanism |
15
+ * | ---------------------------------------- | ---------------------------- | ------------------------------------------------ |
16
+ * | Your own action (take over, hand back, | immediate | command answer applied to the detail AND list |
17
+ * | dismiss, reply) | | `applyServerState` seams — zero polls involved |
18
+ * | The open conversation (row + timeline) | ≤ 5s | detail polls |
19
+ * | The inbox list | ≤ 20s, immediate on refocus | list poll + focus refetch |
20
+ * | The nav badge | ≤ 60s, immediate on refocus | count poll (the filtered list's `total_count`) |
21
+ */
22
+ /** Default poll interval for the org-wide conversation list. */
23
+ export const CONVERSATION_LIST_POLL_INTERVAL_MS = 20_000;
24
+ /**
25
+ * Default poll interval for the OPEN conversation (its row and its
26
+ * timeline head). Tighter than the list on purpose: this is where a
27
+ * teammate's takeover or the customer's next message must appear while
28
+ * a human is watching.
29
+ */
30
+ export const CONVERSATION_DETAIL_POLL_INTERVAL_MS = 5_000;
31
+ /**
32
+ * Default poll interval for the sidebar's wants-human count. Deliberately
33
+ * the slowest budget on the surface: a badge tolerates staleness an open
34
+ * inbox does not, it renders on every console page in every tab, and each
35
+ * tick pays an FGA ListObjects before the count (DD-011 D-g's stated
36
+ * price). Focus refetch keeps it honest at the moment someone returns.
37
+ */
38
+ export const CONVERSATION_BADGE_POLL_INTERVAL_MS = 60_000;
39
+ //# sourceMappingURL=polling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling.js","sourceRoot":"","sources":["../../src/conversation/polling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,gEAAgE;AAChE,MAAM,CAAC,MAAM,kCAAkC,GAAG,MAAM,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG,KAAK,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,MAAM,CAAC"}
@@ -0,0 +1,61 @@
1
+ import type { ChannelConversation } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
2
+ /** Options for {@link useConversation}. */
3
+ export interface UseConversationOptions {
4
+ /**
5
+ * Poll interval in milliseconds; `false` disables polling. Defaults to
6
+ * {@link CONVERSATION_DETAIL_POLL_INTERVAL_MS} — the open conversation
7
+ * is where another teammate's takeover must appear promptly.
8
+ */
9
+ readonly refetchIntervalMs?: number | false;
10
+ }
11
+ /** Return value of {@link useConversation}. */
12
+ export interface UseConversationReturn {
13
+ /**
14
+ * The conversation's participation state, or `null` while loading and
15
+ * while the conversation does not exist yet (see
16
+ * {@link awaitingCustomer} for the distinction).
17
+ */
18
+ readonly conversation: ChannelConversation | null;
19
+ /**
20
+ * `true` when the server answered NOT_FOUND: no conversation row
21
+ * exists yet for this key. NOT an error — a proactive cold-send can
22
+ * have a timeline before the customer ever writes, and the row is
23
+ * created by the customer's first message. Render it as
24
+ * "participation controls unlock when the customer writes".
25
+ */
26
+ readonly awaitingCustomer: boolean;
27
+ /** `true` only until the first answer (row or NOT_FOUND) arrives. */
28
+ readonly isLoading: boolean;
29
+ /** `true` while a background poll is in flight and stale state shows. */
30
+ readonly isRefetching: boolean;
31
+ /** Error from the last failed request (NOT_FOUND excluded), or `null`. */
32
+ readonly error: Error | null;
33
+ /** Imperatively re-read the row. */
34
+ readonly refetch: () => void;
35
+ /**
36
+ * Adopt a fresh row the server just returned — every participation
37
+ * command answers the post-command state (including for the loser of
38
+ * a concurrent-takeover race), and that answer is newer than anything
39
+ * a poll already in flight will deliver. Applying it also fences
40
+ * those in-flight polls so a stale answer can never overwrite it.
41
+ */
42
+ readonly applyServerState: (fresh: ChannelConversation) => void;
43
+ }
44
+ /**
45
+ * Data hook for one conversation's identity and participation state —
46
+ * the single-row read behind a conversation detail view (who holds
47
+ * control, whether attention is needed and why, the display name).
48
+ *
49
+ * Deliberately NOT built on the shared `useFetch`: participation
50
+ * commands hand back fresher state than any in-flight poll
51
+ * ({@link UseConversationReturn.applyServerState}), which needs a write
52
+ * fence over the fetch state — a seam `useFetch`'s single-writer model
53
+ * does not have. The fence is an epoch counter: applying server state
54
+ * bumps it, and a poll that started under an older epoch discards its
55
+ * answer instead of resurrecting pre-command state under the user's
56
+ * cursor.
57
+ *
58
+ * Pass empty strings to skip fetching (the `null`-fetchFn convention).
59
+ */
60
+ export declare function useConversation(agentChannelId: string, conversationKey: string, options?: UseConversationOptions): UseConversationReturn;
61
+ //# sourceMappingURL=useConversation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversation.d.ts","sourceRoot":"","sources":["../../src/conversation/useConversation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uEAAuE,CAAC;AAUjH,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7C;AAED,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClD;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,oCAAoC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACjE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,qBAAqB,CA8IvB"}
@@ -0,0 +1,153 @@
1
+ "use client";
2
+ import { create, equals } from "@bufbuild/protobuf";
3
+ import { isNotFound } from "@stigmer/sdk";
4
+ import { ChannelConversationSchema, GetChannelConversationInputSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { useStigmer } from "../hooks.js";
7
+ import { toError } from "../internal/toError.js";
8
+ import { CONVERSATION_DETAIL_POLL_INTERVAL_MS } from "./polling.js";
9
+ /**
10
+ * Data hook for one conversation's identity and participation state —
11
+ * the single-row read behind a conversation detail view (who holds
12
+ * control, whether attention is needed and why, the display name).
13
+ *
14
+ * Deliberately NOT built on the shared `useFetch`: participation
15
+ * commands hand back fresher state than any in-flight poll
16
+ * ({@link UseConversationReturn.applyServerState}), which needs a write
17
+ * fence over the fetch state — a seam `useFetch`'s single-writer model
18
+ * does not have. The fence is an epoch counter: applying server state
19
+ * bumps it, and a poll that started under an older epoch discards its
20
+ * answer instead of resurrecting pre-command state under the user's
21
+ * cursor.
22
+ *
23
+ * Pass empty strings to skip fetching (the `null`-fetchFn convention).
24
+ */
25
+ export function useConversation(agentChannelId, conversationKey, options) {
26
+ const stigmer = useStigmer();
27
+ const refetchIntervalMs = options?.refetchIntervalMs ?? CONVERSATION_DETAIL_POLL_INTERVAL_MS;
28
+ const enabled = agentChannelId !== "" && conversationKey !== "";
29
+ const [conversation, setConversation] = useState(null);
30
+ const [awaitingCustomer, setAwaitingCustomer] = useState(false);
31
+ const [hasAnswer, setHasAnswer] = useState(false);
32
+ const [isFetching, setIsFetching] = useState(enabled);
33
+ const [error, setError] = useState(null);
34
+ const [fetchKey, setFetchKey] = useState(0);
35
+ // The write fence: bumped by applyServerState and by identity changes;
36
+ // a fetch that started under an older epoch throws its answer away.
37
+ const epochRef = useRef(0);
38
+ // Which fetch is the latest — ONLY the latest fetch's resolution may
39
+ // clear the in-flight flags, so a fenced stale answer cannot strand
40
+ // isFetching=true (which would silence the poll interval forever).
41
+ const fetchIdRef = useRef(0);
42
+ const isFetchingRef = useRef(false);
43
+ const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
44
+ const applyServerState = useCallback((fresh) => {
45
+ epochRef.current += 1;
46
+ setConversation(fresh);
47
+ setAwaitingCustomer(false);
48
+ setHasAnswer(true);
49
+ setError(null);
50
+ }, []);
51
+ useEffect(() => {
52
+ // Identity change (or disable): reset so channel A's control state
53
+ // can never render under channel B's key.
54
+ epochRef.current += 1;
55
+ setConversation(null);
56
+ setAwaitingCustomer(false);
57
+ setHasAnswer(false);
58
+ setError(null);
59
+ if (!enabled) {
60
+ setIsFetching(false);
61
+ isFetchingRef.current = false;
62
+ }
63
+ }, [agentChannelId, conversationKey, stigmer, enabled]);
64
+ useEffect(() => {
65
+ if (!enabled)
66
+ return;
67
+ const epoch = epochRef.current;
68
+ const fetchId = ++fetchIdRef.current;
69
+ setIsFetching(true);
70
+ isFetchingRef.current = true;
71
+ const settle = () => {
72
+ if (fetchIdRef.current !== fetchId)
73
+ return;
74
+ setIsFetching(false);
75
+ isFetchingRef.current = false;
76
+ };
77
+ stigmer.agentChannel
78
+ .getConversation(create(GetChannelConversationInputSchema, { agentChannelId, conversationKey }))
79
+ .then((row) => {
80
+ if (epochRef.current === epoch) {
81
+ // Preserve the reference across polls that change nothing —
82
+ // a fresh-but-equal proto every 5s would defeat React.memo
83
+ // on everything rendering the row (DD-010).
84
+ setConversation((current) => current && equals(ChannelConversationSchema, current, row)
85
+ ? current
86
+ : row);
87
+ setAwaitingCustomer(false);
88
+ setHasAnswer(true);
89
+ setError(null);
90
+ }
91
+ settle();
92
+ }, (err) => {
93
+ if (epochRef.current === epoch) {
94
+ if (isNotFound(err)) {
95
+ // The legitimate "no row yet" answer — not an error state.
96
+ setConversation(null);
97
+ setAwaitingCustomer(true);
98
+ setHasAnswer(true);
99
+ setError(null);
100
+ }
101
+ else {
102
+ setError(toError(err));
103
+ }
104
+ }
105
+ settle();
106
+ });
107
+ }, [enabled, agentChannelId, conversationKey, stigmer, fetchKey]);
108
+ useEffect(() => {
109
+ if (!enabled || !refetchIntervalMs || refetchIntervalMs <= 0)
110
+ return;
111
+ const id = setInterval(() => {
112
+ if (!isFetchingRef.current)
113
+ refetch();
114
+ }, refetchIntervalMs);
115
+ return () => clearInterval(id);
116
+ }, [enabled, refetchIntervalMs, refetch]);
117
+ // Focus refetch (DD-012 D-a: returning to the tab is fresh), guarded by
118
+ // the same in-flight flag as the interval. Mirrors useFetch's
119
+ // refetchOnWindowFocus — the second sighting of this listener shape;
120
+ // extract a shared helper if a third hand-rolled loop ever needs it.
121
+ useEffect(() => {
122
+ if (!enabled)
123
+ return;
124
+ if (typeof window === "undefined")
125
+ return;
126
+ const onActive = () => {
127
+ if (!isFetchingRef.current)
128
+ refetch();
129
+ };
130
+ const onVisible = () => {
131
+ if (document.visibilityState === "visible")
132
+ onActive();
133
+ };
134
+ window.addEventListener("focus", onActive);
135
+ document.addEventListener("visibilitychange", onVisible);
136
+ return () => {
137
+ window.removeEventListener("focus", onActive);
138
+ document.removeEventListener("visibilitychange", onVisible);
139
+ };
140
+ }, [enabled, refetch]);
141
+ const isLoading = isFetching && !hasAnswer;
142
+ const isRefetching = isFetching && hasAnswer;
143
+ return useMemo(() => ({
144
+ conversation,
145
+ awaitingCustomer,
146
+ isLoading,
147
+ isRefetching,
148
+ error,
149
+ refetch,
150
+ applyServerState,
151
+ }), [conversation, awaitingCustomer, isLoading, isRefetching, error, refetch, applyServerState]);
152
+ }
153
+ //# sourceMappingURL=useConversation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversation.js","sourceRoot":"","sources":["../../src/conversation/useConversation.ts"],"names":[],"mappings":"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"}