@adminide-stack/yantra-mobile 12.0.44-alpha.17 → 12.0.44-alpha.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/lib/components/NavigationHeader/NavigationHeader.js +15 -16
  2. package/lib/components/NavigationHeader/NavigationHeader.js.map +1 -1
  3. package/lib/features/apps/AppsCatalog.js +2 -1
  4. package/lib/features/apps/AppsCatalog.js.map +1 -1
  5. package/lib/features/apps/resolveSurfaceUrl.js +22 -2
  6. package/lib/features/apps/resolveSurfaceUrl.js.map +1 -1
  7. package/lib/features/attachments/ComposerAttachmentBar.js +64 -0
  8. package/lib/features/attachments/ComposerAttachmentBar.js.map +1 -0
  9. package/lib/features/attachments/MessageAttachmentPreviews.js +77 -0
  10. package/lib/features/attachments/MessageAttachmentPreviews.js.map +1 -0
  11. package/lib/features/attachments/attachmentPreviewUri.js +9 -0
  12. package/lib/features/attachments/attachmentPreviewUri.js.map +1 -0
  13. package/lib/features/attachments/buildGatewayMedia.js +118 -0
  14. package/lib/features/attachments/buildGatewayMedia.js.map +1 -0
  15. package/lib/features/attachments/composerAttachmentHandoff.js +15 -0
  16. package/lib/features/attachments/composerAttachmentHandoff.js.map +1 -0
  17. package/lib/features/attachments/displayUserMessageText.js +19 -0
  18. package/lib/features/attachments/displayUserMessageText.js.map +1 -0
  19. package/lib/features/attachments/historyAttachmentLabel.js +51 -0
  20. package/lib/features/attachments/historyAttachmentLabel.js.map +1 -0
  21. package/lib/features/attachments/isImageAttachment.js +7 -0
  22. package/lib/features/attachments/isImageAttachment.js.map +1 -0
  23. package/lib/features/attachments/preserveAttachmentPreviews.js +41 -0
  24. package/lib/features/attachments/preserveAttachmentPreviews.js.map +1 -0
  25. package/lib/features/attachments/uploadChatAttachments.js +102 -0
  26. package/lib/features/attachments/uploadChatAttachments.js.map +1 -0
  27. package/lib/features/attachments/useImageAttachments.js +102 -7
  28. package/lib/features/attachments/useImageAttachments.js.map +1 -1
  29. package/lib/features/canvas/nativeViewerRegistry.js +8 -1
  30. package/lib/features/canvas/nativeViewerRegistry.js.map +1 -1
  31. package/lib/features/canvas/surfaceIndex.js +25 -0
  32. package/lib/features/canvas/surfaceIndex.js.map +1 -0
  33. package/lib/features/canvas/useCanvasSession.js +57 -0
  34. package/lib/features/canvas/useCanvasSession.js.map +1 -0
  35. package/lib/features/chat/ChatTranscript.js +198 -0
  36. package/lib/features/chat/ChatTranscript.js.map +1 -0
  37. package/lib/hooks/useCdecliChannel.js +5 -3
  38. package/lib/hooks/useCdecliChannel.js.map +1 -1
  39. package/lib/hooks/useChatApi.js +32 -15
  40. package/lib/hooks/useChatApi.js.map +1 -1
  41. package/lib/hooks/useChatStream.js +35 -8
  42. package/lib/hooks/useChatStream.js.map +1 -1
  43. package/lib/index.js +1 -1
  44. package/lib/index.js.map +1 -1
  45. package/lib/screens/CanvasBoard/index.js +20 -15
  46. package/lib/screens/CanvasBoard/index.js.map +1 -1
  47. package/lib/screens/Chat/index.js +39 -62
  48. package/lib/screens/Chat/index.js.map +1 -1
  49. package/lib/screens/ChatHistory/index.js +2 -8
  50. package/lib/screens/ChatHistory/index.js.map +1 -1
  51. package/lib/screens/Home/HomeScreen.js +57 -21
  52. package/lib/screens/Home/HomeScreen.js.map +1 -1
  53. package/lib/screens/Home/components/ChatHistoryLanding.js +15 -9
  54. package/lib/screens/Home/components/ChatHistoryLanding.js.map +1 -1
  55. package/lib/screens/WebBuilder/index.js.map +1 -1
  56. package/lib/utils/navigateToNewChatHome.js +40 -0
  57. package/lib/utils/navigateToNewChatHome.js.map +1 -0
  58. package/package.json +2 -2
  59. package/lib/features/canvas/canvasCore.js +0 -41
  60. package/lib/features/canvas/canvasCore.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaceIndex.js","sources":["../../../src/features/canvas/surfaceIndex.ts"],"sourcesContent":["/**\n * Native reader for the canvas boards in the SurfaceChannel index.\n *\n * The index (`servers/surface-index-subgraph-server`) is the ONLY store for a\n * canvas: the whole `PersistedCanvasState` rides in the row's `metadata`, and\n * `mySurfaceChannels` enumerates the caller's boards. Rows are per-USER, so the\n * phone — signed in as the same user — sees exactly what the web surface sees.\n *\n * This is the native twin of `@yantra/canvas-surface`'s `state/boards.ts`\n * (`pullBoards`). It is copied rather than imported for the same reason\n * `canvasCore.ts` is: that package builds for the DOM (vite, `import.meta.env`,\n * `window.location`), and importing it would drag the web toolchain into Metro.\n * The contract that matters is the wire shape, which both sides pin.\n *\n * Reads are best-effort: an unreachable index yields an error string for the UI\n * rather than throwing, matching how the web surface degrades.\n */\nimport { parseBoardState, type PersistedCanvasState } from './canvasCore';\n\n/** Fields the index returns for a channel row (mirror of CHANNEL_FIELDS). */\nconst CHANNEL_FIELDS = 'id surface channelId title metadata updatedAt';\n\n/**\n * The web surface's `/c/new` scratch board. It is not a real board (no host\n * session backs it), and `pullBoards` skips it — so must this, or an empty\n * scratch row would shadow the user's actual canvases.\n */\nconst SCRATCH_BOARD_ID = 'global';\n\nexport interface CanvasBoardSummary {\n channelId: string;\n title?: string;\n savedAt: number;\n itemCount: number;\n board: PersistedCanvasState;\n}\n\n/**\n * Where the index lives for this environment.\n *\n * Mirrors the surface-kit fallback: the index is a sibling host of the backend,\n * so drop the backend's first label and prefix `surface-index-backend.`\n * (`ideback.yantra-app-v1.cdebase.dev` -> `surface-index-backend.yantra-app-v1.cdebase.dev`).\n * An explicit URL always wins, so a non-standard environment sets one env var\n * instead of needing a code change.\n */\nexport function surfaceIndexUrl(graphqlUrl: string, explicit?: string): string {\n const override = (explicit || '').trim();\n if (override) return override;\n\n const base = (graphqlUrl || '').trim();\n if (!base) return '';\n try {\n const { protocol, host } = parseOrigin(base);\n const labels = host.split('.');\n if (labels.length < 3) return '';\n return `${protocol}//surface-index-backend.${labels.slice(1).join('.')}/graphql`;\n } catch {\n return '';\n }\n}\n\nfunction parseOrigin(url: string): { protocol: string; host: string } {\n // RN has URL via react-native-url-polyfill (imported in the app entry), but\n // this module is also loaded by jest and the surface build, so parse by hand.\n const match = url.match(/^(https?:)\\/\\/([^/]+)/i);\n if (!match) throw new Error(`unparseable url: ${url}`);\n return { protocol: match[1].toLowerCase(), host: match[2].toLowerCase() };\n}\n\nexport interface FetchBoardsOptions {\n url: string;\n token?: string | null;\n signal?: AbortSignal;\n}\n\nexport interface FetchBoardsResult {\n boards: CanvasBoardSummary[];\n /** Plain sentence for the UI; null when the read succeeded. */\n error: string | null;\n}\n\n/**\n * The caller's canvases, newest first.\n *\n * Empty boards are dropped exactly as the web grid drops them: a row whose\n * `metadata` holds no items is a canvas that was opened and never drawn on, and\n * showing it as a board the user can open is a dead end.\n */\nexport async function fetchMyCanvasBoards(opts: FetchBoardsOptions): Promise<FetchBoardsResult> {\n if (!opts.url) {\n return { boards: [], error: 'No canvas index configured for this build.' };\n }\n\n let payload: SurfaceIndexResponse;\n try {\n const response = await fetch(opts.url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...(opts.token ? { authorization: `Bearer ${opts.token}` } : {}),\n },\n body: JSON.stringify({\n query: `query MySurfaceChannels($surface: SurfaceType) {\n mySurfaceChannels(surface: $surface) { ${CHANNEL_FIELDS} }\n }`,\n variables: { surface: 'canvas' },\n }),\n signal: opts.signal,\n });\n\n if (response.status === 401 || response.status === 403) {\n return { boards: [], error: 'Your session expired. Sign in again to see your canvases.' };\n }\n if (!response.ok) {\n return { boards: [], error: `The canvas index is unreachable (HTTP ${response.status}).` };\n }\n payload = (await response.json()) as SurfaceIndexResponse;\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n return { boards: [], error: null };\n }\n return { boards: [], error: 'Could not reach the canvas index. Check your connection.' };\n }\n\n if (payload?.errors?.length) {\n // GraphQL errors arrive with HTTP 200. The index reports an unauthenticated\n // caller as \"Missing tenantId in userContext\", which is a developer\n // sentence - say what the user can actually do about it.\n const raw = payload.errors[0]?.message || '';\n const isAuth = /tenantid|unauthenticated|unauthorized|not authorised|forbidden/i.test(raw);\n return {\n boards: [],\n error: isAuth\n ? 'Your session expired. Sign in again to see your canvases.'\n : raw || 'The canvas index rejected the request.',\n };\n }\n\n const rows = payload?.data?.mySurfaceChannels ?? [];\n const boards: CanvasBoardSummary[] = [];\n for (const row of rows) {\n const channelId = typeof row?.channelId === 'string' ? row.channelId : '';\n if (!channelId || channelId === SCRATCH_BOARD_ID) continue;\n const board = parseBoardState(coerceMetadata(row?.metadata));\n if (!board || board.items.length === 0) continue;\n boards.push({\n channelId,\n title: board.title || (typeof row?.title === 'string' ? row.title : undefined),\n savedAt: board.savedAt,\n itemCount: board.items.length,\n board,\n });\n }\n\n boards.sort((a, b) => b.savedAt - a.savedAt);\n return { boards, error: null };\n}\n\n/**\n * `metadata` is a JSON scalar, which arrives decoded — but a backend that types\n * it as a String hands back the encoded text instead, and a board that silently\n * fails to parse looks exactly like an empty canvas. Accept both.\n */\nfunction coerceMetadata(value: unknown): unknown {\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\ninterface SurfaceIndexResponse {\n data?: { mySurfaceChannels?: Array<Record<string, unknown>> | null } | null;\n errors?: Array<{ message?: string }>;\n}\n"],"names":[],"mappings":"AA6CgB,SAAA,eAAA,CAAgB,YAAoB,QAA2B,EAAA;AAC7E,EAAM,MAAA,QAAA,GAAA,CAAY,QAAY,IAAA,EAAA,EAAI,IAAK,EAAA;AACvC,EAAA,IAAI,UAAiB,OAAA,QAAA;AACrB,EAAM,MAAA,IAAA,GAAA,CAAQ,UAAc,IAAA,EAAA,EAAI,IAAK,EAAA;AACrC,EAAI,IAAA,CAAC,MAAa,OAAA,EAAA;AAClB,EAAI,IAAA;AACF,IAAM,MAAA;AAAA,MACJ,QAAA;AAAA,MACA;AAAA,KACF,GAAI,YAAY,IAAI,CAAA;AACpB,IAAM,MAAA,MAAA,GAAS,IAAK,CAAA,KAAA,CAAM,GAAG,CAAA;AAC7B,IAAI,IAAA,MAAA,CAAO,MAAS,GAAA,CAAA,EAAU,OAAA,EAAA;AAC9B,IAAO,OAAA,CAAA,EAAG,QAAQ,CAA2B,wBAAA,EAAA,MAAA,CAAO,MAAM,CAAC,CAAA,CAAE,IAAK,CAAA,GAAG,CAAC,CAAA,QAAA,CAAA;AAAA,GAChE,CAAA,OAAA,CAAA,EAAA;AACN,IAAO,OAAA,EAAA;AAAA;AAEX;AACA,SAAS,YAAY,GAGnB,EAAA;AAGA,EAAM,MAAA,KAAA,GAAQ,GAAI,CAAA,KAAA,CAAM,wBAAwB,CAAA;AAChD,EAAA,IAAI,CAAC,KAAO,EAAA,MAAM,IAAI,KAAM,CAAA,CAAA,iBAAA,EAAoB,GAAG,CAAE,CAAA,CAAA;AACrD,EAAO,OAAA;AAAA,IACL,QAAU,EAAA,KAAA,CAAM,CAAC,CAAA,CAAE,WAAY,EAAA;AAAA,IAC/B,IAAM,EAAA,KAAA,CAAM,CAAC,CAAA,CAAE,WAAY;AAAA,GAC7B;AACF"}
@@ -0,0 +1,57 @@
1
+ import {useState,useMemo,useRef,useEffect}from'react';import {config}from'../../config/env-config.js';import {usePrerequisiteIds}from'../../hooks/usePrerequisiteIds.js';import {useObtainSystemToken}from'../../hooks/useObtainSystemToken.js';import {warmWebBuilderSession}from'../../screens/WebBuilder/webbuilderWarmCache.js';import {surfaceIndexUrl}from'./surfaceIndex.js';function useCanvasSession() {
2
+ const [token, setToken] = useState(null);
3
+ const [error, setError] = useState(null);
4
+ const [ready, setReady] = useState(false);
5
+ const {
6
+ orgName,
7
+ projectId,
8
+ tagId
9
+ } = usePrerequisiteIds();
10
+ const {
11
+ obtainSystemToken
12
+ } = useObtainSystemToken(true);
13
+ const indexUrl = useMemo(() => surfaceIndexUrl(config.GRAPHQL_URL, config.SURFACE_INDEX_URL), []);
14
+ const idsRef = useRef({
15
+ orgName,
16
+ projectId,
17
+ tagId
18
+ });
19
+ idsRef.current = {
20
+ orgName,
21
+ projectId,
22
+ tagId
23
+ };
24
+ useEffect(() => {
25
+ let cancelled = false;
26
+ (async () => {
27
+ try {
28
+ const session = await warmWebBuilderSession({
29
+ graphqlUrl: config.GRAPHQL_URL,
30
+ orgName: idsRef.current.orgName,
31
+ projectId: idsRef.current.projectId,
32
+ tagId: idsRef.current.tagId,
33
+ obtainSystemToken
34
+ });
35
+ if (cancelled) return;
36
+ setToken(session.token);
37
+ setError(null);
38
+ } catch (err) {
39
+ if (cancelled) return;
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ if (/waiting for org\/project/i.test(message) || !idsRef.current.orgName) return;
42
+ setError("Could not sign in to load your canvases.");
43
+ } finally {
44
+ if (!cancelled) setReady(true);
45
+ }
46
+ })();
47
+ return () => {
48
+ cancelled = true;
49
+ };
50
+ }, [obtainSystemToken, orgName, projectId]);
51
+ return {
52
+ token,
53
+ indexUrl,
54
+ error,
55
+ ready
56
+ };
57
+ }export{useCanvasSession};//# sourceMappingURL=useCanvasSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasSession.js","sources":["../../../src/features/canvas/useCanvasSession.ts"],"sourcesContent":["/**\n * The host half of the canvas: session + endpoint, nothing else.\n *\n * Everything about READING a canvas lives in the federated remote\n * (`CanvasExperience`) so it can be fixed by a surface deploy. What the remote\n * cannot know is this environment's index URL and a token the index accepts —\n * that is what this resolves and hands over.\n *\n * Auth reuses the WebBuilder warm session rather than minting its own: it is the\n * same system JWT the surfaces already run on, it is cached app-wide, and going\n * through Apollo is what makes the token acceptable to the backend (a raw\n * SecureStore Auth0 bearer 401s — see the note in WebBuilder).\n */\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { config } from '../../config/env-config';\nimport { usePrerequisiteIds } from '../../hooks/usePrerequisiteIds';\nimport { useObtainSystemToken } from '../../hooks/useObtainSystemToken';\nimport { warmWebBuilderSession } from '../../screens/WebBuilder/webbuilderWarmCache';\nimport { surfaceIndexUrl } from './surfaceIndex';\n\nexport interface CanvasSession {\n token: string | null;\n indexUrl: string;\n /** Plain sentence when the session could not be established, else null. */\n error: string | null;\n ready: boolean;\n}\n\nexport function useCanvasSession(): CanvasSession {\n const [token, setToken] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [ready, setReady] = useState(false);\n\n const { orgName, projectId, tagId } = usePrerequisiteIds();\n const { obtainSystemToken } = useObtainSystemToken(true);\n\n const indexUrl = useMemo(\n () => surfaceIndexUrl(config.GRAPHQL_URL, (config as { SURFACE_INDEX_URL?: string }).SURFACE_INDEX_URL),\n [],\n );\n\n // org/project arrive a beat after mount; hold the latest in a ref so a\n // resolved id does not restart an in-flight mint.\n const idsRef = useRef({ orgName, projectId, tagId });\n idsRef.current = { orgName, projectId, tagId };\n\n useEffect(() => {\n let cancelled = false;\n\n (async () => {\n try {\n const session = await warmWebBuilderSession({\n graphqlUrl: config.GRAPHQL_URL,\n orgName: idsRef.current.orgName,\n projectId: idsRef.current.projectId,\n tagId: idsRef.current.tagId,\n obtainSystemToken,\n });\n if (cancelled) return;\n setToken(session.token);\n setError(null);\n } catch (err) {\n if (cancelled) return;\n const message = err instanceof Error ? err.message : String(err);\n // Missing org/project is a not-yet, not a failure: leave the\n // experience on its loading state rather than showing an error.\n if (/waiting for org\\/project/i.test(message) || !idsRef.current.orgName) return;\n setError('Could not sign in to load your canvases.');\n } finally {\n if (!cancelled) setReady(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [obtainSystemToken, orgName, projectId]);\n\n return { token, indexUrl, error, ready };\n}\n"],"names":[],"mappings":"oXA0BO,SAAS,gBAAkC,GAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,KAAK,CAAA;AACxC,EAAM,MAAA;AAAA,IACJ,OAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,MACE,kBAAmB,EAAA;AACvB,EAAM,MAAA;AAAA,IACJ;AAAA,GACF,GAAI,qBAAqB,IAAI,CAAA;AAC7B,EAAM,MAAA,QAAA,GAAW,OAAQ,CAAA,MAAM,eAAgB,CAAA,MAAA,CAAO,aAAc,MAEjE,CAAA,iBAAiB,CAAG,EAAA,EAAE,CAAA;AAIzB,EAAA,MAAM,SAAS,MAAO,CAAA;AAAA,IACpB,OAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACD,CAAA;AACD,EAAA,MAAA,CAAO,OAAU,GAAA;AAAA,IACf,OAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAY,GAAA,KAAA;AAChB,IAAA,CAAC,YAAY;AACX,MAAI,IAAA;AACF,QAAM,MAAA,OAAA,GAAU,MAAM,qBAAsB,CAAA;AAAA,UAC1C,YAAY,MAAO,CAAA,WAAA;AAAA,UACnB,OAAA,EAAS,OAAO,OAAQ,CAAA,OAAA;AAAA,UACxB,SAAA,EAAW,OAAO,OAAQ,CAAA,SAAA;AAAA,UAC1B,KAAA,EAAO,OAAO,OAAQ,CAAA,KAAA;AAAA,UACtB;AAAA,SACD,CAAA;AACD,QAAA,IAAI,SAAW,EAAA;AACf,QAAA,QAAA,CAAS,QAAQ,KAAK,CAAA;AACtB,QAAA,QAAA,CAAS,IAAI,CAAA;AAAA,eACN,GAAK,EAAA;AACZ,QAAA,IAAI,SAAW,EAAA;AACf,QAAA,MAAM,UAAU,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAG/D,QAAA,IAAI,4BAA4B,IAAK,CAAA,OAAO,KAAK,CAAC,MAAA,CAAO,QAAQ,OAAS,EAAA;AAC1E,QAAA,QAAA,CAAS,0CAA0C,CAAA;AAAA,OACnD,SAAA;AACA,QAAI,IAAA,CAAC,SAAW,EAAA,QAAA,CAAS,IAAI,CAAA;AAAA;AAC/B,KACC,GAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAY,SAAA,GAAA,IAAA;AAAA,KACd;AAAA,GACC,EAAA,CAAC,iBAAmB,EAAA,OAAA,EAAS,SAAS,CAAC,CAAA;AAC1C,EAAO,OAAA;AAAA,IACL,KAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AACF"}
@@ -0,0 +1,198 @@
1
+ import {jsx,jsxs}from'react/jsx-runtime';import {useRef,useMemo,useCallback,useEffect,memo}from'react';import {FlatList,StyleSheet,View,Text}from'react-native';import Markdown from'react-native-markdown-display';import {shouldRenderUserTranscriptTurn,displayUserMessageText}from'../attachments/displayUserMessageText.js';import {MessageAttachmentPreviews}from'../attachments/MessageAttachmentPreviews.js';var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ const USER_BUBBLE_BG = "#2563eb";
21
+ function ChatTranscript({
22
+ messages,
23
+ streamingContent,
24
+ renderMessageActions,
25
+ listContentStyle,
26
+ isDark = false
27
+ }) {
28
+ const listRef = useRef(null);
29
+ const stickToBottomRef = useRef(true);
30
+ const isProgrammaticScrollRef = useRef(false);
31
+ const historyRows = useMemo(() => messages.filter((msg) => msg.role !== "user" || shouldRenderUserTranscriptTurn(msg.content, msg.attachments)).map((msg) => __spreadProps(__spreadValues({}, msg), {
32
+ streaming: false
33
+ })), [messages]);
34
+ const rows = useMemo(() => {
35
+ const stream = streamingContent == null ? void 0 : streamingContent.trim();
36
+ if (!stream) return historyRows;
37
+ return [...historyRows, {
38
+ id: "__streaming__",
39
+ role: "assistant",
40
+ content: stream,
41
+ streaming: true
42
+ }];
43
+ }, [historyRows, streamingContent]);
44
+ const scrollToEnd = useCallback((animated) => {
45
+ var _a;
46
+ isProgrammaticScrollRef.current = true;
47
+ (_a = listRef.current) == null ? void 0 : _a.scrollToEnd({
48
+ animated
49
+ });
50
+ }, []);
51
+ useEffect(() => {
52
+ if (rows.length === 0 || !stickToBottomRef.current) return void 0;
53
+ const t = setTimeout(() => scrollToEnd(false), 0);
54
+ return () => clearTimeout(t);
55
+ }, [rows.length, streamingContent == null ? void 0 : streamingContent.length, scrollToEnd]);
56
+ const handleScroll = useCallback((e) => {
57
+ const {
58
+ contentOffset,
59
+ contentSize,
60
+ layoutMeasurement
61
+ } = e.nativeEvent;
62
+ const maxOffset = Math.max(0, contentSize.height - layoutMeasurement.height);
63
+ const nearBottom = maxOffset - contentOffset.y <= 80;
64
+ if (!isProgrammaticScrollRef.current) {
65
+ stickToBottomRef.current = nearBottom || maxOffset <= 80;
66
+ } else if (nearBottom) {
67
+ isProgrammaticScrollRef.current = false;
68
+ }
69
+ }, []);
70
+ const handleContentSizeChange = useCallback(() => {
71
+ if (!stickToBottomRef.current) return;
72
+ scrollToEnd(false);
73
+ }, [scrollToEnd]);
74
+ const renderItem = useCallback(({
75
+ item
76
+ }) => /* @__PURE__ */ jsx(TranscriptRowView, { item, isDark, renderMessageActions: item.streaming ? void 0 : renderMessageActions }), [isDark, renderMessageActions]);
77
+ return /* @__PURE__ */ jsx(FlatList, { ref: listRef, data: rows, inverted: false, keyExtractor: (item) => item.id, renderItem, extraData: isDark, onScroll: handleScroll, onContentSizeChange: handleContentSizeChange, scrollEventThrottle: 16, keyboardShouldPersistTaps: "handled", keyboardDismissMode: "interactive", contentContainerStyle: [styles.listContent, listContentStyle], style: styles.list });
78
+ }
79
+ const TranscriptRowView = memo(function TranscriptRowView2({
80
+ item,
81
+ isDark,
82
+ renderMessageActions
83
+ }) {
84
+ var _a;
85
+ const isUser = item.role === "user";
86
+ const attachments = (_a = item.attachments) != null ? _a : [];
87
+ const text = isUser ? displayUserMessageText(item.content) : item.content;
88
+ if (isUser) {
89
+ return /* @__PURE__ */ jsxs(View, { style: styles.userCol, children: [
90
+ attachments.length > 0 ? /* @__PURE__ */ jsx(MessageAttachmentPreviews, { attachments, align: "end", isDark }) : null,
91
+ text ? /* @__PURE__ */ jsx(View, { style: styles.userBubble, children: /* @__PURE__ */ jsx(Text, { style: styles.userText, children: text }) }) : null
92
+ ] });
93
+ }
94
+ const markdownColor = isDark ? "#e2e8f0" : "#111827";
95
+ const codeBg = isDark ? "#1e293b" : "#f3f4f6";
96
+ return /* @__PURE__ */ jsxs(View, { style: styles.assistantCol, children: [
97
+ attachments.length > 0 ? /* @__PURE__ */ jsx(MessageAttachmentPreviews, { attachments, align: "start", isDark }) : null,
98
+ text ? /* @__PURE__ */ jsx(Markdown, { style: {
99
+ body: {
100
+ color: markdownColor,
101
+ fontSize: 15,
102
+ lineHeight: 22
103
+ },
104
+ heading1: {
105
+ fontSize: 18,
106
+ fontWeight: "700",
107
+ marginTop: 12,
108
+ marginBottom: 4,
109
+ color: markdownColor
110
+ },
111
+ heading2: {
112
+ fontSize: 16,
113
+ fontWeight: "700",
114
+ marginTop: 10,
115
+ marginBottom: 4,
116
+ color: markdownColor
117
+ },
118
+ heading3: {
119
+ fontSize: 15,
120
+ fontWeight: "600",
121
+ marginTop: 8,
122
+ marginBottom: 4,
123
+ color: markdownColor
124
+ },
125
+ strong: {
126
+ fontWeight: "700"
127
+ },
128
+ paragraph: {
129
+ marginTop: 4,
130
+ marginBottom: 4
131
+ },
132
+ list_item: {
133
+ marginVertical: 2
134
+ },
135
+ bullet_list: {
136
+ marginVertical: 4
137
+ },
138
+ ordered_list: {
139
+ marginVertical: 4
140
+ },
141
+ code_inline: {
142
+ backgroundColor: codeBg,
143
+ paddingHorizontal: 4,
144
+ borderRadius: 4,
145
+ fontSize: 14,
146
+ color: markdownColor
147
+ },
148
+ code_block: {
149
+ backgroundColor: codeBg,
150
+ padding: 12,
151
+ borderRadius: 8,
152
+ marginVertical: 8,
153
+ fontSize: 14,
154
+ color: markdownColor
155
+ }
156
+ }, children: text }) : null,
157
+ renderMessageActions ? renderMessageActions({
158
+ id: item.id,
159
+ role: "assistant",
160
+ content: item.content
161
+ }) : null
162
+ ] });
163
+ });
164
+ const styles = StyleSheet.create({
165
+ list: {
166
+ flex: 1
167
+ },
168
+ listContent: {
169
+ flexGrow: 1,
170
+ justifyContent: "flex-end",
171
+ paddingTop: 8,
172
+ paddingBottom: 8,
173
+ paddingHorizontal: 8
174
+ },
175
+ userCol: {
176
+ width: "100%",
177
+ alignItems: "flex-end",
178
+ marginBottom: 8,
179
+ paddingHorizontal: 4
180
+ },
181
+ userBubble: {
182
+ backgroundColor: USER_BUBBLE_BG,
183
+ borderRadius: 16,
184
+ padding: 12,
185
+ maxWidth: "92%"
186
+ },
187
+ userText: {
188
+ color: "#ffffff",
189
+ fontSize: 15,
190
+ lineHeight: 22
191
+ },
192
+ assistantCol: {
193
+ width: "100%",
194
+ alignItems: "flex-start",
195
+ marginBottom: 8,
196
+ paddingHorizontal: 4
197
+ }
198
+ });export{ChatTranscript};//# sourceMappingURL=ChatTranscript.js.map
@@ -0,0 +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 */\nimport React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';\nimport { FlatList, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport Markdown from 'react-native-markdown-display';\nimport type { MessageAttachment } from '../../hooks/useChatStream';\nimport { displayUserMessageText, shouldRenderUserTranscriptTurn } from '../attachments/displayUserMessageText';\nimport { MessageAttachmentPreviews } from '../attachments/MessageAttachmentPreviews';\n\nexport type TranscriptMessage = {\n id: string;\n role: string;\n content: string;\n attachments?: MessageAttachment[];\n};\n\ntype TranscriptRow = TranscriptMessage & { streaming?: boolean };\n\nconst USER_BUBBLE_BG = '#2563eb';\n\nexport function ChatTranscript({\n messages,\n streamingContent,\n renderMessageActions,\n listContentStyle,\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 isDark?: boolean;\n}) {\n const listRef = useRef<FlatList<TranscriptRow>>(null);\n const stickToBottomRef = useRef(true);\n const isProgrammaticScrollRef = useRef(false);\n\n const historyRows = useMemo<TranscriptRow[]>(\n () =>\n messages\n .filter((msg) => msg.role !== 'user' || shouldRenderUserTranscriptTurn(msg.content, msg.attachments))\n .map((msg) => ({ ...msg, streaming: false })),\n [messages],\n );\n\n const rows = useMemo<TranscriptRow[]>(() => {\n const stream = streamingContent?.trim();\n if (!stream) return historyRows;\n return [\n ...historyRows,\n {\n id: '__streaming__',\n role: 'assistant',\n content: stream,\n streaming: true,\n },\n ];\n }, [historyRows, streamingContent]);\n\n const scrollToEnd = useCallback((animated: boolean) => {\n isProgrammaticScrollRef.current = true;\n listRef.current?.scrollToEnd({ animated });\n }, []);\n\n useEffect(() => {\n if (rows.length === 0 || !stickToBottomRef.current) return undefined;\n const t = setTimeout(() => scrollToEnd(false), 0);\n return () => clearTimeout(t);\n }, [rows.length, streamingContent?.length, scrollToEnd]);\n\n const handleScroll = useCallback(\n (e: {\n nativeEvent: {\n contentOffset: { y: number };\n contentSize: { height: number };\n layoutMeasurement: { height: number };\n };\n }) => {\n const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;\n const maxOffset = Math.max(0, contentSize.height - layoutMeasurement.height);\n const nearBottom = maxOffset - contentOffset.y <= 80;\n if (!isProgrammaticScrollRef.current) {\n stickToBottomRef.current = nearBottom || maxOffset <= 80;\n } else if (nearBottom) {\n isProgrammaticScrollRef.current = false;\n }\n },\n [],\n );\n\n const handleContentSizeChange = useCallback(() => {\n if (!stickToBottomRef.current) return;\n scrollToEnd(false);\n }, [scrollToEnd]);\n\n const renderItem = useCallback(\n ({ item }: { item: TranscriptRow }) => (\n <TranscriptRowView\n item={item}\n isDark={isDark}\n renderMessageActions={item.streaming ? undefined : renderMessageActions}\n />\n ),\n [isDark, renderMessageActions],\n );\n\n return (\n <FlatList\n ref={listRef}\n data={rows}\n inverted={false}\n keyExtractor={(item) => item.id}\n renderItem={renderItem}\n extraData={isDark}\n onScroll={handleScroll}\n onContentSizeChange={handleContentSizeChange}\n scrollEventThrottle={16}\n keyboardShouldPersistTaps=\"handled\"\n keyboardDismissMode=\"interactive\"\n contentContainerStyle={[styles.listContent, listContentStyle]}\n style={styles.list}\n />\n );\n}\n\nconst TranscriptRowView = memo(function TranscriptRowView({\n item,\n isDark,\n renderMessageActions,\n}: {\n item: TranscriptRow;\n isDark: boolean;\n renderMessageActions?: (message: { id: string; role: string; content: string }) => React.ReactNode;\n}) {\n const isUser = item.role === 'user';\n const attachments = item.attachments ?? [];\n const text = isUser ? displayUserMessageText(item.content, attachments) : item.content;\n\n if (isUser) {\n return (\n <View style={styles.userCol}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"end\" isDark={isDark} />\n ) : null}\n {text ? (\n <View style={styles.userBubble}>\n <Text style={styles.userText}>{text}</Text>\n </View>\n ) : null}\n </View>\n );\n }\n\n const markdownColor = isDark ? '#e2e8f0' : '#111827';\n const codeBg = isDark ? '#1e293b' : '#f3f4f6';\n\n return (\n <View style={styles.assistantCol}>\n {attachments.length > 0 ? (\n <MessageAttachmentPreviews attachments={attachments} align=\"start\" isDark={isDark} />\n ) : null}\n {text ? (\n <Markdown\n style={{\n body: { color: markdownColor, fontSize: 15, lineHeight: 22 },\n heading1: {\n fontSize: 18,\n fontWeight: '700',\n marginTop: 12,\n marginBottom: 4,\n color: markdownColor,\n },\n heading2: {\n fontSize: 16,\n fontWeight: '700',\n marginTop: 10,\n marginBottom: 4,\n color: markdownColor,\n },\n heading3: {\n fontSize: 15,\n fontWeight: '600',\n marginTop: 8,\n marginBottom: 4,\n color: markdownColor,\n },\n strong: { fontWeight: '700' },\n paragraph: { marginTop: 4, marginBottom: 4 },\n list_item: { marginVertical: 2 },\n bullet_list: { marginVertical: 4 },\n ordered_list: { marginVertical: 4 },\n code_inline: {\n backgroundColor: codeBg,\n paddingHorizontal: 4,\n borderRadius: 4,\n fontSize: 14,\n color: markdownColor,\n },\n code_block: {\n backgroundColor: codeBg,\n padding: 12,\n borderRadius: 8,\n marginVertical: 8,\n fontSize: 14,\n color: markdownColor,\n },\n }}\n >\n {text}\n </Markdown>\n ) : null}\n {renderMessageActions\n ? renderMessageActions({ id: item.id, role: 'assistant', content: item.content })\n : null}\n </View>\n );\n});\n\nconst styles = StyleSheet.create({\n list: {\n flex: 1,\n },\n listContent: {\n flexGrow: 1,\n justifyContent: 'flex-end',\n paddingTop: 8,\n paddingBottom: 8,\n paddingHorizontal: 8,\n },\n userCol: {\n width: '100%',\n alignItems: 'flex-end',\n marginBottom: 8,\n paddingHorizontal: 4,\n },\n userBubble: {\n backgroundColor: USER_BUBBLE_BG,\n borderRadius: 16,\n padding: 12,\n maxWidth: '92%',\n },\n userText: {\n color: '#ffffff',\n fontSize: 15,\n lineHeight: 22,\n },\n assistantCol: {\n width: '100%',\n alignItems: 'flex-start',\n marginBottom: 8,\n paddingHorizontal: 4,\n },\n});\n"],"names":["TranscriptRowView"],"mappings":"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"}
@@ -96,7 +96,7 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
96
96
  cbRef.current.onError(err.message || "CDeCLI subscription error");
97
97
  }
