@adminide-stack/yantra-mobile 12.0.69-alpha.17 → 12.0.69-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/contexts/ChatLiveStreamContext.js +5 -1
- package/lib/contexts/ChatLiveStreamContext.js.map +1 -1
- package/lib/features/chat/ChatTranscript.js +7 -3
- package/lib/features/chat/ChatTranscript.js.map +1 -1
- package/lib/features/chat/ChatWidgetCard.js +13 -0
- package/lib/features/chat/ChatWidgetCard.js.map +1 -0
- package/lib/features/chat/QueuedFollowUps.js +81 -0
- package/lib/features/chat/QueuedFollowUps.js.map +1 -0
- package/lib/features/chat/chatWidgets.js +37 -0
- package/lib/features/chat/chatWidgets.js.map +1 -0
- package/lib/hooks/useCdecliChannel.js +126 -4
- package/lib/hooks/useCdecliChannel.js.map +1 -1
- package/lib/hooks/useChatStream.js +83 -63
- package/lib/hooks/useChatStream.js.map +1 -1
- package/lib/screens/Chat/index.js +41 -21
- package/lib/screens/Chat/index.js.map +1 -1
- package/lib/state/chatThreadMerge.js +6 -1
- package/lib/state/chatThreadMerge.js.map +1 -1
- package/package.json +4 -4
|
@@ -10,6 +10,10 @@ import {useSyncExternalStore,useMemo,useEffect}from'react';import {useChatStream
|
|
|
10
10
|
queuedMessages: [],
|
|
11
11
|
flushQueuedMessages: async () => void 0,
|
|
12
12
|
discardQueuedMessages: () => void 0,
|
|
13
|
+
queuedFollowUps: [],
|
|
14
|
+
queueNotice: null,
|
|
15
|
+
cancelQueuedFollowUp: async () => void 0,
|
|
16
|
+
sendQueuedNow: () => void 0,
|
|
13
17
|
sendMessage: async () => "failed",
|
|
14
18
|
cancel: () => void 0,
|
|
15
19
|
clearMessages: () => void 0
|
|
@@ -18,7 +22,7 @@ let snapshot = EMPTY;
|
|
|
18
22
|
const listeners = /* @__PURE__ */ new Set();
|
|
19
23
|
function publish(next) {
|
|
20
24
|
const prev = snapshot;
|
|
21
|
-
if (prev.messages === next.messages && prev.response === next.response && prev.quickReply === next.quickReply && prev.error === next.error && prev.isLoading === next.isLoading && prev.isStreaming === next.isStreaming && prev.messagesLoading === next.messagesLoading && prev.queuedMessages === next.queuedMessages && prev.sendMessage === next.sendMessage) {
|
|
25
|
+
if (prev.messages === next.messages && prev.response === next.response && prev.quickReply === next.quickReply && prev.error === next.error && prev.isLoading === next.isLoading && prev.isStreaming === next.isStreaming && prev.messagesLoading === next.messagesLoading && prev.queuedMessages === next.queuedMessages && prev.queuedFollowUps === next.queuedFollowUps && prev.queueNotice === next.queueNotice && prev.sendMessage === next.sendMessage) {
|
|
22
26
|
snapshot = next;
|
|
23
27
|
return;
|
|
24
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatLiveStreamContext.js","sources":["../../src/contexts/ChatLiveStreamContext.tsx"],"sourcesContent":["/**\n * App-wide owner of the mobile chat stream.\n *\n * `useChatStream` used to live on ChatScreen. Opening history unmounted that\n * screen, which tore down GraphQL subscriptions — the brain's provisional\n * reply vanished and the published agent stream never reattached. Web keeps\n * ChatLayout mounted; this engine does the same: it stays under\n * `CdecliConnectionProvider` and Chat only *reads* the live snapshot.\n */\nimport React, { useEffect, useMemo } from 'react';\nimport { useSyncExternalStore } from 'react';\nimport { useChatStream, type UseChatStreamRouting } from '../hooks/useChatStream';\nimport { useCdecliConnection } from './CdecliConnectionContext';\n\nexport type ChatLiveStreamSnapshot = ReturnType<typeof useChatStream>;\n\nconst EMPTY: ChatLiveStreamSnapshot = {\n messages: [],\n response: '',\n quickReply: '',\n error: null,\n isLoading: false,\n isStreaming: false,\n messagesLoading: false,\n hasMessages: false,\n queuedMessages: [],\n flushQueuedMessages: async () => undefined,\n discardQueuedMessages: () => undefined,\n sendMessage: async () => 'failed',\n cancel: () => undefined,\n clearMessages: () => undefined,\n};\n\nlet snapshot: ChatLiveStreamSnapshot = EMPTY;\nconst listeners = new Set<() => void>();\n\nfunction publish(next: ChatLiveStreamSnapshot) {\n const prev = snapshot;\n if (\n prev.messages === next.messages &&\n prev.response === next.response &&\n prev.quickReply === next.quickReply &&\n prev.error === next.error &&\n prev.isLoading === next.isLoading &&\n prev.isStreaming === next.isStreaming &&\n prev.messagesLoading === next.messagesLoading &&\n prev.queuedMessages === next.queuedMessages &&\n prev.sendMessage === next.sendMessage\n ) {\n snapshot = next;\n return;\n }\n snapshot = next;\n listeners.forEach((fn) => fn());\n}\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\nfunction getSnapshot() {\n return snapshot;\n}\n\n/** Keeps messenger subscriptions alive while the user is on history / home. */\nexport function ChatLiveStreamEngine() {\n const cdecli = useCdecliConnection();\n const routing: UseChatStreamRouting = useMemo(\n () => ({\n channelConnected: cdecli.channelConnected,\n accountId: cdecli.accountId,\n persistenceMode: cdecli.persistenceMode ?? 'backend',\n recoverAgentAuth: cdecli.recoverAgentAuth,\n }),\n [cdecli.channelConnected, cdecli.accountId, cdecli.persistenceMode, cdecli.recoverAgentAuth],\n );\n const stream = useChatStream(cdecli.activeChannelId ?? null, routing);\n useEffect(() => {\n publish(stream);\n }, [stream]);\n return null;\n}\n\nexport function useChatLiveStream(): ChatLiveStreamSnapshot {\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n"],"names":["_a"],"mappings":"gLAcA,MAAM,KAAgC,GAAA;AAAA,EACpC,UAAU,EAAC;AAAA,EACX,QAAU,EAAA,EAAA;AAAA,EACV,UAAY,EAAA,EAAA;AAAA,EACZ,KAAO,EAAA,IAAA;AAAA,EACP,SAAW,EAAA,KAAA;AAAA,EACX,WAAa,EAAA,KAAA;AAAA,EACb,eAAiB,EAAA,KAAA;AAAA,EACjB,WAAa,EAAA,KAAA;AAAA,EACb,gBAAgB,EAAC;AAAA,EACjB,qBAAqB,YAAY,MAAA;AAAA,EACjC,uBAAuB,MAAM,MAAA;AAAA,EAC7B,aAAa,YAAY,QAAA;AAAA,EACzB,QAAQ,MAAM,MAAA;AAAA,EACd,eAAe,MAAM;AACvB,CAAA;AACA,IAAI,QAAmC,GAAA,KAAA;AACvC,MAAM,SAAA,uBAAgB,GAAgB,EAAA;AACtC,SAAS,QAAQ,IAA8B,EAAA;AAC7C,EAAA,MAAM,IAAO,GAAA,QAAA;AACb,EAAA,IAAI,
|
|
1
|
+
{"version":3,"file":"ChatLiveStreamContext.js","sources":["../../src/contexts/ChatLiveStreamContext.tsx"],"sourcesContent":["/**\n * App-wide owner of the mobile chat stream.\n *\n * `useChatStream` used to live on ChatScreen. Opening history unmounted that\n * screen, which tore down GraphQL subscriptions — the brain's provisional\n * reply vanished and the published agent stream never reattached. Web keeps\n * ChatLayout mounted; this engine does the same: it stays under\n * `CdecliConnectionProvider` and Chat only *reads* the live snapshot.\n */\nimport React, { useEffect, useMemo } from 'react';\nimport { useSyncExternalStore } from 'react';\nimport { useChatStream, type UseChatStreamRouting } from '../hooks/useChatStream';\nimport { useCdecliConnection } from './CdecliConnectionContext';\n\nexport type ChatLiveStreamSnapshot = ReturnType<typeof useChatStream>;\n\nconst EMPTY: ChatLiveStreamSnapshot = {\n messages: [],\n response: '',\n quickReply: '',\n error: null,\n isLoading: false,\n isStreaming: false,\n messagesLoading: false,\n hasMessages: false,\n queuedMessages: [],\n flushQueuedMessages: async () => undefined,\n discardQueuedMessages: () => undefined,\n queuedFollowUps: [],\n queueNotice: null,\n cancelQueuedFollowUp: async () => undefined,\n sendQueuedNow: () => undefined,\n sendMessage: async () => 'failed',\n cancel: () => undefined,\n clearMessages: () => undefined,\n};\n\nlet snapshot: ChatLiveStreamSnapshot = EMPTY;\nconst listeners = new Set<() => void>();\n\nfunction publish(next: ChatLiveStreamSnapshot) {\n const prev = snapshot;\n if (\n prev.messages === next.messages &&\n prev.response === next.response &&\n prev.quickReply === next.quickReply &&\n prev.error === next.error &&\n prev.isLoading === next.isLoading &&\n prev.isStreaming === next.isStreaming &&\n prev.messagesLoading === next.messagesLoading &&\n prev.queuedMessages === next.queuedMessages &&\n prev.queuedFollowUps === next.queuedFollowUps &&\n prev.queueNotice === next.queueNotice &&\n prev.sendMessage === next.sendMessage\n ) {\n snapshot = next;\n return;\n }\n snapshot = next;\n listeners.forEach((fn) => fn());\n}\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\nfunction getSnapshot() {\n return snapshot;\n}\n\n/** Keeps messenger subscriptions alive while the user is on history / home. */\nexport function ChatLiveStreamEngine() {\n const cdecli = useCdecliConnection();\n const routing: UseChatStreamRouting = useMemo(\n () => ({\n channelConnected: cdecli.channelConnected,\n accountId: cdecli.accountId,\n persistenceMode: cdecli.persistenceMode ?? 'backend',\n recoverAgentAuth: cdecli.recoverAgentAuth,\n }),\n [cdecli.channelConnected, cdecli.accountId, cdecli.persistenceMode, cdecli.recoverAgentAuth],\n );\n const stream = useChatStream(cdecli.activeChannelId ?? null, routing);\n useEffect(() => {\n publish(stream);\n }, [stream]);\n return null;\n}\n\nexport function useChatLiveStream(): ChatLiveStreamSnapshot {\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {jsx,Fragment,jsxs}from'react/jsx-runtime';import {useRef,useMemo,useCallback,useEffect,memo}from'react';import {View,StyleSheet,FlatList,Platform,Pressable,Text}from'react-native';import {dismissKeyboard}from'../../utils/keyboardController.js';import {useKeyboardLiftHeight}from'../../components/KeyboardComposerDock.js';import Markdown from'react-native-markdown-display';import {shouldRenderUserTranscriptTurn,displayUserMessageText}from'../attachments/displayUserMessageText.js';import {MessageAttachmentPreviews}from'../attachments/MessageAttachmentPreviews.js';import {ToolTimeline}from'./ToolTimeline.js';import {parseToolActivity}from'./toolActivity.js';import {parseAskUser,displayAskUserAssistantText,stripAskUser}from'./askUser.js';import {collapseDuplicateUserRows,groupConsecutiveRoleRows}from'./transcriptGroups.js';import {AssistantMarkdown}from'./AssistantMarkdown.js';var __defProp = Object.defineProperty;
|
|
1
|
+
import {jsx,Fragment,jsxs}from'react/jsx-runtime';import {useRef,useMemo,useCallback,useEffect,memo}from'react';import {View,StyleSheet,FlatList,Platform,Pressable,Text}from'react-native';import {dismissKeyboard}from'../../utils/keyboardController.js';import {useKeyboardLiftHeight}from'../../components/KeyboardComposerDock.js';import Markdown from'react-native-markdown-display';import {shouldRenderUserTranscriptTurn,displayUserMessageText}from'../attachments/displayUserMessageText.js';import {MessageAttachmentPreviews}from'../attachments/MessageAttachmentPreviews.js';import {ToolTimeline}from'./ToolTimeline.js';import {ChatWidgetCard}from'./ChatWidgetCard.js';import {splitChatWidgets}from'./chatWidgets.js';import {parseToolActivity}from'./toolActivity.js';import {parseAskUser,displayAskUserAssistantText,stripAskUser}from'./askUser.js';import {collapseDuplicateUserRows,groupConsecutiveRoleRows}from'./transcriptGroups.js';import {AssistantMarkdown}from'./AssistantMarkdown.js';var __defProp = Object.defineProperty;
|
|
2
2
|
var __defProps = Object.defineProperties;
|
|
3
3
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
@@ -180,11 +180,15 @@ const AssistantTurnView = memo(function AssistantTurnView2({
|
|
|
180
180
|
const attachments = (_a = item.attachments) != null ? _a : [];
|
|
181
181
|
const stripped = stripAskUser(item.content);
|
|
182
182
|
const toolActivity = parseToolActivity(stripped, !!item.streaming);
|
|
183
|
-
const
|
|
183
|
+
const {
|
|
184
|
+
text,
|
|
185
|
+
widgets
|
|
186
|
+
} = splitChatWidgets(parseToolActivity(displayAskUserAssistantText(item.content, !!item.askAnswered), !!item.streaming).text, !!item.streaming);
|
|
184
187
|
return /* @__PURE__ */ jsxs(View, { style: [styles.assistantCol, tightTop && styles.assistantColTight], children: [
|
|
185
188
|
attachments.length > 0 ? /* @__PURE__ */ jsx(MessageAttachmentPreviews, { attachments, align: "start", isDark }) : null,
|
|
186
189
|
toolActivity.steps.length > 0 ? /* @__PURE__ */ jsx(ToolTimeline, { steps: toolActivity.steps, active: !!item.streaming, isDark }) : null,
|
|
187
190
|
text ? /* @__PURE__ */ jsx(AssistantMarkdown, { text, streaming: !!item.streaming, isDark }) : null,
|
|
191
|
+
widgets.map((widget, i) => /* @__PURE__ */ jsx(ChatWidgetCard, { widget, isDark }, `${widget.component}-${i}`)),
|
|
188
192
|
renderMessageActions ? renderMessageActions({
|
|
189
193
|
id: item.id,
|
|
190
194
|
role: "assistant",
|
|
@@ -280,4 +284,4 @@ const styles = StyleSheet.create({
|
|
|
280
284
|
assistantColTight: {
|
|
281
285
|
marginTop: -4
|
|
282
286
|
}
|
|
283
|
-
});export{ChatTranscript};//# sourceMappingURL=ChatTranscript.js.map
|
|
287
|
+
});export{ChatTranscript,USER_BUBBLE_BG};//# sourceMappingURL=ChatTranscript.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatTranscript.js","sources":["../../../src/features/chat/ChatTranscript.tsx"],"sourcesContent":["/**\n * Chat thread list that can show attachment previews.\n *\n * `@messenger-box/platform-mobile` PlanModeView maps each row to GiftedChat\n * `{ text }` only, so an image send becomes the gateway caption\n * \"Attached: IMG_0005.jpg\" with no thumbnail. This list keeps streaming\n * markdown for the assistant and draws Manus-style previews on the user side.\n *\n * Rows stay oldest → newest (same as web). A previous `inverted` FlatList\n * reversed that order on device. The composer dock already lifts with the\n * keyboard; this list just fills the leftover space and pins to the latest\n * turn.\n */\nimport React, { memo, useCallback, useEffect, useMemo, useRef, type ReactNode } from 'react';\nimport { FlatList, Platform, Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { dismissKeyboard } from '../../utils/keyboardController';\nimport { useKeyboardLiftHeight } from '../../components/KeyboardComposerDock';\nimport Markdown from 'react-native-markdown-display';\nimport type { MessageAttachment } from '../../hooks/useChatStream';\nimport { displayUserMessageText, shouldRenderUserTranscriptTurn } from '../attachments/displayUserMessageText';\nimport { MessageAttachmentPreviews } from '../attachments/MessageAttachmentPreviews';\nimport { ToolTimeline } from './ToolTimeline';\nimport { parseToolActivity } from './toolActivity';\nimport { parseAskUser, stripAskUser, displayAskUserAssistantText } from './askUser';\nimport { collapseDuplicateUserRows, groupConsecutiveRoleRows, type RoleGroup } from './transcriptGroups';\nimport { AssistantMarkdown } from './AssistantMarkdown';\n\nexport type TranscriptMessage = {\n id: string;\n role: string;\n content: string;\n displayContent?: string;\n attachments?: MessageAttachment[];\n /** Typed while the gateway was down: shown in place, waiting on the outbox. */\n queued?: boolean;\n};\n\ntype TranscriptRow = TranscriptMessage & { streaming?: boolean; askAnswered?: boolean };\ntype TranscriptGroup = RoleGroup<TranscriptRow>;\n\nconst USER_BUBBLE_BG = '#2563eb';\n\nexport function ChatTranscript({\n messages,\n streamingContent,\n renderMessageActions,\n listContentStyle,\n listFooter,\n isDark = false,\n}: {\n messages: TranscriptMessage[];\n streamingContent?: string;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n listContentStyle?: StyleProp<ViewStyle>;\n listFooter?: ReactNode;\n isDark?: boolean;\n}) {\n const listRef = useRef<FlatList<TranscriptGroup>>(null);\n const stickToBottomRef = useRef(true);\n const isProgrammaticScrollRef = useRef(false);\n const ignoreScrollUntilRef = useRef(0);\n const keyboardHeight = useKeyboardLiftHeight();\n\n const historyRows = useMemo<TranscriptRow[]>(() => {\n const rows: TranscriptRow[] = [];\n messages.forEach((msg, index) => {\n if (msg.role === 'user') {\n if (shouldRenderUserTranscriptTurn(msg.content, msg.attachments)) {\n rows.push({ ...msg, streaming: false });\n }\n return;\n }\n if (msg.role !== 'assistant') {\n rows.push({ ...msg, streaming: false });\n return;\n }\n const askAnswered =\n Boolean(parseAskUser(msg.content)) && messages.slice(index + 1).some((m) => m.role === 'user');\n const visible = displayAskUserAssistantText(msg.content, askAnswered).trim();\n const tools = parseToolActivity(stripAskUser(msg.content), false);\n const hasAsk = Boolean(parseAskUser(msg.content));\n if (hasAsk && !visible && tools.steps.length === 0 && !(msg.attachments?.length ?? 0)) {\n return;\n }\n if (!hasAsk) {\n const leftover = stripAskUser(msg.content).trim();\n if (!leftover && !(msg.attachments?.length ?? 0)) return;\n }\n rows.push({ ...msg, streaming: false, askAnswered });\n });\n return rows;\n }, [messages]);\n\n const rows = useMemo<TranscriptRow[]>(() => {\n const history = collapseDuplicateUserRows(historyRows);\n const rawStream = streamingContent ?? '';\n const last = history[history.length - 1];\n const streamAskAnswered = last?.role === 'user' && Boolean(parseAskUser(rawStream));\n const streamProse = displayAskUserAssistantText(rawStream, streamAskAnswered).trim();\n const streamTools = parseToolActivity(stripAskUser(rawStream), true);\n if (!streamProse && streamTools.steps.length === 0) return history;\n if (last?.role === 'assistant') {\n const lastText = last.content.trim();\n const streamText = rawStream.trim();\n if (lastText === streamText || lastText.startsWith(streamText) || streamText.startsWith(lastText)) {\n return history;\n }\n }\n return [\n ...history,\n {\n id: '__streaming__',\n role: 'assistant',\n content: rawStream,\n streaming: true,\n askAnswered: streamAskAnswered,\n },\n ];\n }, [historyRows, streamingContent]);\n\n const groups = useMemo(() => groupConsecutiveRoleRows(rows), [rows]);\n\n const scrollToLatest = useCallback(\n (animated: boolean) => {\n if (groups.length === 0) return;\n isProgrammaticScrollRef.current = true;\n stickToBottomRef.current = true;\n const list = listRef.current;\n if (!list) return;\n list.scrollToEnd({ animated });\n },\n [groups.length],\n );\n\n useEffect(() => {\n if (groups.length === 0 || !stickToBottomRef.current) return undefined;\n const t = setTimeout(() => scrollToLatest(false), 0);\n return () => clearTimeout(t);\n }, [groups.length, streamingContent?.length, scrollToLatest]);\n\n // Spacer and list height update on the same keyboard frame. Scroll only\n // after that shrink, or we stay on the top of a long turn (image 1).\n useEffect(() => {\n if (keyboardHeight <= 0) return undefined;\n stickToBottomRef.current = true;\n ignoreScrollUntilRef.current = Date.now() + 400;\n const frame = requestAnimationFrame(() => scrollToLatest(false));\n const afterLayout = setTimeout(() => scrollToLatest(false), 64);\n const afterKeyboard = setTimeout(() => scrollToLatest(false), 280);\n return () => {\n cancelAnimationFrame(frame);\n clearTimeout(afterLayout);\n clearTimeout(afterKeyboard);\n };\n }, [keyboardHeight, scrollToLatest]);\n\n const handleScroll = useCallback(\n (e: {\n nativeEvent: {\n contentOffset: { y: number };\n contentSize: { height: number };\n layoutMeasurement: { height: number };\n };\n }) => {\n if (Date.now() < ignoreScrollUntilRef.current) {\n stickToBottomRef.current = true;\n return;\n }\n const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;\n const nearLatest = contentOffset.y + layoutMeasurement.height >= contentSize.height - 80;\n if (!isProgrammaticScrollRef.current) {\n stickToBottomRef.current = nearLatest;\n } else if (nearLatest) {\n isProgrammaticScrollRef.current = false;\n }\n },\n [],\n );\n\n const handleContentSizeChange = useCallback(() => {\n if (!stickToBottomRef.current) return;\n scrollToLatest(false);\n }, [scrollToLatest]);\n\n const renderItem = useCallback(\n ({ item }: { item: TranscriptGroup }) => (\n <TranscriptGroupView group={item} isDark={isDark} renderMessageActions={renderMessageActions} />\n ),\n [isDark, renderMessageActions],\n );\n\n return (\n <View style={styles.list}>\n <FlatList\n ref={listRef}\n data={groups}\n keyExtractor={(item) => item.id}\n renderItem={renderItem}\n extraData={`${streamingContent ?? ''}:${Boolean(listFooter)}`}\n ListFooterComponent={listFooter ? <>{listFooter}</> : null}\n initialNumToRender={12}\n maxToRenderPerBatch={8}\n updateCellsBatchingPeriod={50}\n windowSize={21}\n scrollEnabled\n nestedScrollEnabled={false}\n directionalLockEnabled\n onScroll={handleScroll}\n onContentSizeChange={handleContentSizeChange}\n onLayout={() => {\n if (stickToBottomRef.current) scrollToLatest(false);\n }}\n scrollEventThrottle={16}\n keyboardShouldPersistTaps=\"handled\"\n keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}\n automaticallyAdjustKeyboardInsets={false}\n automaticallyAdjustContentInsets={false}\n contentInsetAdjustmentBehavior=\"never\"\n contentContainerStyle={[styles.listContent, listContentStyle, !listFooter && styles.listContentFill]}\n style={styles.list}\n />\n </View>\n );\n}\n\nconst TranscriptGroupView = memo(function TranscriptGroupView({\n group,\n isDark,\n renderMessageActions,\n}: {\n group: TranscriptGroup;\n isDark: boolean;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n}) {\n if (group.role === 'user') {\n return (\n <Pressable style={styles.userCol} onPress={dismissKeyboard} accessible={false}>\n {group.items.map((item, index) => {\n const attachments = item.attachments ?? [];\n const text = displayUserMessageText(item.displayContent ?? item.content, attachments);\n if (!text && attachments.length === 0) return null;\n return (\n <View key={item.id} style={[styles.userTurn, index > 0 && styles.userFollow]}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"end\" isDark={isDark} />\n ) : null}\n {text ? (\n <View style={[styles.userBubble, item.queued && styles.userBubbleQueued]}>\n <Markdown style={USER_MARKDOWN}>{text}</Markdown>\n </View>\n ) : null}\n {item.queued ? (\n <Text style={[styles.queuedCaption, { color: isDark ? '#94a3b8' : '#64748b' }]}>\n Queued · sends when the gateway connects\n </Text>\n ) : null}\n </View>\n );\n })}\n </Pressable>\n );\n }\n\n const last = group.items[group.items.length - 1];\n\n return (\n <View style={styles.assistantGroup}>\n {group.items.map((item, index) => (\n <AssistantTurnView\n key={item.id}\n item={item}\n isDark={isDark}\n tightTop={index > 0}\n renderMessageActions={item.streaming || item !== last ? undefined : renderMessageActions}\n />\n ))}\n </View>\n );\n});\n\nconst AssistantTurnView = memo(function AssistantTurnView({\n item,\n isDark,\n tightTop,\n renderMessageActions,\n}: {\n item: TranscriptRow;\n isDark: boolean;\n tightTop: boolean;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n}) {\n const attachments = item.attachments ?? [];\n const stripped = stripAskUser(item.content);\n const toolActivity = parseToolActivity(stripped, !!item.streaming);\n const text = parseToolActivity(\n displayAskUserAssistantText(item.content, !!item.askAnswered),\n !!item.streaming,\n ).text;\n\n return (\n <View style={[styles.assistantCol, tightTop && styles.assistantColTight]}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"start\" isDark={isDark} />\n ) : null}\n {toolActivity.steps.length > 0 ? (\n <ToolTimeline steps={toolActivity.steps} active={!!item.streaming} isDark={isDark} />\n ) : null}\n {text ? <AssistantMarkdown text={text} streaming={!!item.streaming} isDark={isDark} /> : null}\n {renderMessageActions\n ? renderMessageActions({ id: item.id, role: 'assistant', content: item.content })\n : null}\n </View>\n );\n});\n\nconst USER_MARKDOWN = {\n body: { color: '#ffffff', fontSize: 15, lineHeight: 22 },\n paragraph: { marginTop: 4, marginBottom: 4, color: '#ffffff' },\n strong: { fontWeight: '700' as const, color: '#ffffff' },\n em: { color: '#ffffff' },\n text: { color: '#ffffff' },\n bullet_list: { marginVertical: 4 },\n ordered_list: { marginVertical: 4 },\n list_item: { marginVertical: 2, color: '#ffffff' },\n};\n\nconst styles = StyleSheet.create({\n list: {\n flex: 1,\n minHeight: 0,\n },\n listContent: {\n paddingTop: 8,\n paddingBottom: 8,\n paddingHorizontal: 8,\n },\n listContentFill: {\n flexGrow: 1,\n },\n userCol: {\n width: '100%',\n alignItems: 'flex-end',\n marginBottom: 12,\n paddingHorizontal: 4,\n },\n userTurn: {\n maxWidth: '92%',\n alignItems: 'flex-end',\n },\n userFollow: {\n marginTop: 4,\n },\n userBubble: {\n backgroundColor: USER_BUBBLE_BG,\n borderRadius: 16,\n paddingHorizontal: 12,\n paddingVertical: 8,\n },\n // Same bubble, held back: a queued turn reads as pending, not as delivered.\n userBubbleQueued: {\n opacity: 0.55,\n },\n queuedCaption: {\n fontSize: 11,\n fontWeight: '500',\n marginTop: 4,\n marginRight: 2,\n },\n assistantGroup: {\n width: '100%',\n marginBottom: 4,\n },\n assistantCol: {\n width: '100%',\n flexGrow: 0,\n alignItems: 'stretch',\n marginBottom: 8,\n paddingHorizontal: 4,\n },\n assistantColTight: {\n marginTop: -4,\n },\n});\n"],"names":["rows","TranscriptGroupView","AssistantTurnView"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChatTranscript.js","sources":["../../../src/features/chat/ChatTranscript.tsx"],"sourcesContent":["/**\n * Chat thread list that can show attachment previews.\n *\n * `@messenger-box/platform-mobile` PlanModeView maps each row to GiftedChat\n * `{ text }` only, so an image send becomes the gateway caption\n * \"Attached: IMG_0005.jpg\" with no thumbnail. This list keeps streaming\n * markdown for the assistant and draws Manus-style previews on the user side.\n *\n * Rows stay oldest → newest (same as web). A previous `inverted` FlatList\n * reversed that order on device. The composer dock already lifts with the\n * keyboard; this list just fills the leftover space and pins to the latest\n * turn.\n */\nimport React, { memo, useCallback, useEffect, useMemo, useRef, type ReactNode } from 'react';\nimport { FlatList, Platform, Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { dismissKeyboard } from '../../utils/keyboardController';\nimport { useKeyboardLiftHeight } from '../../components/KeyboardComposerDock';\nimport Markdown from 'react-native-markdown-display';\nimport type { MessageAttachment } from '../../hooks/useChatStream';\nimport { displayUserMessageText, shouldRenderUserTranscriptTurn } from '../attachments/displayUserMessageText';\nimport { MessageAttachmentPreviews } from '../attachments/MessageAttachmentPreviews';\nimport { ToolTimeline } from './ToolTimeline';\nimport { ChatWidgetCard } from './ChatWidgetCard';\nimport { splitChatWidgets } from './chatWidgets';\nimport { parseToolActivity } from './toolActivity';\nimport { parseAskUser, stripAskUser, displayAskUserAssistantText } from './askUser';\nimport { collapseDuplicateUserRows, groupConsecutiveRoleRows, type RoleGroup } from './transcriptGroups';\nimport { AssistantMarkdown } from './AssistantMarkdown';\n\nexport type TranscriptMessage = {\n id: string;\n role: string;\n content: string;\n displayContent?: string;\n attachments?: MessageAttachment[];\n /** Typed while the gateway was down: shown in place, waiting on the outbox. */\n queued?: boolean;\n};\n\ntype TranscriptRow = TranscriptMessage & { streaming?: boolean; askAnswered?: boolean };\ntype TranscriptGroup = RoleGroup<TranscriptRow>;\n\nexport const USER_BUBBLE_BG = '#2563eb';\n\nexport function ChatTranscript({\n messages,\n streamingContent,\n renderMessageActions,\n listContentStyle,\n listFooter,\n isDark = false,\n}: {\n messages: TranscriptMessage[];\n streamingContent?: string;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n listContentStyle?: StyleProp<ViewStyle>;\n listFooter?: ReactNode;\n isDark?: boolean;\n}) {\n const listRef = useRef<FlatList<TranscriptGroup>>(null);\n const stickToBottomRef = useRef(true);\n const isProgrammaticScrollRef = useRef(false);\n const ignoreScrollUntilRef = useRef(0);\n const keyboardHeight = useKeyboardLiftHeight();\n\n const historyRows = useMemo<TranscriptRow[]>(() => {\n const rows: TranscriptRow[] = [];\n messages.forEach((msg, index) => {\n if (msg.role === 'user') {\n if (shouldRenderUserTranscriptTurn(msg.content, msg.attachments)) {\n rows.push({ ...msg, streaming: false });\n }\n return;\n }\n if (msg.role !== 'assistant') {\n rows.push({ ...msg, streaming: false });\n return;\n }\n const askAnswered =\n Boolean(parseAskUser(msg.content)) && messages.slice(index + 1).some((m) => m.role === 'user');\n const visible = displayAskUserAssistantText(msg.content, askAnswered).trim();\n const tools = parseToolActivity(stripAskUser(msg.content), false);\n const hasAsk = Boolean(parseAskUser(msg.content));\n if (hasAsk && !visible && tools.steps.length === 0 && !(msg.attachments?.length ?? 0)) {\n return;\n }\n if (!hasAsk) {\n const leftover = stripAskUser(msg.content).trim();\n if (!leftover && !(msg.attachments?.length ?? 0)) return;\n }\n rows.push({ ...msg, streaming: false, askAnswered });\n });\n return rows;\n }, [messages]);\n\n const rows = useMemo<TranscriptRow[]>(() => {\n const history = collapseDuplicateUserRows(historyRows);\n const rawStream = streamingContent ?? '';\n const last = history[history.length - 1];\n const streamAskAnswered = last?.role === 'user' && Boolean(parseAskUser(rawStream));\n const streamProse = displayAskUserAssistantText(rawStream, streamAskAnswered).trim();\n const streamTools = parseToolActivity(stripAskUser(rawStream), true);\n if (!streamProse && streamTools.steps.length === 0) return history;\n if (last?.role === 'assistant') {\n const lastText = last.content.trim();\n const streamText = rawStream.trim();\n if (lastText === streamText || lastText.startsWith(streamText) || streamText.startsWith(lastText)) {\n return history;\n }\n }\n return [\n ...history,\n {\n id: '__streaming__',\n role: 'assistant',\n content: rawStream,\n streaming: true,\n askAnswered: streamAskAnswered,\n },\n ];\n }, [historyRows, streamingContent]);\n\n const groups = useMemo(() => groupConsecutiveRoleRows(rows), [rows]);\n\n const scrollToLatest = useCallback(\n (animated: boolean) => {\n if (groups.length === 0) return;\n isProgrammaticScrollRef.current = true;\n stickToBottomRef.current = true;\n const list = listRef.current;\n if (!list) return;\n list.scrollToEnd({ animated });\n },\n [groups.length],\n );\n\n useEffect(() => {\n if (groups.length === 0 || !stickToBottomRef.current) return undefined;\n const t = setTimeout(() => scrollToLatest(false), 0);\n return () => clearTimeout(t);\n }, [groups.length, streamingContent?.length, scrollToLatest]);\n\n // Spacer and list height update on the same keyboard frame. Scroll only\n // after that shrink, or we stay on the top of a long turn (image 1).\n useEffect(() => {\n if (keyboardHeight <= 0) return undefined;\n stickToBottomRef.current = true;\n ignoreScrollUntilRef.current = Date.now() + 400;\n const frame = requestAnimationFrame(() => scrollToLatest(false));\n const afterLayout = setTimeout(() => scrollToLatest(false), 64);\n const afterKeyboard = setTimeout(() => scrollToLatest(false), 280);\n return () => {\n cancelAnimationFrame(frame);\n clearTimeout(afterLayout);\n clearTimeout(afterKeyboard);\n };\n }, [keyboardHeight, scrollToLatest]);\n\n const handleScroll = useCallback(\n (e: {\n nativeEvent: {\n contentOffset: { y: number };\n contentSize: { height: number };\n layoutMeasurement: { height: number };\n };\n }) => {\n if (Date.now() < ignoreScrollUntilRef.current) {\n stickToBottomRef.current = true;\n return;\n }\n const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;\n const nearLatest = contentOffset.y + layoutMeasurement.height >= contentSize.height - 80;\n if (!isProgrammaticScrollRef.current) {\n stickToBottomRef.current = nearLatest;\n } else if (nearLatest) {\n isProgrammaticScrollRef.current = false;\n }\n },\n [],\n );\n\n const handleContentSizeChange = useCallback(() => {\n if (!stickToBottomRef.current) return;\n scrollToLatest(false);\n }, [scrollToLatest]);\n\n const renderItem = useCallback(\n ({ item }: { item: TranscriptGroup }) => (\n <TranscriptGroupView group={item} isDark={isDark} renderMessageActions={renderMessageActions} />\n ),\n [isDark, renderMessageActions],\n );\n\n return (\n <View style={styles.list}>\n <FlatList\n ref={listRef}\n data={groups}\n keyExtractor={(item) => item.id}\n renderItem={renderItem}\n extraData={`${streamingContent ?? ''}:${Boolean(listFooter)}`}\n ListFooterComponent={listFooter ? <>{listFooter}</> : null}\n initialNumToRender={12}\n maxToRenderPerBatch={8}\n updateCellsBatchingPeriod={50}\n windowSize={21}\n scrollEnabled\n nestedScrollEnabled={false}\n directionalLockEnabled\n onScroll={handleScroll}\n onContentSizeChange={handleContentSizeChange}\n onLayout={() => {\n if (stickToBottomRef.current) scrollToLatest(false);\n }}\n scrollEventThrottle={16}\n keyboardShouldPersistTaps=\"handled\"\n keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}\n automaticallyAdjustKeyboardInsets={false}\n automaticallyAdjustContentInsets={false}\n contentInsetAdjustmentBehavior=\"never\"\n contentContainerStyle={[styles.listContent, listContentStyle, !listFooter && styles.listContentFill]}\n style={styles.list}\n />\n </View>\n );\n}\n\nconst TranscriptGroupView = memo(function TranscriptGroupView({\n group,\n isDark,\n renderMessageActions,\n}: {\n group: TranscriptGroup;\n isDark: boolean;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n}) {\n if (group.role === 'user') {\n return (\n <Pressable style={styles.userCol} onPress={dismissKeyboard} accessible={false}>\n {group.items.map((item, index) => {\n const attachments = item.attachments ?? [];\n const text = displayUserMessageText(item.displayContent ?? item.content, attachments);\n if (!text && attachments.length === 0) return null;\n return (\n <View key={item.id} style={[styles.userTurn, index > 0 && styles.userFollow]}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"end\" isDark={isDark} />\n ) : null}\n {text ? (\n <View style={[styles.userBubble, item.queued && styles.userBubbleQueued]}>\n <Markdown style={USER_MARKDOWN}>{text}</Markdown>\n </View>\n ) : null}\n {item.queued ? (\n <Text style={[styles.queuedCaption, { color: isDark ? '#94a3b8' : '#64748b' }]}>\n Queued · sends when the gateway connects\n </Text>\n ) : null}\n </View>\n );\n })}\n </Pressable>\n );\n }\n\n const last = group.items[group.items.length - 1];\n\n return (\n <View style={styles.assistantGroup}>\n {group.items.map((item, index) => (\n <AssistantTurnView\n key={item.id}\n item={item}\n isDark={isDark}\n tightTop={index > 0}\n renderMessageActions={item.streaming || item !== last ? undefined : renderMessageActions}\n />\n ))}\n </View>\n );\n});\n\nconst AssistantTurnView = memo(function AssistantTurnView({\n item,\n isDark,\n tightTop,\n renderMessageActions,\n}: {\n item: TranscriptRow;\n isDark: boolean;\n tightTop: boolean;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n}) {\n const attachments = item.attachments ?? [];\n const stripped = stripAskUser(item.content);\n const toolActivity = parseToolActivity(stripped, !!item.streaming);\n // Widget envelopes become native cards below the text, never raw JSON.\n const { text, widgets } = splitChatWidgets(\n parseToolActivity(displayAskUserAssistantText(item.content, !!item.askAnswered), !!item.streaming).text,\n !!item.streaming,\n );\n\n return (\n <View style={[styles.assistantCol, tightTop && styles.assistantColTight]}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"start\" isDark={isDark} />\n ) : null}\n {toolActivity.steps.length > 0 ? (\n <ToolTimeline steps={toolActivity.steps} active={!!item.streaming} isDark={isDark} />\n ) : null}\n {text ? <AssistantMarkdown text={text} streaming={!!item.streaming} isDark={isDark} /> : null}\n {widgets.map((widget, i) => (\n <ChatWidgetCard key={`${widget.component}-${i}`} widget={widget} isDark={isDark} />\n ))}\n {renderMessageActions\n ? renderMessageActions({ id: item.id, role: 'assistant', content: item.content })\n : null}\n </View>\n );\n});\n\nconst USER_MARKDOWN = {\n body: { color: '#ffffff', fontSize: 15, lineHeight: 22 },\n paragraph: { marginTop: 4, marginBottom: 4, color: '#ffffff' },\n strong: { fontWeight: '700' as const, color: '#ffffff' },\n em: { color: '#ffffff' },\n text: { color: '#ffffff' },\n bullet_list: { marginVertical: 4 },\n ordered_list: { marginVertical: 4 },\n list_item: { marginVertical: 2, color: '#ffffff' },\n};\n\nconst styles = StyleSheet.create({\n list: {\n flex: 1,\n minHeight: 0,\n },\n listContent: {\n paddingTop: 8,\n paddingBottom: 8,\n paddingHorizontal: 8,\n },\n listContentFill: {\n flexGrow: 1,\n },\n userCol: {\n width: '100%',\n alignItems: 'flex-end',\n marginBottom: 12,\n paddingHorizontal: 4,\n },\n userTurn: {\n maxWidth: '92%',\n alignItems: 'flex-end',\n },\n userFollow: {\n marginTop: 4,\n },\n userBubble: {\n backgroundColor: USER_BUBBLE_BG,\n borderRadius: 16,\n paddingHorizontal: 12,\n paddingVertical: 8,\n },\n // Same bubble, held back: a queued turn reads as pending, not as delivered.\n userBubbleQueued: {\n opacity: 0.55,\n },\n queuedCaption: {\n fontSize: 11,\n fontWeight: '500',\n marginTop: 4,\n marginRight: 2,\n },\n assistantGroup: {\n width: '100%',\n marginBottom: 4,\n },\n assistantCol: {\n width: '100%',\n flexGrow: 0,\n alignItems: 'stretch',\n marginBottom: 8,\n paddingHorizontal: 4,\n },\n assistantColTight: {\n marginTop: -4,\n },\n});\n"],"names":["rows","TranscriptGroupView","AssistantTurnView"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {jsx}from'react/jsx-runtime';import {memo}from'react';import {Linking}from'react-native';import {isFlightAttachment,FlightCardRow}from'@admin-layout/gluestack-ui-mobile/lib/components/FlightCards/index.js';const openOffer = (url) => {
|
|
2
|
+
void Linking.openURL(url);
|
|
3
|
+
};
|
|
4
|
+
const ChatWidgetCard = memo(function ChatWidgetCard2({
|
|
5
|
+
widget,
|
|
6
|
+
isDark
|
|
7
|
+
}) {
|
|
8
|
+
const attachment = widget.attachment;
|
|
9
|
+
if (widget.component === "travel" && isFlightAttachment(attachment)) {
|
|
10
|
+
return /* @__PURE__ */ jsx(FlightCardRow, { attachment, onOpenOffer: openOffer, scheme: isDark ? "dark" : "light" });
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
});export{ChatWidgetCard};//# sourceMappingURL=ChatWidgetCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatWidgetCard.js","sources":["../../../src/features/chat/ChatWidgetCard.tsx"],"sourcesContent":["import React, { memo } from 'react';\nimport { Linking } from 'react-native';\n// The flight-cards entry, not the package root: this renders inside the chat\n// that surface remotes bundle, and the root (layout, redux, client barrel)\n// would ship as a private copy in each of them.\nimport {\n FlightCardRow,\n isFlightAttachment,\n} from '@admin-layout/gluestack-ui-mobile/lib/components/FlightCards/index.js';\nimport type { FlightOfferSetLike } from '@admin-layout/gluestack-ui-mobile/lib/components/FlightCards/index.js';\nimport type { ChatWidget } from './chatWidgets';\n\nconst openOffer = (url: string) => {\n void Linking.openURL(url);\n};\n\n/**\n * The native card for a widget envelope in the main chat. Flights render as\n * the shared gluestack flight cards (the web chat renders the same results\n * with ai-chat-ext's tailwind cards, from the same model). A widget with no\n * native card yet renders nothing: its envelope is still kept out of the\n * text, so the thread never shows raw JSON.\n */\nexport const ChatWidgetCard = memo(function ChatWidgetCard({\n widget,\n isDark,\n}: {\n widget: ChatWidget;\n isDark: boolean;\n}) {\n const attachment = widget.attachment as FlightOfferSetLike & { title?: string; totalCount?: number };\n if (widget.component === 'travel' && isFlightAttachment(attachment)) {\n return <FlightCardRow attachment={attachment} onOpenOffer={openOffer} scheme={isDark ? 'dark' : 'light'} />;\n }\n return null;\n});\n"],"names":["ChatWidgetCard"],"mappings":"qNAQA,MAAM,SAAA,GAAY,CAAC,GAAgB,KAAA;AACjC,EAAK,KAAA,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAC1B,CAAA;AASa,MAAA,cAAA,GAAiB,IAAK,CAAA,SAASA,eAAe,CAAA;AAAA,EACzD,MAAA;AAAA,EACA;AACF,CAGG,EAAA;AACD,EAAA,MAAM,aAAa,MAAO,CAAA,UAAA;AAI1B,EAAA,IAAI,MAAO,CAAA,SAAA,KAAc,QAAY,IAAA,kBAAA,CAAmB,UAAU,CAAG,EAAA;AACnE,IAAO,uBAAA,GAAA,CAAC,iBAAc,UAAwB,EAAA,WAAA,EAAa,WAAW,MAAQ,EAAA,MAAA,GAAS,SAAS,OAAS,EAAA,CAAA;AAAA;AAE3G,EAAO,OAAA,IAAA;AACT,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {jsxs,jsx}from'react/jsx-runtime';import {View,StyleSheet,Text,Pressable}from'react-native';import {USER_BUBBLE_BG}from'./ChatTranscript.js';function QueuedFollowUps({
|
|
2
|
+
items,
|
|
3
|
+
notice,
|
|
4
|
+
onCancel,
|
|
5
|
+
onSendNow,
|
|
6
|
+
isDark
|
|
7
|
+
}) {
|
|
8
|
+
if (items.length === 0 && !notice) return null;
|
|
9
|
+
const muted = isDark ? "#94a3b8" : "#64748b";
|
|
10
|
+
const action = isDark ? "#93c5fd" : "#2563eb";
|
|
11
|
+
return /* @__PURE__ */ jsxs(View, { style: styles.container, testID: "queued-follow-ups", children: [
|
|
12
|
+
notice ? /* @__PURE__ */ jsx(Text, { style: [styles.notice, {
|
|
13
|
+
color: muted
|
|
14
|
+
}], accessibilityRole: "text", children: notice }) : null,
|
|
15
|
+
items.map((item) => /* @__PURE__ */ jsxs(View, { style: styles.item, children: [
|
|
16
|
+
/* @__PURE__ */ jsx(View, { style: styles.bubble, children: /* @__PURE__ */ jsx(Text, { style: styles.bubbleText, numberOfLines: 3, children: item.text }) }),
|
|
17
|
+
/* @__PURE__ */ jsxs(View, { style: styles.captionRow, children: [
|
|
18
|
+
/* @__PURE__ */ jsx(Text, { style: [styles.caption, {
|
|
19
|
+
color: muted
|
|
20
|
+
}], children: "Queued \xB7 answered after the current reply" }),
|
|
21
|
+
/* @__PURE__ */ jsx(Pressable, { onPress: () => onCancel(item.id), hitSlop: 8, accessibilityRole: "button", accessibilityLabel: `Cancel queued message: ${item.text}`, style: styles.action, children: /* @__PURE__ */ jsx(Text, { style: [styles.actionText, {
|
|
22
|
+
color: action
|
|
23
|
+
}], children: "Cancel" }) })
|
|
24
|
+
] })
|
|
25
|
+
] }, item.id)),
|
|
26
|
+
items.length > 0 ? /* @__PURE__ */ jsx(Pressable, { onPress: onSendNow, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Stop the current reply and send the queued messages now", style: [styles.action, styles.sendNow], children: /* @__PURE__ */ jsx(Text, { style: [styles.actionText, {
|
|
27
|
+
color: action
|
|
28
|
+
}], children: "Stop reply and send now" }) }) : null
|
|
29
|
+
] });
|
|
30
|
+
}
|
|
31
|
+
const styles = StyleSheet.create({
|
|
32
|
+
container: {
|
|
33
|
+
paddingHorizontal: 12,
|
|
34
|
+
paddingBottom: 6,
|
|
35
|
+
alignItems: "flex-end"
|
|
36
|
+
},
|
|
37
|
+
notice: {
|
|
38
|
+
alignSelf: "stretch",
|
|
39
|
+
fontSize: 12,
|
|
40
|
+
lineHeight: 17,
|
|
41
|
+
marginBottom: 6
|
|
42
|
+
},
|
|
43
|
+
item: {
|
|
44
|
+
alignItems: "flex-end",
|
|
45
|
+
marginBottom: 6,
|
|
46
|
+
maxWidth: "85%"
|
|
47
|
+
},
|
|
48
|
+
// The user's bubble, held back: queued reads as pending, not as delivered.
|
|
49
|
+
bubble: {
|
|
50
|
+
backgroundColor: USER_BUBBLE_BG,
|
|
51
|
+
borderRadius: 16,
|
|
52
|
+
paddingHorizontal: 12,
|
|
53
|
+
paddingVertical: 8,
|
|
54
|
+
opacity: 0.55
|
|
55
|
+
},
|
|
56
|
+
bubbleText: {
|
|
57
|
+
color: "#ffffff",
|
|
58
|
+
fontSize: 15,
|
|
59
|
+
lineHeight: 21
|
|
60
|
+
},
|
|
61
|
+
captionRow: {
|
|
62
|
+
flexDirection: "row",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
marginTop: 4
|
|
65
|
+
},
|
|
66
|
+
caption: {
|
|
67
|
+
fontSize: 11,
|
|
68
|
+
fontWeight: "500"
|
|
69
|
+
},
|
|
70
|
+
action: {
|
|
71
|
+
paddingHorizontal: 8,
|
|
72
|
+
paddingVertical: 2
|
|
73
|
+
},
|
|
74
|
+
actionText: {
|
|
75
|
+
fontSize: 12,
|
|
76
|
+
fontWeight: "600"
|
|
77
|
+
},
|
|
78
|
+
sendNow: {
|
|
79
|
+
marginTop: -2
|
|
80
|
+
}
|
|
81
|
+
});export{QueuedFollowUps};//# sourceMappingURL=QueuedFollowUps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueuedFollowUps.js","sources":["../../../src/features/chat/QueuedFollowUps.tsx"],"sourcesContent":["/**\n * Messages sent while the agent was busy, waiting their turn above the\n * composer - the way Claude Code shows a message typed mid-turn. Each reads as\n * the user's own bubble held back (the same look as a turn queued for the\n * gateway) and can be cancelled; \"Send now\" stops the running reply so the\n * agent takes them next. The gateway's latest word on the running work, when\n * the user asked for it or the wait got long, sits on top.\n */\nimport React from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport type { QueuedFollowUp } from '../../hooks/useChatStream';\nimport { USER_BUBBLE_BG } from './ChatTranscript';\n\nexport interface QueuedFollowUpsProps {\n items: QueuedFollowUp[];\n notice: string | null;\n onCancel: (id: string) => void;\n onSendNow: () => void;\n isDark: boolean;\n}\n\nexport function QueuedFollowUps({ items, notice, onCancel, onSendNow, isDark }: QueuedFollowUpsProps) {\n if (items.length === 0 && !notice) return null;\n const muted = isDark ? '#94a3b8' : '#64748b';\n const action = isDark ? '#93c5fd' : '#2563eb';\n return (\n <View style={styles.container} testID=\"queued-follow-ups\">\n {notice ? (\n <Text style={[styles.notice, { color: muted }]} accessibilityRole=\"text\">\n {notice}\n </Text>\n ) : null}\n {items.map((item) => (\n <View key={item.id} style={styles.item}>\n <View style={styles.bubble}>\n <Text style={styles.bubbleText} numberOfLines={3}>\n {item.text}\n </Text>\n </View>\n <View style={styles.captionRow}>\n <Text style={[styles.caption, { color: muted }]}>\n Queued · answered after the current reply\n </Text>\n <Pressable\n onPress={() => onCancel(item.id)}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel={`Cancel queued message: ${item.text}`}\n style={styles.action}\n >\n <Text style={[styles.actionText, { color: action }]}>Cancel</Text>\n </Pressable>\n </View>\n </View>\n ))}\n {items.length > 0 ? (\n <Pressable\n onPress={onSendNow}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Stop the current reply and send the queued messages now\"\n style={[styles.action, styles.sendNow]}\n >\n <Text style={[styles.actionText, { color: action }]}>Stop reply and send now</Text>\n </Pressable>\n ) : null}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n paddingHorizontal: 12,\n paddingBottom: 6,\n alignItems: 'flex-end',\n },\n notice: {\n alignSelf: 'stretch',\n fontSize: 12,\n lineHeight: 17,\n marginBottom: 6,\n },\n item: {\n alignItems: 'flex-end',\n marginBottom: 6,\n maxWidth: '85%',\n },\n // The user's bubble, held back: queued reads as pending, not as delivered.\n bubble: {\n backgroundColor: USER_BUBBLE_BG,\n borderRadius: 16,\n paddingHorizontal: 12,\n paddingVertical: 8,\n opacity: 0.55,\n },\n bubbleText: {\n color: '#ffffff',\n fontSize: 15,\n lineHeight: 21,\n },\n captionRow: {\n flexDirection: 'row',\n alignItems: 'center',\n marginTop: 4,\n },\n caption: {\n fontSize: 11,\n fontWeight: '500',\n },\n action: {\n paddingHorizontal: 8,\n paddingVertical: 2,\n },\n actionText: {\n fontSize: 12,\n fontWeight: '600',\n },\n sendNow: {\n marginTop: -2,\n },\n});\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const WIDGET_FENCE = /```(?:yantra:widget|json)[^\S\n]*\n?([\s\S]*?)```/gi;
|
|
2
|
+
const OPEN_WIDGET_TAIL = /```(?:yantra:widget|json)[^\S\n]*\n?\s*\{\s*"propsComponent"[\s\S]*$/i;
|
|
3
|
+
const OPEN_YANTRA_WIDGET_TAIL = /```yantra:widget[\s\S]*$/i;
|
|
4
|
+
function toWidget(body) {
|
|
5
|
+
var _a, _b, _c;
|
|
6
|
+
let parsed;
|
|
7
|
+
try {
|
|
8
|
+
parsed = JSON.parse(body.trim());
|
|
9
|
+
} catch (e) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
13
|
+
const obj = parsed;
|
|
14
|
+
if (typeof obj.propsComponent !== "string" || !obj.propsComponent) return null;
|
|
15
|
+
const attachment = (_c = (_b = (_a = obj.propsConfiguration) == null ? void 0 : _a.contents) == null ? void 0 : _b.attachment) != null ? _c : obj.attachment;
|
|
16
|
+
if (!attachment || typeof attachment !== "object") return null;
|
|
17
|
+
return {
|
|
18
|
+
component: obj.propsComponent,
|
|
19
|
+
attachment
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function splitChatWidgets(content, streaming = false) {
|
|
23
|
+
const widgets = [];
|
|
24
|
+
let text = content.replace(WIDGET_FENCE, (match, body) => {
|
|
25
|
+
const widget = toWidget(body);
|
|
26
|
+
if (!widget) return match;
|
|
27
|
+
widgets.push(widget);
|
|
28
|
+
return "";
|
|
29
|
+
});
|
|
30
|
+
if (streaming) {
|
|
31
|
+
text = text.replace(OPEN_WIDGET_TAIL, "").replace(OPEN_YANTRA_WIDGET_TAIL, "");
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
text: text.replace(/\n{3,}/g, "\n\n").trim(),
|
|
35
|
+
widgets
|
|
36
|
+
};
|
|
37
|
+
}export{splitChatWidgets};//# sourceMappingURL=chatWidgets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatWidgets.js","sources":["../../../src/features/chat/chatWidgets.ts"],"sourcesContent":["/**\n * Widget envelopes in an assistant reply, for the mobile main chat.\n *\n * Skills end a reply with a card envelope in a fenced block:\n *\n * ```yantra:widget (or ```json)\n * {\"propsComponent\":\"travel\",\"propsConfiguration\":{\"contents\":{\"attachment\":{…}}}}\n * ```\n *\n * The web chat turns that into a card (parseChatWidget). The mobile chat had no\n * parser, so the envelope rendered as a raw JSON code block, and while\n * streaming it grew in place character by character. This pulls every envelope\n * out of the text and returns what the thread should show instead.\n */\n\nexport interface ChatWidget {\n component: string;\n attachment: Record<string, unknown>;\n}\n\nexport interface ChatWidgetSplit {\n /** The reply with every widget envelope removed. */\n text: string;\n widgets: ChatWidget[];\n}\n\nconst WIDGET_FENCE = /```(?:yantra:widget|json)[^\\S\\n]*\\n?([\\s\\S]*?)```/gi;\n/** An envelope still streaming in: an opened widget fence with no closing fence yet. */\nconst OPEN_WIDGET_TAIL = /```(?:yantra:widget|json)[^\\S\\n]*\\n?\\s*\\{\\s*\"propsComponent\"[\\s\\S]*$/i;\n/** The same, before the JSON reaches the component key. */\nconst OPEN_YANTRA_WIDGET_TAIL = /```yantra:widget[\\s\\S]*$/i;\n\nfunction toWidget(body: string): ChatWidget | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(body.trim());\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== 'object') return null;\n const obj = parsed as {\n propsComponent?: unknown;\n propsConfiguration?: { contents?: { attachment?: unknown } };\n attachment?: unknown;\n };\n if (typeof obj.propsComponent !== 'string' || !obj.propsComponent) return null;\n // Canonical path, tolerating the flatter `attachment` the web parser also accepts.\n const attachment = obj.propsConfiguration?.contents?.attachment ?? obj.attachment;\n if (!attachment || typeof attachment !== 'object') return null;\n return { component: obj.propsComponent, attachment: attachment as Record<string, unknown> };\n}\n\nexport function splitChatWidgets(content: string, streaming = false): ChatWidgetSplit {\n const widgets: ChatWidget[] = [];\n let text = content.replace(WIDGET_FENCE, (match, body: string) => {\n const widget = toWidget(body);\n if (!widget) return match; // an ordinary ```json example stays in the answer\n widgets.push(widget);\n return '';\n });\n if (streaming) {\n // Hide an envelope that is still arriving rather than paint half of it.\n text = text.replace(OPEN_WIDGET_TAIL, '').replace(OPEN_YANTRA_WIDGET_TAIL, '');\n }\n return { text: text.replace(/\\n{3,}/g, '\\n\\n').trim(), widgets };\n}\n"],"names":[],"mappings":"AAwBA,MAAM,YAAe,GAAA,qDAAA;AAErB,MAAM,gBAAmB,GAAA,uEAAA;AAEzB,MAAM,uBAA0B,GAAA,2BAAA;AAChC,SAAS,SAAS,IAAiC,EAAA;AA7BnD,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AA8BE,EAAI,IAAA,MAAA;AACJ,EAAI,IAAA;AACF,IAAA,MAAA,GAAS,IAAK,CAAA,KAAA,CAAM,IAAK,CAAA,IAAA,EAAM,CAAA;AAAA,GACzB,CAAA,OAAA,CAAA,EAAA;AACN,IAAO,OAAA,IAAA;AAAA;AAET,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,UAAiB,OAAA,IAAA;AAClD,EAAA,MAAM,GAAM,GAAA,MAAA;AASZ,EAAA,IAAI,OAAO,GAAI,CAAA,cAAA,KAAmB,YAAY,CAAC,GAAA,CAAI,gBAAuB,OAAA,IAAA;AAE1E,EAAM,MAAA,UAAA,GAAA,CAAa,qBAAI,kBAAJ,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAwB,aAAxB,IAAkC,GAAA,MAAA,GAAA,EAAA,CAAA,UAAA,KAAlC,YAAgD,GAAI,CAAA,UAAA;AACvE,EAAA,IAAI,CAAC,UAAA,IAAc,OAAO,UAAA,KAAe,UAAiB,OAAA,IAAA;AAC1D,EAAO,OAAA;AAAA,IACL,WAAW,GAAI,CAAA,cAAA;AAAA,IACf;AAAA,GACF;AACF;AACgB,SAAA,gBAAA,CAAiB,OAAiB,EAAA,SAAA,GAAY,KAAwB,EAAA;AACpF,EAAA,MAAM,UAAwB,EAAC;AAC/B,EAAA,IAAI,OAAO,OAAQ,CAAA,OAAA,CAAQ,YAAc,EAAA,CAAC,OAAO,IAAiB,KAAA;AAChE,IAAM,MAAA,MAAA,GAAS,SAAS,IAAI,CAAA;AAC5B,IAAI,IAAA,CAAC,QAAe,OAAA,KAAA;AACpB,IAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AACnB,IAAO,OAAA,EAAA;AAAA,GACR,CAAA;AACD,EAAA,IAAI,SAAW,EAAA;AAEb,IAAA,IAAA,GAAO,KAAK,OAAQ,CAAA,gBAAA,EAAkB,EAAE,CAAE,CAAA,OAAA,CAAQ,yBAAyB,EAAE,CAAA;AAAA;AAE/E,EAAO,OAAA;AAAA,IACL,MAAM,IAAK,CAAA,OAAA,CAAQ,SAAW,EAAA,MAAM,EAAE,IAAK,EAAA;AAAA,IAC3C;AAAA,GACF;AACF"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {useRef,useCallback,useEffect}from'react';import {AppState}from'react-native';import {useApolloClient}from'@apollo/client/index.js';import {isConnectorReply}from'common';import {quickReplyViaBrain}from'../services/brainQuickReply.js';import {stripModelCostHeader,MESSENGER_ACTIVE_STREAM_QUERY}from'./messengerStream.js';import {CHAT_RUN_TIMEOUT_TEXT,EMPTY_CHAT_RESPONSE_TEXT,resolveAssistantReply,CDECLI_MAX_RUN_MS}from'../features/chat/emptyChatResponse.js';import {splitPreResponse,isFastLaneRunId}from'../features/chat/preResponse.js';import {useGatewaySendMessageMutation,useMessengerStreamDeltaSubscription,useGatewayInboundMessageByChannelSubscription}from'common/graphql';var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
2
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
6
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -14,6 +16,10 @@ var __spreadValues = (a, b) => {
|
|
|
14
16
|
}
|
|
15
17
|
return a;
|
|
16
18
|
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
const QUEUED_EXTERNAL_ID_PREFIX = "handoff-queued-";
|
|
21
|
+
const STATUS_EXTERNAL_ID_PREFIX = "handoff-status-";
|
|
22
|
+
const GATEWAY_NOTICE_ID_PREFIX = "handoff-";
|
|
17
23
|
function isNoActiveSessionError(message) {
|
|
18
24
|
return /no active session/i.test(message);
|
|
19
25
|
}
|
|
@@ -31,6 +37,8 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
|
|
|
31
37
|
const apolloClient = useApolloClient();
|
|
32
38
|
const seededChannelRef = useRef(null);
|
|
33
39
|
const quickReplyShownRef = useRef(false);
|
|
40
|
+
const completedRunIdRef = useRef(null);
|
|
41
|
+
const deliveredInboundRef = useRef([]);
|
|
34
42
|
const ingestAccumulatedText = useCallback((accumulated, asFastLane = false) => {
|
|
35
43
|
var _a, _b, _c, _d;
|
|
36
44
|
const stripped = stripModelCostHeader(accumulated);
|
|
@@ -79,6 +87,7 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
|
|
|
79
87
|
pendingRef.current = null;
|
|
80
88
|
hasReceivedDeltasRef.current = false;
|
|
81
89
|
streamCompletedRef.current = true;
|
|
90
|
+
completedRunIdRef.current = lastRunIdRef.current;
|
|
82
91
|
runStartedAtRef.current = null;
|
|
83
92
|
clearRunTimers();
|
|
84
93
|
void sendMutation({
|
|
@@ -141,6 +150,12 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
|
|
|
141
150
|
const delta = (_a = data == null ? void 0 : data.data) == null ? void 0 : _a.messengerStreamDelta;
|
|
142
151
|
if (!delta) return;
|
|
143
152
|
if (delta.isFinal) return;
|
|
153
|
+
if (streamCompletedRef.current && delta.runId && delta.runId !== completedRunIdRef.current && !isFastLaneRunId(delta.runId)) {
|
|
154
|
+
streamCompletedRef.current = false;
|
|
155
|
+
hasReceivedDeltasRef.current = false;
|
|
156
|
+
accumulatedLenRef.current = 0;
|
|
157
|
+
lastRunIdRef.current = null;
|
|
158
|
+
}
|
|
144
159
|
const prevRunId = lastRunIdRef.current;
|
|
145
160
|
if (delta.runId && delta.runId !== prevRunId) {
|
|
146
161
|
accumulatedLenRef.current = 0;
|
|
@@ -206,26 +221,40 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
|
|
|
206
221
|
onData: ({
|
|
207
222
|
data
|
|
208
223
|
}) => {
|
|
209
|
-
var _a, _b, _c, _d;
|
|
224
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
210
225
|
const msg = (_a = data == null ? void 0 : data.data) == null ? void 0 : _a.gatewayInboundMessageByChannel;
|
|
211
226
|
if (!msg) return;
|
|
212
|
-
|
|
213
|
-
|
|
227
|
+
const externalId = (_b = msg.externalId) != null ? _b : "";
|
|
228
|
+
if (externalId && deliveredInboundRef.current.includes(externalId)) return;
|
|
229
|
+
const sanitizedText = stripModelCostHeader((_c = msg.text) != null ? _c : "");
|
|
214
230
|
const {
|
|
215
231
|
preResponse,
|
|
216
232
|
text: finalText
|
|
217
233
|
} = splitPreResponse(sanitizedText);
|
|
218
|
-
if (
|
|
234
|
+
if (externalId.startsWith(GATEWAY_NOTICE_ID_PREFIX) && finalText.trim() && !streamCompletedRef.current && (pendingRef.current || hasReceivedDeltasRef.current)) {
|
|
235
|
+
deliveredInboundRef.current = [...deliveredInboundRef.current.slice(-49), externalId];
|
|
236
|
+
(_e = (_d = cbRef.current).onNotice) == null ? void 0 : _e.call(_d, finalText.trim());
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (streamCompletedRef.current) {
|
|
240
|
+
if (!externalId || !finalText.trim()) return;
|
|
241
|
+
streamCompletedRef.current = false;
|
|
242
|
+
hasReceivedDeltasRef.current = false;
|
|
243
|
+
accumulatedLenRef.current = 0;
|
|
244
|
+
}
|
|
245
|
+
if (preResponse) (_g = (_f = cbRef.current).onQuickReply) == null ? void 0 : _g.call(_f, preResponse);
|
|
219
246
|
if (msg.text && !finalText.trim() && preResponse) return;
|
|
220
247
|
if (!finalText.trim() && !pendingRef.current && !hasReceivedDeltasRef.current) return;
|
|
221
248
|
if (finalText.trim() && !hasReceivedDeltasRef.current) {
|
|
222
249
|
cbRef.current.onChunk(finalText);
|
|
223
250
|
}
|
|
224
251
|
cbRef.current.onComplete(resolveAssistantReply(finalText));
|
|
252
|
+
if (externalId) deliveredInboundRef.current = [...deliveredInboundRef.current.slice(-49), externalId];
|
|
225
253
|
pendingRef.current = null;
|
|
226
254
|
hasReceivedDeltasRef.current = false;
|
|
227
255
|
reconnectedStreamRef.current = false;
|
|
228
256
|
streamCompletedRef.current = true;
|
|
257
|
+
completedRunIdRef.current = lastRunIdRef.current;
|
|
229
258
|
accumulatedLenRef.current = 0;
|
|
230
259
|
lastRunIdRef.current = null;
|
|
231
260
|
runStartedAtRef.current = null;
|
|
@@ -355,11 +384,102 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
|
|
|
355
384
|
};
|
|
356
385
|
}
|
|
357
386
|
}, [isConnected, accountId, sendMutation]);
|
|
387
|
+
const sendQueued = useCallback(async (text, chatId = "messenger", media = []) => {
|
|
388
|
+
var _a, _b;
|
|
389
|
+
if (!isConnected) return {
|
|
390
|
+
status: "failed",
|
|
391
|
+
error: "CDeCLI is not connected."
|
|
392
|
+
};
|
|
393
|
+
try {
|
|
394
|
+
const result = await sendMutation({
|
|
395
|
+
variables: {
|
|
396
|
+
input: __spreadProps(__spreadValues({
|
|
397
|
+
channelType: "cdecli-serve",
|
|
398
|
+
accountId,
|
|
399
|
+
chatId,
|
|
400
|
+
text
|
|
401
|
+
}, media.length > 0 ? {
|
|
402
|
+
media
|
|
403
|
+
} : {}), {
|
|
404
|
+
metadata: __spreadValues(__spreadValues({
|
|
405
|
+
queueAware: true
|
|
406
|
+
}, model && {
|
|
407
|
+
model
|
|
408
|
+
}), skill && {
|
|
409
|
+
skill
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
const payload = (_a = result.data) == null ? void 0 : _a.gatewaySendMessage;
|
|
415
|
+
if (!(payload == null ? void 0 : payload.success)) return {
|
|
416
|
+
status: "failed",
|
|
417
|
+
error: (payload == null ? void 0 : payload.error) || "CDeCLI send failed"
|
|
418
|
+
};
|
|
419
|
+
const externalId = (_b = payload.externalId) != null ? _b : "";
|
|
420
|
+
if (externalId.startsWith(QUEUED_EXTERNAL_ID_PREFIX)) {
|
|
421
|
+
return {
|
|
422
|
+
status: "queued",
|
|
423
|
+
id: externalId.slice(QUEUED_EXTERNAL_ID_PREFIX.length)
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (externalId.startsWith(STATUS_EXTERNAL_ID_PREFIX)) return {
|
|
427
|
+
status: "answered"
|
|
428
|
+
};
|
|
429
|
+
return {
|
|
430
|
+
status: "started"
|
|
431
|
+
};
|
|
432
|
+
} catch (err) {
|
|
433
|
+
return {
|
|
434
|
+
status: "failed",
|
|
435
|
+
error: err instanceof Error ? err.message : String(err)
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}, [isConnected, accountId, sendMutation, model, skill]);
|
|
439
|
+
const cancelQueued = useCallback(async (id, chatId = "messenger") => {
|
|
440
|
+
var _a;
|
|
441
|
+
if (!isConnected) return {
|
|
442
|
+
ok: false,
|
|
443
|
+
error: "CDeCLI is not connected."
|
|
444
|
+
};
|
|
445
|
+
try {
|
|
446
|
+
const result = await sendMutation({
|
|
447
|
+
variables: {
|
|
448
|
+
input: {
|
|
449
|
+
channelType: "cdecli-serve",
|
|
450
|
+
accountId,
|
|
451
|
+
chatId,
|
|
452
|
+
text: "",
|
|
453
|
+
metadata: {
|
|
454
|
+
dequeue: id
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
const payload = (_a = result.data) == null ? void 0 : _a.gatewaySendMessage;
|
|
460
|
+
return (payload == null ? void 0 : payload.success) ? {
|
|
461
|
+
ok: true
|
|
462
|
+
} : {
|
|
463
|
+
ok: false,
|
|
464
|
+
error: (payload == null ? void 0 : payload.error) || "Could not cancel that message."
|
|
465
|
+
};
|
|
466
|
+
} catch (err) {
|
|
467
|
+
return {
|
|
468
|
+
ok: false,
|
|
469
|
+
error: err instanceof Error ? err.message : String(err)
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}, [isConnected, accountId, sendMutation]);
|
|
358
473
|
const cancel = useCallback(async (chatId = "messenger") => {
|
|
359
474
|
var _a;
|
|
360
475
|
if (!isConnected) return false;
|
|
361
476
|
pendingRef.current = null;
|
|
362
477
|
runStartedAtRef.current = null;
|
|
478
|
+
if (hasReceivedDeltasRef.current || lastRunIdRef.current) {
|
|
479
|
+
streamCompletedRef.current = true;
|
|
480
|
+
completedRunIdRef.current = lastRunIdRef.current;
|
|
481
|
+
hasReceivedDeltasRef.current = false;
|
|
482
|
+
}
|
|
363
483
|
clearRunTimers();
|
|
364
484
|
try {
|
|
365
485
|
const result = await sendMutation({
|
|
@@ -392,6 +512,8 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
|
|
|
392
512
|
}, [isConnected, accountId, sendMutation, clearRunTimers]);
|
|
393
513
|
return {
|
|
394
514
|
sendMessage,
|
|
515
|
+
sendQueued,
|
|
516
|
+
cancelQueued,
|
|
395
517
|
steer,
|
|
396
518
|
cancel
|
|
397
519
|
};
|