@adminide-stack/yantra-mobile 12.0.69-alpha.18 → 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 +1 -1
- package/lib/features/chat/ChatTranscript.js.map +1 -1
- package/lib/features/chat/QueuedFollowUps.js +81 -0
- package/lib/features/chat/QueuedFollowUps.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 +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 { 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\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 // 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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"}
|
|
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,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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"}
|
|
@@ -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
|
};
|