@adminide-stack/yantra-mobile 12.0.53-alpha.2 → 12.0.53-alpha.3
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/features/chat/ChatTranscript.js +7 -0
- package/lib/features/chat/ChatTranscript.js.map +1 -1
- package/lib/hooks/useChatApi.js +195 -25
- package/lib/hooks/useChatApi.js.map +1 -1
- package/lib/hooks/useChatStream.js +56 -30
- package/lib/hooks/useChatStream.js.map +1 -1
- package/lib/screens/Home/components/ChatHistoryLanding.js +4 -2
- package/lib/screens/Home/components/ChatHistoryLanding.js.map +1 -1
- package/lib/state/chatThreadMerge.js +76 -2
- package/lib/state/chatThreadMerge.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatThreadMerge.js","sources":["../../src/state/chatThreadMerge.ts"],"sourcesContent":["/**\n * Positional thread merge, mirroring the browser package's\n * `state/chatThreadMerge.ts` (yantra-app PR #750). Kept as a dependency-free leaf\n * so it can be unit-tested without dragging in the Apollo / inversify import chain\n * that `useChatStream` pulls in.\n *\n * Mobile chat routes exclusively through CDeCLI (messenger-gateway) and lets the\n * gateway persist posts (`persistenceMode: 'backend'`). Many gateway lanes -\n * notably cdecli-serve skill turns - NEVER persist the assistant post: no\n * `chatMessageAdded` ever arrives and the reply lives only in the in-memory\n * thread. Before this merge the hydration effect wholesale-replaced the visible\n * thread with the backend snapshot on every refetch, so those replies vanished\n * the moment the next backend snapshot landed (and were gone on reopen).\n *\n * The merge keeps backend rows authoritative wherever they content-match, while\n * preserving optimistic-only rows in place:\n * - a user send the backend has not written yet stays visible (on mobile every\n * send is a gateway send, so there is no separate frontend-save lane that would\n * re-deliver it);\n * - an assistant reply the backend has no row for stays visible, unless the\n * backend's next row is itself an assistant for that turn (prefer the persisted\n * text, e.g. a webbuilder RESULT) or the text is an ephemeral status line.\n *\n * Attachment previews are intentionally NOT handled here - the caller composes\n * this with `preserveAttachmentPreviews`, which owns grafting the in-memory\n * data-URL onto the persisted user row.\n */\n\ntype MergeableMessage = {\n role: string;\n content: string;\n};\n\n/** Short gateway/workflow progress lines that must not stick under a final reply. */\nexport function isEphemeralStreamStatus(text: string | null | undefined): boolean {\n const t = (text ?? '').trim();\n if (!t) return false;\n return /^(Initializing\\.?\\.?\\.?|Setting up( your (project|workspace))?\\.?\\.?\\.?)$/i.test(t);\n}\n\nexport function mergeOptimisticGatewayMessages<T extends MergeableMessage>(optimistic: T[], backend: T[]): T[] {\n if (!optimistic.length || !backend.length) return backend;\n\n const norm = (value: string) => value.trim();\n\n const merged: T[] = [];\n let cursor = 0;\n for (const message of optimistic) {\n let matchIndex = -1;\n for (let j = cursor; j < backend.length; j++) {\n const candidate = backend[j];\n if (candidate.role === message.role && norm(candidate.content) === norm(message.content)) {\n matchIndex = j;\n break;\n }\n }\n if (matchIndex >= 0) {\n // Adopt every backend row up to and including the match so rows persisted\n // by other paths (channel mirror, sibling client) keep their position.\n while (cursor <= matchIndex) {\n merged.push(backend[cursor]);\n cursor += 1;\n }\n continue;\n }\n if (message.role === 'user') {\n // Every mobile send is a gateway send; keep it until the backend writes it.\n merged.push(message);\n continue;\n }\n // Assistant reply with no backend row. If the backend's next row is itself an\n // assistant (this turn's canonical reply, possibly different text), prefer it.\n // Otherwise keep the optimistic reply - dropping it here is what made replies\n // vanish the moment the next message was sent. Ephemeral status lines never\n // earn a permanent bubble.\n if (backend[cursor]?.role === 'assistant') {\n continue;\n }\n if (norm(message.content) && !isEphemeralStreamStatus(message.content)) {\n merged.push(message);\n }\n }\n // Backend rows past the last match (e.g. the assistant post landing mid-merge)\n // still belong at the end.\n while (cursor < backend.length) {\n merged.push(backend[cursor]);\n cursor += 1;\n }\n\n return merged;\n}\n"],"names":[],"mappings":"AAkCO,SAAS,wBAAwB,IAA0C,EAAA;AAChF,EAAM,MAAA,CAAA,GAAA,CAAK,IAAQ,IAAA,IAAA,GAAA,IAAA,GAAA,EAAA,EAAI,IAAK,EAAA;AAC5B,EAAI,IAAA,CAAC,GAAU,OAAA,KAAA;AACf,EAAO,OAAA,4EAAA,CAA6E,KAAK,CAAC,CAAA;AAC5F;AACgB,SAAA,8BAAA,CAA2D,YAAiB,OAAmB,EAAA;AAvC/G,EAAA,IAAA,EAAA;AAwCE,EAAA,IAAI,CAAC,UAAW,CAAA,MAAA,IAAU,CAAC,OAAA,CAAQ,QAAe,OAAA,OAAA;AAClD,EAAA,MAAM,IAAO,GAAA,CAAC,KAAkB,KAAA,KAAA,CAAM,IAAK,EAAA;AAC3C,EAAA,MAAM,SAAc,EAAC;AACrB,EAAA,IAAI,MAAS,GAAA,CAAA;AACb,EAAA,KAAA,MAAW,WAAW,UAAY,EAAA;AAChC,IAAA,IAAI,UAAa,GAAA,EAAA;AACjB,IAAA,KAAA,IAAS,CAAI,GAAA,MAAA,EAAQ,CAAI,GAAA,OAAA,CAAQ,QAAQ,CAAK,EAAA,EAAA;AAC5C,MAAM,MAAA,SAAA,GAAY,QAAQ,CAAC,CAAA;AAC3B,MAAI,IAAA,SAAA,CAAU,IAAS,KAAA,OAAA,CAAQ,IAAQ,IAAA,IAAA,CAAK,SAAU,CAAA,OAAO,CAAM,KAAA,IAAA,CAAK,OAAQ,CAAA,OAAO,CAAG,EAAA;AACxF,QAAa,UAAA,GAAA,CAAA;AACb,QAAA;AAAA;AACF;AAEF,IAAA,IAAI,cAAc,CAAG,EAAA;AAGnB,MAAA,OAAO,UAAU,UAAY,EAAA;AAC3B,QAAO,MAAA,CAAA,IAAA,CAAK,OAAQ,CAAA,MAAM,CAAC,CAAA;AAC3B,QAAU,MAAA,IAAA,CAAA;AAAA;AAEZ,MAAA;AAAA;AAEF,IAAI,IAAA,OAAA,CAAQ,SAAS,MAAQ,EAAA;AAE3B,MAAA,MAAA,CAAO,KAAK,OAAO,CAAA;AACnB,MAAA;AAAA;AAOF,IAAA,IAAA,CAAA,CAAI,EAAQ,GAAA,OAAA,CAAA,MAAM,CAAd,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAiB,UAAS,WAAa,EAAA;AACzC,MAAA;AAAA;AAEF,IAAI,IAAA,IAAA,CAAK,QAAQ,OAAO,CAAA,IAAK,CAAC,uBAAwB,CAAA,OAAA,CAAQ,OAAO,CAAG,EAAA;AACtE,MAAA,MAAA,CAAO,KAAK,OAAO,CAAA;AAAA;AACrB;AAIF,EAAO,OAAA,MAAA,GAAS,QAAQ,MAAQ,EAAA;AAC9B,IAAO,MAAA,CAAA,IAAA,CAAK,OAAQ,CAAA,MAAM,CAAC,CAAA;AAC3B,IAAU,MAAA,IAAA,CAAA;AAAA;AAEZ,EAAO,OAAA,MAAA;AACT"}
|
|
1
|
+
{"version":3,"file":"chatThreadMerge.js","sources":["../../src/state/chatThreadMerge.ts"],"sourcesContent":["/**\n * Positional thread merge, mirroring the browser package's\n * `state/chatThreadMerge.ts` (yantra-app PR #750). Kept as a dependency-free leaf\n * so it can be unit-tested without dragging in the Apollo / inversify import chain\n * that `useChatStream` pulls in.\n *\n * Mobile chat routes exclusively through CDeCLI (messenger-gateway) and lets the\n * gateway persist posts (`persistenceMode: 'backend'`). Many gateway lanes -\n * notably cdecli-serve skill turns - NEVER persist the assistant post: no\n * `chatMessageAdded` ever arrives and the reply lives only in the in-memory\n * thread. Before this merge the hydration effect wholesale-replaced the visible\n * thread with the backend snapshot on every refetch, so those replies vanished\n * the moment the next backend snapshot landed (and were gone on reopen).\n *\n * The merge keeps backend rows authoritative wherever they content-match, while\n * preserving optimistic-only rows in place:\n * - a user send the backend has not written yet stays visible (on mobile every\n * send is a gateway send, so there is no separate frontend-save lane that would\n * re-deliver it);\n * - an assistant reply the backend has no row for stays visible, unless the\n * backend's next row is itself an assistant for that turn (prefer the persisted\n * text, e.g. a webbuilder RESULT) or the text is an ephemeral status line.\n *\n * Attachment previews are intentionally NOT handled here - the caller composes\n * this with `preserveAttachmentPreviews`, which owns grafting the in-memory\n * data-URL onto the persisted user row.\n */\n\ntype MergeableMessage = {\n role: string;\n content: string;\n};\n\n/** Short gateway/workflow progress lines that must not stick under a final reply. */\nexport function isEphemeralStreamStatus(text: string | null | undefined): boolean {\n const t = (text ?? '').trim();\n if (!t) return false;\n return /^(Initializing\\.?\\.?\\.?|Setting up( your (project|workspace))?\\.?\\.?\\.?)$/i.test(t);\n}\n\n/**\n * One user turn must not keep both a quick-reply persist and the final persist.\n * Consecutive assistant rows from the backend are the same turn — keep the\n * longest non-ephemeral text (the completed answer).\n */\nexport function takeCanonicalAssistantRun<T extends MergeableMessage>(\n rows: readonly T[],\n start: number,\n): { row: T; next: number } {\n let end = start;\n while (end + 1 < rows.length && rows[end + 1].role === 'assistant') end += 1;\n let best = start;\n let bestLen = -1;\n for (let i = start; i <= end; i++) {\n if (isEphemeralStreamStatus(rows[i].content)) continue;\n const n = (rows[i].content ?? '').trim().length;\n if (n >= bestLen) {\n best = i;\n bestLen = n;\n }\n }\n return { row: rows[best], next: end + 1 };\n}\n\nexport function collapseConsecutiveAssistants<T extends MergeableMessage>(rows: T[]): T[] {\n const out: T[] = [];\n let i = 0;\n while (i < rows.length) {\n if (rows[i].role === 'assistant') {\n const run = takeCanonicalAssistantRun(rows, i);\n out.push(run.row);\n i = run.next;\n } else {\n out.push(rows[i]);\n i += 1;\n }\n }\n return out;\n}\n\n/** After stream complete, replace an assistant already on the last turn instead of stacking a second bubble. */\nexport function appendOrReplaceAssistant<T extends MergeableMessage>(prev: T[], assistant: T): T[] {\n if (prev[prev.length - 1]?.role === 'assistant') {\n const next = prev.slice();\n next[next.length - 1] = { ...next[next.length - 1], ...assistant };\n return next;\n }\n return [...prev, assistant];\n}\n\nexport function mergeOptimisticGatewayMessages<T extends MergeableMessage>(optimistic: T[], backend: T[]): T[] {\n if (!optimistic.length || !backend.length) return backend;\n\n const norm = (value: string) => value.trim();\n\n const merged: T[] = [];\n let cursor = 0;\n for (const message of optimistic) {\n let matchIndex = -1;\n for (let j = cursor; j < backend.length; j++) {\n const candidate = backend[j];\n if (candidate.role === message.role && norm(candidate.content) === norm(message.content)) {\n matchIndex = j;\n break;\n }\n }\n if (matchIndex >= 0) {\n while (cursor <= matchIndex) {\n if (backend[cursor].role === 'assistant') {\n const run = takeCanonicalAssistantRun(backend, cursor);\n merged.push(run.row);\n cursor = run.next;\n continue;\n }\n merged.push(backend[cursor]);\n cursor += 1;\n }\n continue;\n }\n if (message.role === 'user') {\n // Every mobile send is a gateway send; keep it until the backend writes it.\n merged.push(message);\n continue;\n }\n // Assistant reply with no backend row. If the backend's next row is itself an\n // assistant (this turn's canonical reply, possibly different text), prefer it.\n // Otherwise keep the optimistic reply - dropping it here is what made replies\n // vanish the moment the next message was sent. Ephemeral status lines never\n // earn a permanent bubble.\n if (backend[cursor]?.role === 'assistant') {\n const run = takeCanonicalAssistantRun(backend, cursor);\n merged.push(run.row);\n cursor = run.next;\n continue;\n }\n if (norm(message.content) && !isEphemeralStreamStatus(message.content)) {\n merged.push(message);\n }\n }\n // Backend rows past the last match (e.g. the assistant post landing mid-merge)\n // still belong at the end. Consecutive assistants are one turn — keep one.\n while (cursor < backend.length) {\n if (backend[cursor].role === 'assistant') {\n const run = takeCanonicalAssistantRun(backend, cursor);\n merged.push(run.row);\n cursor = run.next;\n continue;\n }\n merged.push(backend[cursor]);\n cursor += 1;\n }\n\n return collapseConsecutiveAssistants(merged);\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminide-stack/yantra-mobile",
|
|
3
|
-
"version": "12.0.53-alpha.
|
|
3
|
+
"version": "12.0.53-alpha.3",
|
|
4
4
|
"description": "Sample core for higher packages to depend on",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "CDMBase LLC",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"typescript": {
|
|
58
58
|
"definition": "lib/index.d.ts"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "899b78a6c15b777ecc0f3b89934fe0f91cf620e8"
|
|
61
61
|
}
|