98
98
  });
99
- const sendMessage = useCallback(async (text, chatId = "messenger") => {
99
+ const sendMessage = useCallback(async (text, chatId = "messenger", media = []) => {
100
100
  var _a;
101
101
  if (!isConnected) return false;
102
102
  pendingRef.current = {
@@ -110,12 +110,14 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
110
110
  try {
111
111
  const result = await sendMutation({
112
112
  variables: {
113
- input: __spreadValues({
113
+ input: __spreadValues(__spreadValues({
114
114
  channelType: "cdecli-serve",
115
115
  accountId,
116
116
  chatId,
117
117
  text
118
- }, model || skill ? {
118
+ }, media.length > 0 ? {
119
+ media
120
+ } : {}), model || skill ? {
119
121
  metadata: __spreadValues(__spreadValues({}, model && {
120
122
  model
121
123
  }), skill && {
@@ -1 +1 @@
1
- {"version":3,"file":"useCdecliChannel.js","sources":["../../src/hooks/useCdecliChannel.ts"],"sourcesContent":["/**\n * useCdecliChannel — wires the cdecli-serve messenger-gateway channel into the mobile chat UI.\n *\n * Kept in sync with `packages-modules/account/browser/src/hooks/useCdecliChannel.ts`.\n * When the CDeCLI channel is connected:\n * - `sendMessage(text)` calls `gatewaySendMessage`\n * - `MessengerStreamDelta` subscription delivers streaming chunks via `onChunk`\n * - `GatewayInboundMessageByChannel` delivers the final reply via `onComplete`\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport {\n useGatewaySendMessageMutation,\n useGatewayInboundMessageByChannelSubscription,\n useMessengerStreamDeltaSubscription,\n} from 'common/graphql';\n\nfunction stripModelCostHeader(content: string): string {\n const normalized = content.replace(/\\r\\n/g, '\\n');\n return normalized.replace(\n /^\\s*(?:[^\\w\\n]+\\s*)?[a-z0-9][a-z0-9._-]*\\s*\\(\\s*\\$[\\d.]+\\s*\\/\\s*MTok\\s+in\\s*\\)\\s*\\n+/i,\n '',\n );\n}\n\n// `error?: undefined` on the success arm keeps `result.error` reachable on the\n// union: with strictNullChecks off (repo-wide), TS will not narrow the\n// discriminated union after an `if (result.ok) continue`.\nexport type SteerResult = { ok: true; error?: undefined } | { ok: false; error: string };\n\nexport interface CdecliChannelCallbacks {\n onChunk: (text: string) => void;\n onComplete: (text: string) => void;\n onError: (error: string) => void;\n}\n\nexport function isNoActiveSessionError(message: string): boolean {\n return /no active session/i.test(message);\n}\n\n/** Same 120s window as web — deep CDeCLI queries can exceed 45s. */\nconst CDECLI_RESPONSE_TIMEOUT_MS = 300_000;\n\nexport function useCdecliChannel(\n isConnected: boolean,\n accountId: string,\n channelId: string | undefined,\n callbacks: CdecliChannelCallbacks,\n model?: string,\n skill?: string,\n) {\n const [sendMutation] = useGatewaySendMessageMutation();\n const cbRef = useRef(callbacks);\n cbRef.current = callbacks;\n\n const pendingRef = useRef<{ text: string; sentAt: number } | null>(null);\n const hasReceivedDeltasRef = useRef(false);\n const reconnectedStreamRef = useRef(false);\n const streamCompletedRef = useRef(false);\n const accumulatedLenRef = useRef(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const streamSkip = !channelId;\n const streamChannelId = channelId || accountId;\n\n useMessengerStreamDeltaSubscription({\n variables: { channelId: streamChannelId },\n skip: streamSkip,\n onData: ({ data }) => {\n const delta = data?.data?.messengerStreamDelta;\n if (!delta) return;\n\n if (delta.isFinal) return;\n\n if (streamCompletedRef.current) return;\n\n if (!pendingRef.current) {\n if (!reconnectedStreamRef.current) {\n reconnectedStreamRef.current = true;\n accumulatedLenRef.current = 0;\n }\n }\n\n hasReceivedDeltasRef.current = true;\n\n const fullText = stripModelCostHeader(delta.text ?? '');\n const newChunk = fullText.substring(accumulatedLenRef.current);\n accumulatedLenRef.current = fullText.length;\n\n if (newChunk) {\n cbRef.current.onChunk(newChunk);\n }\n },\n onError: (err) => {\n console.error('[useCdecliChannel] stream delta subscription error:', err);\n },\n });\n\n useGatewayInboundMessageByChannelSubscription({\n variables: { channelId: streamChannelId },\n skip: !isConnected || streamSkip,\n onData: ({ data }) => {\n const msg = data?.data?.gatewayInboundMessageByChannel;\n if (!msg?.text) return;\n const sanitizedText = stripModelCostHeader(msg.text);\n\n if (!hasReceivedDeltasRef.current) {\n cbRef.current.onChunk(sanitizedText);\n }\n\n cbRef.current.onComplete(sanitizedText);\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = true;\n accumulatedLenRef.current = 0;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n },\n onError: (err) => {\n console.error('[useCdecliChannel] subscription error:', err);\n cbRef.current.onError(err.message || 'CDeCLI subscription error');\n },\n });\n\n const sendMessage = useCallback(\n async (text: string, chatId = 'messenger'): Promise<boolean> => {\n if (!isConnected) return false;\n\n pendingRef.current = { text, sentAt: Date.now() };\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = false;\n accumulatedLenRef.current = 0;\n\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n ...(model || skill\n ? { metadata: { ...(model && { model }), ...(skill && { skill }) } }\n : {}),\n },\n },\n });\n\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI send failed';\n cbRef.current.onError(errMsg);\n pendingRef.current = null;\n return false;\n }\n\n timeoutRef.current = setTimeout(() => {\n if (pendingRef.current) {\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n timeoutRef.current = null;\n cbRef.current.onError(\n 'CDeCLI agent did not respond within 300 seconds. The query may still be processing.',\n );\n }\n }, CDECLI_RESPONSE_TIMEOUT_MS);\n\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n cbRef.current.onError(msg);\n pendingRef.current = null;\n return false;\n }\n },\n [isConnected, accountId, channelId, sendMutation, model, skill],\n );\n\n useEffect(\n () => () => {\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = false;\n accumulatedLenRef.current = 0;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n },\n [],\n );\n\n /**\n * Inject a follow-up into the turn that is currently streaming.\n * Same `gatewaySendMessage` mutation with `metadata.steer = true` (browser\n * `useCdecliChannel.steer`). Do NOT reset streaming refs: the existing\n * delta subscription must keep running.\n *\n * \"Cannot steer: no active session\" is an expected race before cdecli has\n * started the turn. Callers should queue and retry; do not `console.error`\n * (that pops the RN LogBox and looks like a crash).\n */\n const steer = useCallback(\n async (text: string, chatId = 'messenger'): Promise<SteerResult> => {\n if (!isConnected) return { ok: false, error: 'CDeCLI is not connected.' };\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n metadata: { steer: true },\n },\n },\n });\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI steer failed';\n if (!isNoActiveSessionError(errMsg)) {\n console.warn('[useCdecliChannel] steer failed:', errMsg);\n }\n return { ok: false, error: errMsg };\n }\n return { ok: true };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn('[useCdecliChannel] steer mutation error:', msg);\n return { ok: false, error: msg };\n }\n },\n [isConnected, accountId, sendMutation],\n );\n\n /**\n * Abort the in-flight turn. `metadata.cancel = true` maps to\n * `POST /v1/chat/cancel` on cdecli-serve (browser parity).\n */\n const cancel = useCallback(\n async (chatId = 'messenger'): Promise<boolean> => {\n if (!isConnected) return false;\n pendingRef.current = null;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text: '',\n metadata: { cancel: true },\n },\n },\n });\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n console.error('[useCdecliChannel] cancel failed:', payload?.error || 'CDeCLI cancel failed');\n return false;\n }\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[useCdecliChannel] cancel mutation error:', msg);\n return false;\n }\n },\n [isConnected, accountId, sendMutation],\n );\n\n return { sendMessage, steer, cancel };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useCdecliChannel.js","sources":["../../src/hooks/useCdecliChannel.ts"],"sourcesContent":["/**\n * useCdecliChannel — wires the cdecli-serve messenger-gateway channel into the mobile chat UI.\n *\n * Kept in sync with `packages-modules/account/browser/src/hooks/useCdecliChannel.ts`.\n * When the CDeCLI channel is connected:\n * - `sendMessage(text, chatId, media)` calls `gatewaySendMessage`\n * - `MessengerStreamDelta` subscription delivers streaming chunks via `onChunk`\n * - `GatewayInboundMessageByChannel` delivers the final reply via `onComplete`\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport {\n useGatewaySendMessageMutation,\n useGatewayInboundMessageByChannelSubscription,\n useMessengerStreamDeltaSubscription,\n} from 'common/graphql';\n\nfunction stripModelCostHeader(content: string): string {\n const normalized = content.replace(/\\r\\n/g, '\\n');\n return normalized.replace(\n /^\\s*(?:[^\\w\\n]+\\s*)?[a-z0-9][a-z0-9._-]*\\s*\\(\\s*\\$[\\d.]+\\s*\\/\\s*MTok\\s+in\\s*\\)\\s*\\n+/i,\n '',\n );\n}\n\n// `error?: undefined` on the success arm keeps `result.error` reachable on the\n// union: with strictNullChecks off (repo-wide), TS will not narrow the\n// discriminated union after an `if (result.ok) continue`.\nexport type SteerResult = { ok: true; error?: undefined } | { ok: false; error: string };\n\nexport interface CdecliChannelCallbacks {\n onChunk: (text: string) => void;\n onComplete: (text: string) => void;\n onError: (error: string) => void;\n}\n\nexport function isNoActiveSessionError(message: string): boolean {\n return /no active session/i.test(message);\n}\n\n/** Same 120s window as web — deep CDeCLI queries can exceed 45s. */\nconst CDECLI_RESPONSE_TIMEOUT_MS = 300_000;\n\nexport function useCdecliChannel(\n isConnected: boolean,\n accountId: string,\n channelId: string | undefined,\n callbacks: CdecliChannelCallbacks,\n model?: string,\n skill?: string,\n) {\n const [sendMutation] = useGatewaySendMessageMutation();\n const cbRef = useRef(callbacks);\n cbRef.current = callbacks;\n\n const pendingRef = useRef<{ text: string; sentAt: number } | null>(null);\n const hasReceivedDeltasRef = useRef(false);\n const reconnectedStreamRef = useRef(false);\n const streamCompletedRef = useRef(false);\n const accumulatedLenRef = useRef(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const streamSkip = !channelId;\n const streamChannelId = channelId || accountId;\n\n useMessengerStreamDeltaSubscription({\n variables: { channelId: streamChannelId },\n skip: streamSkip,\n onData: ({ data }) => {\n const delta = data?.data?.messengerStreamDelta;\n if (!delta) return;\n\n if (delta.isFinal) return;\n\n if (streamCompletedRef.current) return;\n\n if (!pendingRef.current) {\n if (!reconnectedStreamRef.current) {\n reconnectedStreamRef.current = true;\n accumulatedLenRef.current = 0;\n }\n }\n\n hasReceivedDeltasRef.current = true;\n\n const fullText = stripModelCostHeader(delta.text ?? '');\n const newChunk = fullText.substring(accumulatedLenRef.current);\n accumulatedLenRef.current = fullText.length;\n\n if (newChunk) {\n cbRef.current.onChunk(newChunk);\n }\n },\n onError: (err) => {\n console.error('[useCdecliChannel] stream delta subscription error:', err);\n },\n });\n\n useGatewayInboundMessageByChannelSubscription({\n variables: { channelId: streamChannelId },\n skip: !isConnected || streamSkip,\n onData: ({ data }) => {\n const msg = data?.data?.gatewayInboundMessageByChannel;\n if (!msg?.text) return;\n const sanitizedText = stripModelCostHeader(msg.text);\n\n if (!hasReceivedDeltasRef.current) {\n cbRef.current.onChunk(sanitizedText);\n }\n\n cbRef.current.onComplete(sanitizedText);\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = true;\n accumulatedLenRef.current = 0;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n },\n onError: (err) => {\n console.error('[useCdecliChannel] subscription error:', err);\n cbRef.current.onError(err.message || 'CDeCLI subscription error');\n },\n });\n\n const sendMessage = useCallback(\n async (\n text: string,\n chatId = 'messenger',\n media: Array<{ type: string; url: string; data?: string; mimeType?: string; filename?: string }> = [],\n ): Promise<boolean> => {\n if (!isConnected) return false;\n\n pendingRef.current = { text, sentAt: Date.now() };\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = false;\n accumulatedLenRef.current = 0;\n\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n ...(media.length > 0 ? { media: media as never } : {}),\n ...(model || skill\n ? { metadata: { ...(model && { model }), ...(skill && { skill }) } }\n : {}),\n },\n },\n });\n\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI send failed';\n cbRef.current.onError(errMsg);\n pendingRef.current = null;\n return false;\n }\n\n timeoutRef.current = setTimeout(() => {\n if (pendingRef.current) {\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n timeoutRef.current = null;\n cbRef.current.onError(\n 'CDeCLI agent did not respond within 300 seconds. The query may still be processing.',\n );\n }\n }, CDECLI_RESPONSE_TIMEOUT_MS);\n\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n cbRef.current.onError(msg);\n pendingRef.current = null;\n return false;\n }\n },\n [isConnected, accountId, channelId, sendMutation, model, skill],\n );\n\n useEffect(\n () => () => {\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = false;\n accumulatedLenRef.current = 0;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n },\n [],\n );\n\n /**\n * Inject a follow-up into the turn that is currently streaming.\n * Same `gatewaySendMessage` mutation with `metadata.steer = true` (browser\n * `useCdecliChannel.steer`). Do NOT reset streaming refs: the existing\n * delta subscription must keep running.\n *\n * \"Cannot steer: no active session\" is an expected race before cdecli has\n * started the turn. Callers should queue and retry; do not `console.error`\n * (that pops the RN LogBox and looks like a crash).\n */\n const steer = useCallback(\n async (text: string, chatId = 'messenger'): Promise<SteerResult> => {\n if (!isConnected) return { ok: false, error: 'CDeCLI is not connected.' };\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n metadata: { steer: true },\n },\n },\n });\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI steer failed';\n if (!isNoActiveSessionError(errMsg)) {\n console.warn('[useCdecliChannel] steer failed:', errMsg);\n }\n return { ok: false, error: errMsg };\n }\n return { ok: true };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn('[useCdecliChannel] steer mutation error:', msg);\n return { ok: false, error: msg };\n }\n },\n [isConnected, accountId, sendMutation],\n );\n\n /**\n * Abort the in-flight turn. `metadata.cancel = true` maps to\n * `POST /v1/chat/cancel` on cdecli-serve (browser parity).\n */\n const cancel = useCallback(\n async (chatId = 'messenger'): Promise<boolean> => {\n if (!isConnected) return false;\n pendingRef.current = null;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text: '',\n metadata: { cancel: true },\n },\n },\n });\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n console.error('[useCdecliChannel] cancel failed:', payload?.error || 'CDeCLI cancel failed');\n return false;\n }\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[useCdecliChannel] cancel mutation error:', msg);\n return false;\n }\n },\n [isConnected, accountId, sendMutation],\n );\n\n return { sendMessage, steer, cancel };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAYA,SAAS,qBAAqB,OAAyB,EAAA;AACrD,EAAA,MAAM,UAAa,GAAA,OAAA,CAAQ,OAAQ,CAAA,OAAA,EAAS,IAAI,CAAA;AAChD,EAAO,OAAA,UAAA,CAAW,OAAQ,CAAA,uFAAA,EAAyF,EAAE,CAAA;AACvH;AAiBO,SAAS,uBAAuB,OAA0B,EAAA;AAC/D,EAAO,OAAA,oBAAA,CAAqB,KAAK,OAAO,CAAA;AAC1C;AAGA,MAAM,0BAA6B,GAAA,GAAA;AAC5B,SAAS,iBAAiB,WAAsB,EAAA,SAAA,EAAmB,SAA+B,EAAA,SAAA,EAAmC,OAAgB,KAAgB,EAAA;AAC1K,EAAM,MAAA,CAAC,YAAY,CAAA,GAAI,6BAA8B,EAAA;AACrD,EAAM,MAAA,KAAA,GAAQ,OAAO,SAAS,CAAA;AAC9B,EAAA,KAAA,CAAM,OAAU,GAAA,SAAA;AAChB,EAAM,MAAA,UAAA,GAAa,OAGT,IAAI,CAAA;AACd,EAAM,MAAA,oBAAA,GAAuB,OAAO,KAAK,CAAA;AACzC,EAAM,MAAA,oBAAA,GAAuB,OAAO,KAAK,CAAA;AACzC,EAAM,MAAA,kBAAA,GAAqB,OAAO,KAAK,CAAA;AACvC,EAAM,MAAA,iBAAA,GAAoB,OAAO,CAAC,CAAA;AAClC,EAAM,MAAA,UAAA,GAAa,OAA6C,IAAI,CAAA;AACpE,EAAA,MAAM,aAAa,CAAC,SAAA;AACpB,EAAA,MAAM,kBAAkB,SAAa,IAAA,SAAA;AACrC,EAAoC,mCAAA,CAAA;AAAA,IAClC,SAAW,EAAA;AAAA,MACT,SAAW,EAAA;AAAA,KACb;AAAA,IACA,IAAM,EAAA,UAAA;AAAA,IACN,QAAQ,CAAC;AAAA,MACP;AAAA,KACI,KAAA;AA5DV,MAAA,IAAA,EAAA,EAAA,EAAA;AA6DM,MAAM,MAAA,KAAA,GAAA,CAAQ,EAAM,GAAA,IAAA,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAA,IAAA,KAAN,IAAY,GAAA,MAAA,GAAA,EAAA,CAAA,oBAAA;AAC1B,MAAA,IAAI,CAAC,KAAO,EAAA;AACZ,MAAA,IAAI,MAAM,OAAS,EAAA;AACnB,MAAA,IAAI,mBAAmB,OAAS,EAAA;AAChC,MAAI,IAAA,CAAC,WAAW,OAAS,EAAA;AACvB,QAAI,IAAA,CAAC,qBAAqB,OAAS,EAAA;AACjC,UAAA,oBAAA,CAAqB,OAAU,GAAA,IAAA;AAC/B,UAAA,iBAAA,CAAkB,OAAU,GAAA,CAAA;AAAA;AAC9B;AAEF,MAAA,oBAAA,CAAqB,OAAU,GAAA,IAAA;AAC/B,MAAA,MAAM,QAAW,GAAA,oBAAA,CAAA,CAAqB,EAAM,GAAA,KAAA,CAAA,IAAA,KAAN,YAAc,EAAE,CAAA;AACtD,MAAA,MAAM,QAAW,GAAA,QAAA,CAAS,SAAU,CAAA,iBAAA,CAAkB,OAAO,CAAA;AAC7D,MAAA,iBAAA,CAAkB,UAAU,QAAS,CAAA,MAAA;AACrC,MAAA,IAAI,QAAU,EAAA;AACZ,QAAM,KAAA,CAAA,OAAA,CAAQ,QAAQ,QAAQ,CAAA;AAAA;AAChC,KACF;AAAA,IACA,SAAS,CAAO,GAAA,KAAA;AACd,MAAQ,OAAA,CAAA,KAAA,CAAM,uDAAuD,GAAG,CAAA;AAAA;AAC1E,GACD,CAAA;AACD,EAA8C,6CAAA,CAAA;AAAA,IAC5C,SAAW,EAAA;AAAA,MACT,SAAW,EAAA;AAAA,KACb;AAAA,IACA,IAAA,EAAM,CAAC,WAAe,IAAA,UAAA;AAAA,IACtB,QAAQ,CAAC;AAAA,MACP;AAAA,KACI,KAAA;AA1FV,MAAA,IAAA,EAAA;AA2FM,MAAM,MAAA,GAAA,GAAA,CAAM,EAAM,GAAA,IAAA,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAA,IAAA,KAAN,IAAY,GAAA,MAAA,GAAA,EAAA,CAAA,8BAAA;AACxB,MAAI,IAAA,EAAC,2BAAK,IAAM,CAAA,EAAA;AAChB,MAAM,MAAA,aAAA,GAAgB,oBAAqB,CAAA,GAAA,CAAI,IAAI,CAAA;AACnD,MAAI,IAAA,CAAC,qBAAqB,OAAS,EAAA;AACjC,QAAM,KAAA,CAAA,OAAA,CAAQ,QAAQ,aAAa,CAAA;AAAA;AAErC,MAAM,KAAA,CAAA,OAAA,CAAQ,WAAW,aAAa,CAAA;AACtC,MAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AACrB,MAAA,oBAAA,CAAqB,OAAU,GAAA,KAAA;AAC/B,MAAA,oBAAA,CAAqB,OAAU,GAAA,KAAA;AAC/B,MAAA,kBAAA,CAAmB,OAAU,GAAA,IAAA;AAC7B,MAAA,iBAAA,CAAkB,OAAU,GAAA,CAAA;AAC5B,MAAA,IAAI,WAAW,OAAS,EAAA;AACtB,QAAA,YAAA,CAAa,WAAW,OAAO,CAAA;AAC/B,QAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AAAA;AACvB,KACF;AAAA,IACA,SAAS,CAAO,GAAA,KAAA;AACd,MAAQ,OAAA,CAAA,KAAA,CAAM,0CAA0C,GAAG,CAAA;AAC3D,MAAA,KAAA,CAAM,OAAQ,CAAA,OAAA,CAAQ,GAAI,CAAA,OAAA,IAAW,2BAA2B,CAAA;AAAA;AAClE,GACD,CAAA;AACD,EAAM,MAAA,WAAA,GAAc,YAAY,OAAO,IAAA,EAAc,SAAS,WAAa,EAAA,KAAA,GAMtE,EAAyB,KAAA;AAvHhC,IAAA,IAAA,EAAA;AAwHI,IAAI,IAAA,CAAC,aAAoB,OAAA,KAAA;AACzB,IAAA,UAAA,CAAW,OAAU,GAAA;AAAA,MACnB,IAAA;AAAA,MACA,MAAA,EAAQ,KAAK,GAAI;AAAA,KACnB;AACA,IAAA,oBAAA,CAAqB,OAAU,GAAA,KAAA;AAC/B,IAAA,oBAAA,CAAqB,OAAU,GAAA,KAAA;AAC/B,IAAA,kBAAA,CAAmB,OAAU,GAAA,KAAA;AAC7B,IAAA,iBAAA,CAAkB,OAAU,GAAA,CAAA;AAC5B,IAAI,IAAA;AACF,MAAM,MAAA,MAAA,GAAS,MAAM,YAAa,CAAA;AAAA,QAChC,SAAW,EAAA;AAAA,UACT,KAAO,EAAA,cAAA,CAAA,cAAA,CAAA;AAAA,YACL,WAAa,EAAA,cAAA;AAAA,YACb,SAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA,WACI,EAAA,KAAA,CAAM,SAAS,CAAI,GAAA;AAAA,YACrB;AAAA,WACE,GAAA,EACA,CAAA,EAAA,KAAA,IAAS,KAAQ,GAAA;AAAA,YACnB,QAAA,EAAU,kCACJ,KAAS,IAAA;AAAA,cACX;AAAA,gBAEE,KAAS,IAAA;AAAA,cACX;AAAA,aACF;AAAA,cAEA,EAAC;AAAA;AAET,OACD,CAAA;AACD,MAAM,MAAA,OAAA,GAAA,CAAU,EAAO,GAAA,MAAA,CAAA,IAAA,KAAP,IAAa,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,kBAAA;AAC7B,MAAI,IAAA,EAAC,mCAAS,OAAS,CAAA,EAAA;AACrB,QAAM,MAAA,MAAA,GAAA,CAAS,mCAAS,KAAS,KAAA,oBAAA;AACjC,QAAM,KAAA,CAAA,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAC5B,QAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AACrB,QAAO,OAAA,KAAA;AAAA;AAET,MAAW,UAAA,CAAA,OAAA,GAAU,WAAW,MAAM;AACpC,QAAA,IAAI,WAAW,OAAS,EAAA;AACtB,UAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AACrB,UAAA,oBAAA,CAAqB,OAAU,GAAA,KAAA;AAC/B,UAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AACrB,UAAM,KAAA,CAAA,OAAA,CAAQ,QAAQ,qFAAqF,CAAA;AAAA;AAC7G,SACC,0BAA0B,CAAA;AAC7B,MAAO,OAAA,IAAA;AAAA,aACA,GAAK,EAAA;AACZ,MAAA,MAAM,MAAM,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,MAAM,KAAA,CAAA,OAAA,CAAQ,QAAQ,GAAG,CAAA;AACzB,MAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AACrB,MAAO,OAAA,KAAA;AAAA;AACT,GACF,EAAG,CAAC,WAAa,EAAA,SAAA,EAAW,WAAW,YAAc,EAAA,KAAA,EAAO,KAAK,CAAC,CAAA;AAClE,EAAA,SAAA,CAAU,MAAM,MAAM;AACpB,IAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AACrB,IAAA,oBAAA,CAAqB,OAAU,GAAA,KAAA;AAC/B,IAAA,oBAAA,CAAqB,OAAU,GAAA,KAAA;AAC/B,IAAA,kBAAA,CAAmB,OAAU,GAAA,KAAA;AAC7B,IAAA,iBAAA,CAAkB,OAAU,GAAA,CAAA;AAC5B,IAAA,IAAI,WAAW,OAAS,EAAA;AACtB,MAAA,YAAA,CAAa,WAAW,OAAO,CAAA;AAC/B,MAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AAAA;AACvB,GACF,EAAG,EAAE,CAAA;AAYL,EAAA,MAAM,KAAQ,GAAA,WAAA,CAAY,OAAO,IAAA,EAAc,SAAS,WAAsC,KAAA;AAtMhG,IAAA,IAAA,EAAA;AAuMI,IAAI,IAAA,CAAC,aAAoB,OAAA;AAAA,MACvB,EAAI,EAAA,KAAA;AAAA,MACJ,KAAO,EAAA;AAAA,KACT;AACA,IAAI,IAAA;AACF,MAAM,MAAA,MAAA,GAAS,MAAM,YAAa,CAAA;AAAA,QAChC,SAAW,EAAA;AAAA,UACT,KAAO,EAAA;AAAA,YACL,WAAa,EAAA,cAAA;AAAA,YACb,SAAA;AAAA,YACA,MAAA;AAAA,YACA,IAAA;AAAA,YACA,QAAU,EAAA;AAAA,cACR,KAAO,EAAA;AAAA;AACT;AACF;AACF,OACD,CAAA;AACD,MAAM,MAAA,OAAA,GAAA,CAAU,EAAO,GAAA,MAAA,CAAA,IAAA,KAAP,IAAa,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,kBAAA;AAC7B,MAAI,IAAA,EAAC,mCAAS,OAAS,CAAA,EAAA;AACrB,QAAM,MAAA,MAAA,GAAA,CAAS,mCAAS,KAAS,KAAA,qBAAA;AACjC,QAAI,IAAA,CAAC,sBAAuB,CAAA,MAAM,CAAG,EAAA;AACnC,UAAQ,OAAA,CAAA,IAAA,CAAK,oCAAoC,MAAM,CAAA;AAAA;AAEzD,QAAO,OAAA;AAAA,UACL,EAAI,EAAA,KAAA;AAAA,UACJ,KAAO,EAAA;AAAA,SACT;AAAA;AAEF,MAAO,OAAA;AAAA,QACL,EAAI,EAAA;AAAA,OACN;AAAA,aACO,GAAK,EAAA;AACZ,MAAA,MAAM,MAAM,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,MAAQ,OAAA,CAAA,IAAA,CAAK,4CAA4C,GAAG,CAAA;AAC5D,MAAO,OAAA;AAAA,QACL,EAAI,EAAA,KAAA;AAAA,QACJ,KAAO,EAAA;AAAA,OACT;AAAA;AACF,GACC,EAAA,CAAC,WAAa,EAAA,SAAA,EAAW,YAAY,CAAC,CAAA;AAMzC,EAAA,MAAM,MAAS,GAAA,WAAA,CAAY,OAAO,MAAA,GAAS,WAAkC,KAAA;AArP/E,IAAA,IAAA,EAAA;AAsPI,IAAI,IAAA,CAAC,aAAoB,OAAA,KAAA;AACzB,IAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AACrB,IAAA,IAAI,WAAW,OAAS,EAAA;AACtB,MAAA,YAAA,CAAa,WAAW,OAAO,CAAA;AAC/B,MAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AAAA;AAEvB,IAAI,IAAA;AACF,MAAM,MAAA,MAAA,GAAS,MAAM,YAAa,CAAA;AAAA,QAChC,SAAW,EAAA;AAAA,UACT,KAAO,EAAA;AAAA,YACL,WAAa,EAAA,cAAA;AAAA,YACb,SAAA;AAAA,YACA,MAAA;AAAA,YACA,IAAM,EAAA,EAAA;AAAA,YACN,QAAU,EAAA;AAAA,cACR,MAAQ,EAAA;AAAA;AACV;AACF;AACF,OACD,CAAA;AACD,MAAM,MAAA,OAAA,GAAA,CAAU,EAAO,GAAA,MAAA,CAAA,IAAA,KAAP,IAAa,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,kBAAA;AAC7B,MAAI,IAAA,EAAC,mCAAS,OAAS,CAAA,EAAA;AACrB,QAAA,OAAA,CAAQ,KAAM,CAAA,mCAAA,EAAA,CAAqC,OAAS,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,OAAA,CAAA,KAAA,KAAS,sBAAsB,CAAA;AAC3F,QAAO,OAAA,KAAA;AAAA;AAET,MAAO,OAAA,IAAA;AAAA,aACA,GAAK,EAAA;AACZ,MAAA,MAAM,MAAM,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,MAAQ,OAAA,CAAA,KAAA,CAAM,6CAA6C,GAAG,CAAA;AAC9D,MAAO,OAAA,KAAA;AAAA;AACT,GACC,EAAA,CAAC,WAAa,EAAA,SAAA,EAAW,YAAY,CAAC,CAAA;AACzC,EAAO,OAAA;AAAA,IACL,WAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AACF"}
@@ -1,4 +1,4 @@
1
- import {SortEnum,RoomType,PostTypeEnum,AiAgentMessageRole}from'common';import {useGetChannelsByUserWithLastMessageQuery,useAddChannelMutation,useSendMessagesMutation,GetChannelsByUserWithLastMessageDocument,useMessagesQuery}from'common/graphql';import {useMemo,useCallback}from'react';import {v4}from'uuid';var __defProp = Object.defineProperty;
1
+ import {SortEnum,RoomType,PostTypeEnum,AiAgentMessageRole}from'common';import {useGetChannelsByUserWithLastMessageQuery,useAddChannelMutation,useSendMessagesMutation,GetChannelsByUserWithLastMessageDocument,useMessagesQuery}from'common/graphql';import {useMemo,useCallback}from'react';import {v4}from'uuid';import {isAttachedCaption,historyAttachmentTitle,attachmentsFromUserContent}from'../features/attachments/historyAttachmentLabel.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -129,11 +129,14 @@ function buildSessionFromChannel(channel) {
129
129
  resolvedTitle = rawDefault;
130
130
  }
131
131
  }
132
+ const isAttachment = isAttachedCaption(resolvedTitle) || isAttachedCaption(parentTrimmed) || isAttachedCaption(userTurn);
133
+ if (isAttachment) resolvedTitle = historyAttachmentTitle(resolvedTitle);
132
134
  const isPlaceholder = !resolvedTitle;
133
135
  if (isPlaceholder) resolvedTitle = "Thinking\u2026";
134
136
  const rawPreview = String((_d = lastMessage == null ? void 0 : lastMessage.message) != null ? _d : "").replace(/\s+/g, " ").trim();
135
137
  let preview = rawPreview;
136
138
  if (preview && preview === parentTrimmed) preview = "";
139
+ if (isAttachedCaption(preview) || preview === resolvedTitle) preview = "";
137
140
  const sortAt = (() => {
138
141
  const candidates = [channel == null ? void 0 : channel.updatedAt, lastMessage == null ? void 0 : lastMessage.updatedAt, lastMessage == null ? void 0 : lastMessage.createdAt, channel == null ? void 0 : channel.createdAt];
139
142
  for (const candidate of candidates) {
@@ -152,7 +155,8 @@ function buildSessionFromChannel(channel) {
152
155
  mode: detectMode(channel),
153
156
  updatedAt: new Date(sortAt),
154
157
  createdAt: new Date(Number.isFinite(createdAtRaw) ? createdAtRaw : sortAt),
155
- isPlaceholder
158
+ isPlaceholder,
159
+ isAttachment
156
160
  };
157
161
  }
158
162
  function chatHistorySessionsFromChannels(data) {
@@ -213,22 +217,35 @@ function mapPostToChatMessageUI(post, fallbackChannelId) {
213
217
  else if (roleRaw === "system") role = "system";
214
218
  const rawContent = (_h = post == null ? void 0 : post.message) != null ? _h : "";
215
219
  const content = sanitizeAssistantContentByRole(role, rawContent);
220
+ const fromFiles = ((_j = (_i = post == null ? void 0 : post.files) == null ? void 0 : _i.data) != null ? _j : []).map((file) => {
221
+ var _a2, _b2, _c2, _d2, _e2;
222
+ return {
223
+ id: (_a2 = file.id) != null ? _a2 : v4(),
224
+ name: (_b2 = file.name) != null ? _b2 : "file",
225
+ type: typeof file.mimeType === "string" && file.mimeType.startsWith("image/") ? "screenshot" : "file",
226
+ mimeType: (_c2 = file.mimeType) != null ? _c2 : void 0,
227
+ size: (_d2 = file.size) != null ? _d2 : void 0,
228
+ url: (_e2 = file.url) != null ? _e2 : void 0
229
+ };
230
+ });
231
+ const fromProps = Array.isArray(props.attachments) ? props.attachments.map((file) => {
232
+ var _a2, _b2, _c2, _d2, _e2;
233
+ return {
234
+ id: (_a2 = file.id) != null ? _a2 : v4(),
235
+ name: (_b2 = file.name) != null ? _b2 : "file",
236
+ type: file.type === "screenshot" || typeof file.mimeType === "string" && file.mimeType.startsWith("image/") ? "screenshot" : "file",
237
+ mimeType: (_c2 = file.mimeType) != null ? _c2 : void 0,
238
+ size: (_d2 = file.size) != null ? _d2 : void 0,
239
+ url: (_e2 = file.url) != null ? _e2 : void 0
240
+ };
241
+ }) : [];
242
+ const attachments = attachmentsFromUserContent(content, fromFiles.length ? fromFiles : fromProps);
216
243
  return {
217
- id: (_i = post == null ? void 0 : post.id) != null ? _i : v4(),
218
- sessionId: (_k = (_j = post == null ? void 0 : post.channel) == null ? void 0 : _j.id) != null ? _k : fallbackChannelId,
244
+ id: (_k = post == null ? void 0 : post.id) != null ? _k : v4(),
245
+ sessionId: (_m = (_l = post == null ? void 0 : post.channel) == null ? void 0 : _l.id) != null ? _m : fallbackChannelId,
219
246
  role,
220
247
  content,
221
- attachments: ((_m = (_l = post == null ? void 0 : post.files) == null ? void 0 : _l.data) != null ? _m : []).map((file) => {
222
- var _a2, _b2, _c2, _d2, _e2;
223
- return {
224
- id: (_a2 = file.id) != null ? _a2 : v4(),
225
- name: (_b2 = file.name) != null ? _b2 : "file",
226
- type: typeof file.mimeType === "string" && file.mimeType.startsWith("image/") ? "screenshot" : "file",
227
- mimeType: (_c2 = file.mimeType) != null ? _c2 : void 0,
228
- size: (_d2 = file.size) != null ? _d2 : void 0,
229
- url: (_e2 = file.url) != null ? _e2 : void 0
230
- };
231
- }),
248
+ attachments,
232
249
  tokenCount: (_n = props.tokenCount) != null ? _n : void 0,
233
250
  model: (_o = props.model) != null ? _o : void 0,
234
251
  createdAt: (post == null ? void 0 : post.createdAt) ? new Date(post.createdAt) : /* @__PURE__ */ new Date(),