@exotel-npm-dev/exotel-ai-assist 1.3.6 → 1.3.7

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.
@@ -0,0 +1,5 @@
1
+ import { TransferSummary } from "../../types";
2
+ export interface TransferSummaryCardProps {
3
+ data: TransferSummary;
4
+ }
5
+ export declare function TransferSummaryCard({ data }: TransferSummaryCardProps): JSX.Element;
@@ -1849,6 +1849,24 @@ class ExotelAIAssistController extends EventEmitter {
1849
1849
  };
1850
1850
  this.emit("sentiment", sentiment);
1851
1851
  }
1852
+ if (event.event_type === "transfer_summary" && event.value) {
1853
+ const summary = typeof event.value.summary === "string" ? event.value.summary.trim() : "";
1854
+ if (!summary) continue;
1855
+ const rawSentiment = event.value.sentiment;
1856
+ const sentimentLabel = rawSentiment === undefined || rawSentiment === null ? null : String(rawSentiment);
1857
+ const transferSummary = {
1858
+ summary,
1859
+ sentiment: sentimentLabel
1860
+ };
1861
+ this.emit("transferSummary", transferSummary);
1862
+ if (sentimentLabel) {
1863
+ const sentiment = {
1864
+ label: sentimentLabel.toLowerCase(),
1865
+ timestamp: now
1866
+ };
1867
+ this.emit("sentiment", sentiment);
1868
+ }
1869
+ }
1852
1870
  }
1853
1871
  }
1854
1872
  _handleStreamStatus(state) {
@@ -1,4 +1,4 @@
1
- import { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, BotConfig, StreamState } from "../types";
1
+ import { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, TransferSummary, BotConfig, StreamState } from "../types";
2
2
  export interface UseExotelAIAssistReturn {
3
3
  /**
4
4
  * `true` once the WebSocket connection is established and the server has
@@ -21,6 +21,8 @@ export interface UseExotelAIAssistReturn {
21
21
  transcripts: TranscriptLine[];
22
22
  /** Latest sentiment reading, or null before the first event. */
23
23
  sentiment: Sentiment | null;
24
+ /** Warm-transfer summary from handover-context cache, if present. */
25
+ transferSummary: TransferSummary | null;
24
26
  lastError: Error | null;
25
27
  connect: () => void;
26
28
  disconnect: () => void;
package/dist/index.d.ts CHANGED
@@ -2,4 +2,4 @@ import { ExotelAIAssistParams } from "./types";
2
2
  export declare function mountExotelAIAssist(container: HTMLElement, params: ExotelAIAssistParams): void;
3
3
  export declare function unmountExotelAIAssist(container: HTMLElement): void;
4
4
  export { ExotelAIAssistController } from "./controller";
5
- export type { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, ConnectionStatus, StreamState } from "./types";
5
+ export type { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, TransferSummary, TransferSummaryValue, ConnectionStatus, StreamState, } from "./types";
package/dist/index.js CHANGED
@@ -20175,6 +20175,24 @@ const createLucideIcon = (iconName, iconNode) => {
20175
20175
  };
20176
20176
  var createLucideIcon$1 = createLucideIcon;
20177
20177
 
20178
+ /**
20179
+ * lucide-react v0.0.1 - ISC
20180
+ */
20181
+
20182
+ const ChevronDown = createLucideIcon$1("ChevronDown", [["path", {
20183
+ d: "m6 9 6 6 6-6",
20184
+ key: "qrunsl"
20185
+ }]]);
20186
+
20187
+ /**
20188
+ * lucide-react v0.0.1 - ISC
20189
+ */
20190
+
20191
+ const ChevronUp = createLucideIcon$1("ChevronUp", [["path", {
20192
+ d: "m18 15-6-6-6 6",
20193
+ key: "153udz"
20194
+ }]]);
20195
+
20178
20196
  /**
20179
20197
  * lucide-react v0.0.1 - ISC
20180
20198
  */
@@ -22332,6 +22350,24 @@ class ExotelAIAssistController extends EventEmitter {
22332
22350
  };
22333
22351
  this.emit("sentiment", sentiment);
22334
22352
  }
22353
+ if (event.event_type === "transfer_summary" && event.value) {
22354
+ const summary = typeof event.value.summary === "string" ? event.value.summary.trim() : "";
22355
+ if (!summary) continue;
22356
+ const rawSentiment = event.value.sentiment;
22357
+ const sentimentLabel = rawSentiment === undefined || rawSentiment === null ? null : String(rawSentiment);
22358
+ const transferSummary = {
22359
+ summary,
22360
+ sentiment: sentimentLabel
22361
+ };
22362
+ this.emit("transferSummary", transferSummary);
22363
+ if (sentimentLabel) {
22364
+ const sentiment = {
22365
+ label: sentimentLabel.toLowerCase(),
22366
+ timestamp: now
22367
+ };
22368
+ this.emit("sentiment", sentiment);
22369
+ }
22370
+ }
22335
22371
  }
22336
22372
  }
22337
22373
  _handleStreamStatus(state) {
@@ -22360,6 +22396,7 @@ function useExotelAIAssist(params) {
22360
22396
  const [suggestions, setSuggestions] = reactExports.useState([]);
22361
22397
  const [transcripts, setTranscripts] = reactExports.useState([]);
22362
22398
  const [sentiment, setSentiment] = reactExports.useState(null);
22399
+ const [transferSummary, setTransferSummary] = reactExports.useState(null);
22363
22400
  const [botConfig, setBotConfig] = reactExports.useState(null);
22364
22401
  const [lastError, setLastError] = reactExports.useState(null);
22365
22402
  const paramHash = Utils.hash(params);
@@ -22369,6 +22406,7 @@ function useExotelAIAssist(params) {
22369
22406
  setSuggestions([]);
22370
22407
  setTranscripts([]);
22371
22408
  setSentiment(null);
22409
+ setTransferSummary(null);
22372
22410
  setBotConfig(null);
22373
22411
  setLastError(null);
22374
22412
  const ctrl = new ExotelAIAssistController(params);
@@ -22379,6 +22417,7 @@ function useExotelAIAssist(params) {
22379
22417
  setSuggestions([]);
22380
22418
  setTranscripts([]);
22381
22419
  setSentiment(null);
22420
+ setTransferSummary(null);
22382
22421
  }
22383
22422
  });
22384
22423
  ctrl.on("streamState", setStreamState);
@@ -22388,6 +22427,7 @@ function useExotelAIAssist(params) {
22388
22427
  return [...prev, s].slice(-50);
22389
22428
  }));
22390
22429
  ctrl.on("sentiment", setSentiment);
22430
+ ctrl.on("transferSummary", setTransferSummary);
22391
22431
  ctrl.on("transcript", lines => setTranscripts(prev => {
22392
22432
  const map = new Map(prev.map(l => [l.id, l]));
22393
22433
  for (const l of lines) map.set(l.id, l);
@@ -22440,6 +22480,7 @@ function useExotelAIAssist(params) {
22440
22480
  suggestions,
22441
22481
  transcripts,
22442
22482
  sentiment,
22483
+ transferSummary,
22443
22484
  botConfig,
22444
22485
  lastError,
22445
22486
  connect,
@@ -22474,7 +22515,7 @@ function styleInject(css, ref) {
22474
22515
  }
22475
22516
  }
22476
22517
 
22477
- var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{flex:1;min-height:0;overflow:hidden}.oa-tabs-content[data-state=inactive]{display:none!important}.oa-tabs-content[data-state=active]{display:flex;flex-direction:column}.oa-tabs-list{border-bottom:1px solid #e5e7eb;display:flex;gap:20px;padding:0}.oa-tabs-trigger{align-items:center;background:transparent;border:none;border-radius:0;color:#6b7280;cursor:pointer;display:inline-flex;font-size:15px;font-weight:500;gap:6px;outline:none;padding:10px 0;position:relative}.oa-tabs-trigger:hover{color:#374151}.oa-tabs-trigger[data-state=active]{color:#2563eb}.oa-tabs-trigger[data-state=active]:after{background:#2563eb;border-radius:2px;bottom:-1px;content:\"\";height:2px;left:0;position:absolute;right:0}.oa-suggestion-card{border:1px solid rgba(17,24,39,.04);border-radius:12px;padding:12px;text-align:left}.oa-suggestion-card--recent-wrapper{animation:oa-suggestion-enter .4s ease-out,oa-border-shimmer 3s ease-in-out .4s infinite;background:linear-gradient(90deg,#7c3aed,#e11d48,#7c3aed);background-size:200% 100%;border-radius:12px;padding:2px;text-align:left}.oa-suggestion-card--recent-wrapper .oa-suggestion-card--recent{border:none}.oa-suggestion-card--recent{animation:none;background:linear-gradient(90deg,#f5f3ff 0,#fef3f2 50%,#fff7ed);border-radius:9px}.oa-suggestion-card--older{background:#f5f5f5;border:1px solid #e5e7eb}.oa-suggestion-card--older .oa-suggestion-text{color:#252525}@keyframes oa-suggestion-enter{0%{opacity:.3;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes oa-border-shimmer{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.oa-suggestion-text{color:#111827;font-size:13px;line-height:18px;white-space:pre-wrap}.oa-copy-icon,.oa-feedback-icon{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#6b7280;cursor:pointer;display:inline-flex;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}.oa-copy-icon:hover,.oa-feedback-icon:hover{background:#f9fafb;color:#374151}.oa-feedback-icon--positive{background:#dcfce7!important;border-color:#86efac!important;color:#16a34a!important}.oa-feedback-icon--negative{background:#fee2e2!important;border-color:#fca5a5!important;color:#dc2626!important}.oa-feedback-icon:disabled{cursor:default;opacity:.5}.oa-feedback-icon--negative:disabled,.oa-feedback-icon--positive:disabled{opacity:1}.oa-feedback-reason-btn{align-items:center;background:#fef2f2;border:1px solid #fecaca;border-radius:16px;color:#991b1b;cursor:pointer;display:inline-flex;font-family:inherit;font-size:12px;font-weight:500;line-height:18px;padding:4px 12px;transition:background .15s,border-color .15s}.oa-feedback-reason-btn:hover:not(:disabled){background:#fee2e2;border-color:#f87171}.oa-feedback-reason-btn--selected{background:#dc2626!important;border-color:#dc2626!important;color:#fff!important}.oa-feedback-reason-btn:disabled{cursor:default;opacity:.45}@keyframes oa-toast-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.oa-stream-overlay{display:flex;flex:1;flex-direction:column;min-height:0}.oa-stream-banner{align-items:flex-start;background:#fef3c7;border:1px solid #fde68a;border-radius:8px;color:#92400e;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-stream-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-stream-banner-text{flex:1}.oa-stream-empty{flex:1;gap:16px;justify-content:center;min-height:0}.oa-stream-empty,.oa-stream-empty-text{align-items:center;display:flex;flex-direction:column}.oa-stream-empty-text{gap:6px;text-align:center}.oa-stream-empty-title{color:#111827;font-size:16px;font-weight:700;line-height:20px}.oa-stream-empty-subtitle{color:#6b7280;font-size:14px;line-height:20px}.oa-throttle-banner{align-items:flex-start;background:#fcefee;border:1px solid #e8cecb;border-radius:8px;color:#833d09;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-throttle-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-throttle-banner-text{flex:1}.oa-starting-loader{align-items:center;display:flex;flex:1;flex-direction:column;gap:16px;height:100%;justify-content:center;min-height:0}.oa-starting-spinner{animation:oa-spinner-rotate 1.4s linear infinite;height:48px;width:48px}.oa-starting-spinner-track{stroke:#e5e7eb}.oa-starting-spinner-arc{stroke:#2563eb;stroke-dasharray:80,126;stroke-dashoffset:0;animation:oa-spinner-dash 1.4s ease-in-out infinite}@keyframes oa-spinner-rotate{to{transform:rotate(1turn)}}@keyframes oa-spinner-dash{0%{stroke-dasharray:1,126;stroke-dashoffset:0}50%{stroke-dasharray:80,126;stroke-dashoffset:-35}to{stroke-dasharray:80,126;stroke-dashoffset:-124}}.oa-starting-loader-text{color:#111827;font-size:16px;font-weight:600;line-height:20px}.oa-muted{color:#6b7280}";
22518
+ var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{flex:1;min-height:0;overflow:hidden}.oa-tabs-content[data-state=inactive]{display:none!important}.oa-tabs-content[data-state=active]{display:flex;flex-direction:column}.oa-tabs-list{border-bottom:1px solid #e5e7eb;display:flex;gap:20px;padding:0}.oa-tabs-trigger{align-items:center;background:transparent;border:none;border-radius:0;color:#6b7280;cursor:pointer;display:inline-flex;font-size:15px;font-weight:500;gap:6px;outline:none;padding:10px 0;position:relative}.oa-tabs-trigger:hover{color:#374151}.oa-tabs-trigger[data-state=active]{color:#2563eb}.oa-tabs-trigger[data-state=active]:after{background:#2563eb;border-radius:2px;bottom:-1px;content:\"\";height:2px;left:0;position:absolute;right:0}.oa-suggestion-card{border:1px solid rgba(17,24,39,.04);border-radius:12px;padding:12px;text-align:left}.oa-suggestion-card--recent-wrapper{animation:oa-suggestion-enter .4s ease-out,oa-border-shimmer 3s ease-in-out .4s infinite;background:linear-gradient(90deg,#7c3aed,#e11d48,#7c3aed);background-size:200% 100%;border-radius:12px;padding:2px;text-align:left}.oa-suggestion-card--recent-wrapper .oa-suggestion-card--recent{border:none}.oa-suggestion-card--recent{animation:none;background:linear-gradient(90deg,#f5f3ff 0,#fef3f2 50%,#fff7ed);border-radius:9px}.oa-suggestion-card--older{background:#f5f5f5;border:1px solid #e5e7eb}.oa-suggestion-card--older .oa-suggestion-text{color:#252525}@keyframes oa-suggestion-enter{0%{opacity:.3;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes oa-border-shimmer{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.oa-suggestion-text{color:#111827;font-size:13px;line-height:18px;white-space:pre-wrap}.oa-copy-icon,.oa-feedback-icon{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#6b7280;cursor:pointer;display:inline-flex;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}.oa-copy-icon:hover,.oa-feedback-icon:hover{background:#f9fafb;color:#374151}.oa-feedback-icon--positive{background:#dcfce7!important;border-color:#86efac!important;color:#16a34a!important}.oa-feedback-icon--negative{background:#fee2e2!important;border-color:#fca5a5!important;color:#dc2626!important}.oa-feedback-icon:disabled{cursor:default;opacity:.5}.oa-feedback-icon--negative:disabled,.oa-feedback-icon--positive:disabled{opacity:1}.oa-feedback-reason-btn{align-items:center;background:#fef2f2;border:1px solid #fecaca;border-radius:16px;color:#991b1b;cursor:pointer;display:inline-flex;font-family:inherit;font-size:12px;font-weight:500;line-height:18px;padding:4px 12px;transition:background .15s,border-color .15s}.oa-feedback-reason-btn:hover:not(:disabled){background:#fee2e2;border-color:#f87171}.oa-feedback-reason-btn--selected{background:#dc2626!important;border-color:#dc2626!important;color:#fff!important}.oa-feedback-reason-btn:disabled{cursor:default;opacity:.45}@keyframes oa-toast-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.oa-stream-overlay{display:flex;flex:1;flex-direction:column;min-height:0}.oa-stream-banner{align-items:flex-start;background:#fef3c7;border:1px solid #fde68a;border-radius:8px;color:#92400e;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-stream-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-stream-banner-text{flex:1}.oa-stream-empty{flex:1;gap:16px;justify-content:center;min-height:0}.oa-stream-empty,.oa-stream-empty-text{align-items:center;display:flex;flex-direction:column}.oa-stream-empty-text{gap:6px;text-align:center}.oa-stream-empty-title{color:#111827;font-size:16px;font-weight:700;line-height:20px}.oa-stream-empty-subtitle{color:#6b7280;font-size:14px;line-height:20px}.oa-throttle-banner{align-items:flex-start;background:#fcefee;border:1px solid #e8cecb;border-radius:8px;color:#833d09;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-throttle-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-throttle-banner-text{flex:1}.oa-starting-loader{align-items:center;display:flex;flex:1;flex-direction:column;gap:16px;height:100%;justify-content:center;min-height:0}.oa-starting-spinner{animation:oa-spinner-rotate 1.4s linear infinite;height:48px;width:48px}.oa-starting-spinner-track{stroke:#e5e7eb}.oa-starting-spinner-arc{stroke:#2563eb;stroke-dasharray:80,126;stroke-dashoffset:0;animation:oa-spinner-dash 1.4s ease-in-out infinite}@keyframes oa-spinner-rotate{to{transform:rotate(1turn)}}@keyframes oa-spinner-dash{0%{stroke-dasharray:1,126;stroke-dashoffset:0}50%{stroke-dasharray:80,126;stroke-dashoffset:-35}to{stroke-dasharray:80,126;stroke-dashoffset:-124}}.oa-starting-loader-text{color:#111827;font-size:16px;font-weight:600;line-height:20px}.oa-transfer-summary-card{background:#f9f7ff;border:1px solid #ece7f8;border-radius:12px;padding:12px 14px}.oa-transfer-summary-header{user-select:none}.oa-transfer-summary-body,.oa-transfer-summary-text{text-align:left}.oa-transfer-summary-badge{align-items:center;border-radius:999px;display:inline-flex;font-size:13px;font-weight:600;padding:2px 10px}.oa-transfer-summary-badge--positive{background:#dcfce7;color:#166534}.oa-transfer-summary-badge--neutral{background:#f3f4f6;color:#374151}.oa-transfer-summary-badge--negative{background:#ffe0db;color:#9f1239}.oa-muted{color:#6b7280}";
22478
22519
  styleInject(css_248z);
22479
22520
 
22480
22521
  function Sparkle() {
@@ -23088,6 +23129,113 @@ function TranscriptTab({
23088
23129
  });
23089
23130
  }
23090
23131
 
23132
+ /** Renders `**bold**` markdown spans; leaves the rest as plain text. */
23133
+ function renderSummaryWithBold(text) {
23134
+ const parts = text.split(/(\*\*[^*]+\*\*)/g);
23135
+ return parts.map((part, i) => {
23136
+ if (part.startsWith("**") && part.endsWith("**") && part.length > 4) {
23137
+ return jsxRuntimeExports.jsx("strong", {
23138
+ children: part.slice(2, -2)
23139
+ }, i);
23140
+ }
23141
+ return jsxRuntimeExports.jsx(React.Fragment, {
23142
+ children: part
23143
+ }, i);
23144
+ });
23145
+ }
23146
+ /** Normalizes inline bullet markers into separate left-aligned lines with bold support. */
23147
+ function formatSummaryLines(summary) {
23148
+ const normalizedSummary = summary.replace(/\s+([*-•])\s+/g, "\n$1 ");
23149
+ const lines = normalizedSummary.split("\n").map(line => line.trim()).filter(Boolean);
23150
+ return lines.map((line, index) => {
23151
+ const isBulletLine = /^[-*•]\s+/.test(line);
23152
+ const lineContent = isBulletLine ? line.replace(/^[-*•]\s+/, "") : line;
23153
+ return jsxRuntimeExports.jsxs("span", {
23154
+ style: {
23155
+ display: "block"
23156
+ },
23157
+ children: [isBulletLine ? "• " : "", renderSummaryWithBold(lineContent)]
23158
+ }, `line-${index}`);
23159
+ });
23160
+ }
23161
+ function TransferSummaryCard({
23162
+ data
23163
+ }) {
23164
+ var _a, _b;
23165
+ const [expanded, setExpanded] = reactExports.useState(true);
23166
+ const sentiment = (_a = data.sentiment) !== null && _a !== void 0 ? _a : "";
23167
+ const lower = sentiment.toLowerCase();
23168
+ const icon = lower.includes("positive") ? jsxRuntimeExports.jsx(Smile, {
23169
+ size: 16
23170
+ }) : lower.includes("neutral") ? jsxRuntimeExports.jsx(Meh, {
23171
+ size: 16
23172
+ }) : lower.includes("negative") ? jsxRuntimeExports.jsx(Frown, {
23173
+ size: 16
23174
+ }) : null;
23175
+ const badgeClass = lower.includes("positive") ? "oa-transfer-summary-badge oa-transfer-summary-badge--positive" : lower.includes("negative") ? "oa-transfer-summary-badge oa-transfer-summary-badge--negative" : "oa-transfer-summary-badge oa-transfer-summary-badge--neutral";
23176
+ return jsxRuntimeExports.jsxs("div", {
23177
+ className: "oa-transfer-summary-card",
23178
+ style: {
23179
+ marginBottom: 12
23180
+ },
23181
+ children: [jsxRuntimeExports.jsxs("div", {
23182
+ className: "oa-transfer-summary-header",
23183
+ onClick: () => setExpanded(v => !v),
23184
+ style: {
23185
+ display: "flex",
23186
+ alignItems: "center",
23187
+ justifyContent: "space-between",
23188
+ cursor: "pointer"
23189
+ },
23190
+ children: [jsxRuntimeExports.jsxs("div", {
23191
+ style: {
23192
+ display: "flex",
23193
+ alignItems: "center",
23194
+ gap: 8
23195
+ },
23196
+ children: [jsxRuntimeExports.jsx("span", {
23197
+ style: {
23198
+ fontWeight: 700,
23199
+ fontSize: 16,
23200
+ color: "#111827"
23201
+ },
23202
+ children: "Transfer Summary"
23203
+ }), sentiment ? jsxRuntimeExports.jsx("span", {
23204
+ className: badgeClass,
23205
+ children: jsxRuntimeExports.jsxs("span", {
23206
+ style: {
23207
+ display: "inline-flex",
23208
+ alignItems: "center",
23209
+ gap: 4
23210
+ },
23211
+ children: [icon, sentiment]
23212
+ })
23213
+ }) : null]
23214
+ }), expanded ? jsxRuntimeExports.jsx(ChevronUp, {
23215
+ size: 16
23216
+ }) : jsxRuntimeExports.jsx(ChevronDown, {
23217
+ size: 16
23218
+ })]
23219
+ }), expanded ? jsxRuntimeExports.jsx("div", {
23220
+ className: "oa-transfer-summary-body",
23221
+ style: {
23222
+ marginTop: 8
23223
+ },
23224
+ children: jsxRuntimeExports.jsx("div", {
23225
+ className: "oa-transfer-summary-text",
23226
+ style: {
23227
+ color: "#374151",
23228
+ margin: 0,
23229
+ lineHeight: 1.5,
23230
+ fontSize: 14,
23231
+ textAlign: "left"
23232
+ },
23233
+ children: formatSummaryLines((_b = data.summary) !== null && _b !== void 0 ? _b : "")
23234
+ })
23235
+ }) : null]
23236
+ });
23237
+ }
23238
+
23091
23239
  function ExotelAIAssist(_a) {
23092
23240
  var {
23093
23241
  className,
@@ -23100,6 +23248,7 @@ function ExotelAIAssist(_a) {
23100
23248
  suggestions,
23101
23249
  transcripts,
23102
23250
  sentiment,
23251
+ transferSummary,
23103
23252
  botConfig,
23104
23253
  sendSuggestionFeedback
23105
23254
  } = useExotelAIAssist(params);
@@ -23145,7 +23294,9 @@ function ExotelAIAssist(_a) {
23145
23294
  children: [jsxRuntimeExports.jsx(Header, {
23146
23295
  sentiment: sentiment,
23147
23296
  botConfig: botConfig
23148
- }), streamState !== "throttled" ? jsxRuntimeExports.jsxs(Root2$1, {
23297
+ }), transferSummary ? jsxRuntimeExports.jsx(TransferSummaryCard, {
23298
+ data: transferSummary
23299
+ }) : null, streamState !== "throttled" ? jsxRuntimeExports.jsxs(Root2$1, {
23149
23300
  defaultValue: "suggestions",
23150
23301
  className: "oa-tabs",
23151
23302
  style: {
@@ -2,7 +2,9 @@ export { ExotelAIAssist, ExotelAIAssistApp } from "../components/ExotelAIAssistA
2
2
  export { ExotelAIAssistProvider, useExotelAIAssistContext } from "../components/ExotelAIAssistProvider";
3
3
  export { useExotelAIAssist } from "../hooks/useExotelAIAssist";
4
4
  export { ExotelAIAssistController } from "../controller";
5
- export type { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, ConnectionStatus, StreamState } from "../types";
5
+ export type { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, TransferSummary, TransferSummaryValue, ConnectionStatus, StreamState, } from "../types";
6
6
  export type { UseExotelAIAssistReturn } from "../hooks/useExotelAIAssist";
7
7
  export type { ExotelAIAssistProps } from "../components/ExotelAIAssistApp";
8
8
  export type { ExotelAIAssistProviderProps } from "../components/ExotelAIAssistProvider";
9
+ export { TransferSummaryCard } from "../components/key-components/TransferSummaryCard";
10
+ export type { TransferSummaryCardProps } from "../components/key-components/TransferSummaryCard";
@@ -5457,6 +5457,24 @@ const createLucideIcon = (iconName, iconNode) => {
5457
5457
  };
5458
5458
  var createLucideIcon$1 = createLucideIcon;
5459
5459
 
5460
+ /**
5461
+ * lucide-react v0.0.1 - ISC
5462
+ */
5463
+
5464
+ const ChevronDown = createLucideIcon$1("ChevronDown", [["path", {
5465
+ d: "m6 9 6 6 6-6",
5466
+ key: "qrunsl"
5467
+ }]]);
5468
+
5469
+ /**
5470
+ * lucide-react v0.0.1 - ISC
5471
+ */
5472
+
5473
+ const ChevronUp = createLucideIcon$1("ChevronUp", [["path", {
5474
+ d: "m18 15-6-6-6 6",
5475
+ key: "153udz"
5476
+ }]]);
5477
+
5460
5478
  /**
5461
5479
  * lucide-react v0.0.1 - ISC
5462
5480
  */
@@ -7618,6 +7636,24 @@ class ExotelAIAssistController extends EventEmitter {
7618
7636
  };
7619
7637
  this.emit("sentiment", sentiment);
7620
7638
  }
7639
+ if (event.event_type === "transfer_summary" && event.value) {
7640
+ const summary = typeof event.value.summary === "string" ? event.value.summary.trim() : "";
7641
+ if (!summary) continue;
7642
+ const rawSentiment = event.value.sentiment;
7643
+ const sentimentLabel = rawSentiment === undefined || rawSentiment === null ? null : String(rawSentiment);
7644
+ const transferSummary = {
7645
+ summary,
7646
+ sentiment: sentimentLabel
7647
+ };
7648
+ this.emit("transferSummary", transferSummary);
7649
+ if (sentimentLabel) {
7650
+ const sentiment = {
7651
+ label: sentimentLabel.toLowerCase(),
7652
+ timestamp: now
7653
+ };
7654
+ this.emit("sentiment", sentiment);
7655
+ }
7656
+ }
7621
7657
  }
7622
7658
  }
7623
7659
  _handleStreamStatus(state) {
@@ -7646,6 +7682,7 @@ function useExotelAIAssist(params) {
7646
7682
  const [suggestions, setSuggestions] = React.useState([]);
7647
7683
  const [transcripts, setTranscripts] = React.useState([]);
7648
7684
  const [sentiment, setSentiment] = React.useState(null);
7685
+ const [transferSummary, setTransferSummary] = React.useState(null);
7649
7686
  const [botConfig, setBotConfig] = React.useState(null);
7650
7687
  const [lastError, setLastError] = React.useState(null);
7651
7688
  const paramHash = Utils.hash(params);
@@ -7655,6 +7692,7 @@ function useExotelAIAssist(params) {
7655
7692
  setSuggestions([]);
7656
7693
  setTranscripts([]);
7657
7694
  setSentiment(null);
7695
+ setTransferSummary(null);
7658
7696
  setBotConfig(null);
7659
7697
  setLastError(null);
7660
7698
  const ctrl = new ExotelAIAssistController(params);
@@ -7665,6 +7703,7 @@ function useExotelAIAssist(params) {
7665
7703
  setSuggestions([]);
7666
7704
  setTranscripts([]);
7667
7705
  setSentiment(null);
7706
+ setTransferSummary(null);
7668
7707
  }
7669
7708
  });
7670
7709
  ctrl.on("streamState", setStreamState);
@@ -7674,6 +7713,7 @@ function useExotelAIAssist(params) {
7674
7713
  return [...prev, s].slice(-50);
7675
7714
  }));
7676
7715
  ctrl.on("sentiment", setSentiment);
7716
+ ctrl.on("transferSummary", setTransferSummary);
7677
7717
  ctrl.on("transcript", lines => setTranscripts(prev => {
7678
7718
  const map = new Map(prev.map(l => [l.id, l]));
7679
7719
  for (const l of lines) map.set(l.id, l);
@@ -7726,6 +7766,7 @@ function useExotelAIAssist(params) {
7726
7766
  suggestions,
7727
7767
  transcripts,
7728
7768
  sentiment,
7769
+ transferSummary,
7729
7770
  botConfig,
7730
7771
  lastError,
7731
7772
  connect,
@@ -7760,7 +7801,7 @@ function styleInject(css, ref) {
7760
7801
  }
7761
7802
  }
7762
7803
 
7763
- var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{flex:1;min-height:0;overflow:hidden}.oa-tabs-content[data-state=inactive]{display:none!important}.oa-tabs-content[data-state=active]{display:flex;flex-direction:column}.oa-tabs-list{border-bottom:1px solid #e5e7eb;display:flex;gap:20px;padding:0}.oa-tabs-trigger{align-items:center;background:transparent;border:none;border-radius:0;color:#6b7280;cursor:pointer;display:inline-flex;font-size:15px;font-weight:500;gap:6px;outline:none;padding:10px 0;position:relative}.oa-tabs-trigger:hover{color:#374151}.oa-tabs-trigger[data-state=active]{color:#2563eb}.oa-tabs-trigger[data-state=active]:after{background:#2563eb;border-radius:2px;bottom:-1px;content:\"\";height:2px;left:0;position:absolute;right:0}.oa-suggestion-card{border:1px solid rgba(17,24,39,.04);border-radius:12px;padding:12px;text-align:left}.oa-suggestion-card--recent-wrapper{animation:oa-suggestion-enter .4s ease-out,oa-border-shimmer 3s ease-in-out .4s infinite;background:linear-gradient(90deg,#7c3aed,#e11d48,#7c3aed);background-size:200% 100%;border-radius:12px;padding:2px;text-align:left}.oa-suggestion-card--recent-wrapper .oa-suggestion-card--recent{border:none}.oa-suggestion-card--recent{animation:none;background:linear-gradient(90deg,#f5f3ff 0,#fef3f2 50%,#fff7ed);border-radius:9px}.oa-suggestion-card--older{background:#f5f5f5;border:1px solid #e5e7eb}.oa-suggestion-card--older .oa-suggestion-text{color:#252525}@keyframes oa-suggestion-enter{0%{opacity:.3;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes oa-border-shimmer{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.oa-suggestion-text{color:#111827;font-size:13px;line-height:18px;white-space:pre-wrap}.oa-copy-icon,.oa-feedback-icon{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#6b7280;cursor:pointer;display:inline-flex;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}.oa-copy-icon:hover,.oa-feedback-icon:hover{background:#f9fafb;color:#374151}.oa-feedback-icon--positive{background:#dcfce7!important;border-color:#86efac!important;color:#16a34a!important}.oa-feedback-icon--negative{background:#fee2e2!important;border-color:#fca5a5!important;color:#dc2626!important}.oa-feedback-icon:disabled{cursor:default;opacity:.5}.oa-feedback-icon--negative:disabled,.oa-feedback-icon--positive:disabled{opacity:1}.oa-feedback-reason-btn{align-items:center;background:#fef2f2;border:1px solid #fecaca;border-radius:16px;color:#991b1b;cursor:pointer;display:inline-flex;font-family:inherit;font-size:12px;font-weight:500;line-height:18px;padding:4px 12px;transition:background .15s,border-color .15s}.oa-feedback-reason-btn:hover:not(:disabled){background:#fee2e2;border-color:#f87171}.oa-feedback-reason-btn--selected{background:#dc2626!important;border-color:#dc2626!important;color:#fff!important}.oa-feedback-reason-btn:disabled{cursor:default;opacity:.45}@keyframes oa-toast-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.oa-stream-overlay{display:flex;flex:1;flex-direction:column;min-height:0}.oa-stream-banner{align-items:flex-start;background:#fef3c7;border:1px solid #fde68a;border-radius:8px;color:#92400e;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-stream-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-stream-banner-text{flex:1}.oa-stream-empty{flex:1;gap:16px;justify-content:center;min-height:0}.oa-stream-empty,.oa-stream-empty-text{align-items:center;display:flex;flex-direction:column}.oa-stream-empty-text{gap:6px;text-align:center}.oa-stream-empty-title{color:#111827;font-size:16px;font-weight:700;line-height:20px}.oa-stream-empty-subtitle{color:#6b7280;font-size:14px;line-height:20px}.oa-throttle-banner{align-items:flex-start;background:#fcefee;border:1px solid #e8cecb;border-radius:8px;color:#833d09;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-throttle-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-throttle-banner-text{flex:1}.oa-starting-loader{align-items:center;display:flex;flex:1;flex-direction:column;gap:16px;height:100%;justify-content:center;min-height:0}.oa-starting-spinner{animation:oa-spinner-rotate 1.4s linear infinite;height:48px;width:48px}.oa-starting-spinner-track{stroke:#e5e7eb}.oa-starting-spinner-arc{stroke:#2563eb;stroke-dasharray:80,126;stroke-dashoffset:0;animation:oa-spinner-dash 1.4s ease-in-out infinite}@keyframes oa-spinner-rotate{to{transform:rotate(1turn)}}@keyframes oa-spinner-dash{0%{stroke-dasharray:1,126;stroke-dashoffset:0}50%{stroke-dasharray:80,126;stroke-dashoffset:-35}to{stroke-dasharray:80,126;stroke-dashoffset:-124}}.oa-starting-loader-text{color:#111827;font-size:16px;font-weight:600;line-height:20px}.oa-muted{color:#6b7280}";
7804
+ var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{flex:1;min-height:0;overflow:hidden}.oa-tabs-content[data-state=inactive]{display:none!important}.oa-tabs-content[data-state=active]{display:flex;flex-direction:column}.oa-tabs-list{border-bottom:1px solid #e5e7eb;display:flex;gap:20px;padding:0}.oa-tabs-trigger{align-items:center;background:transparent;border:none;border-radius:0;color:#6b7280;cursor:pointer;display:inline-flex;font-size:15px;font-weight:500;gap:6px;outline:none;padding:10px 0;position:relative}.oa-tabs-trigger:hover{color:#374151}.oa-tabs-trigger[data-state=active]{color:#2563eb}.oa-tabs-trigger[data-state=active]:after{background:#2563eb;border-radius:2px;bottom:-1px;content:\"\";height:2px;left:0;position:absolute;right:0}.oa-suggestion-card{border:1px solid rgba(17,24,39,.04);border-radius:12px;padding:12px;text-align:left}.oa-suggestion-card--recent-wrapper{animation:oa-suggestion-enter .4s ease-out,oa-border-shimmer 3s ease-in-out .4s infinite;background:linear-gradient(90deg,#7c3aed,#e11d48,#7c3aed);background-size:200% 100%;border-radius:12px;padding:2px;text-align:left}.oa-suggestion-card--recent-wrapper .oa-suggestion-card--recent{border:none}.oa-suggestion-card--recent{animation:none;background:linear-gradient(90deg,#f5f3ff 0,#fef3f2 50%,#fff7ed);border-radius:9px}.oa-suggestion-card--older{background:#f5f5f5;border:1px solid #e5e7eb}.oa-suggestion-card--older .oa-suggestion-text{color:#252525}@keyframes oa-suggestion-enter{0%{opacity:.3;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes oa-border-shimmer{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.oa-suggestion-text{color:#111827;font-size:13px;line-height:18px;white-space:pre-wrap}.oa-copy-icon,.oa-feedback-icon{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#6b7280;cursor:pointer;display:inline-flex;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}.oa-copy-icon:hover,.oa-feedback-icon:hover{background:#f9fafb;color:#374151}.oa-feedback-icon--positive{background:#dcfce7!important;border-color:#86efac!important;color:#16a34a!important}.oa-feedback-icon--negative{background:#fee2e2!important;border-color:#fca5a5!important;color:#dc2626!important}.oa-feedback-icon:disabled{cursor:default;opacity:.5}.oa-feedback-icon--negative:disabled,.oa-feedback-icon--positive:disabled{opacity:1}.oa-feedback-reason-btn{align-items:center;background:#fef2f2;border:1px solid #fecaca;border-radius:16px;color:#991b1b;cursor:pointer;display:inline-flex;font-family:inherit;font-size:12px;font-weight:500;line-height:18px;padding:4px 12px;transition:background .15s,border-color .15s}.oa-feedback-reason-btn:hover:not(:disabled){background:#fee2e2;border-color:#f87171}.oa-feedback-reason-btn--selected{background:#dc2626!important;border-color:#dc2626!important;color:#fff!important}.oa-feedback-reason-btn:disabled{cursor:default;opacity:.45}@keyframes oa-toast-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.oa-stream-overlay{display:flex;flex:1;flex-direction:column;min-height:0}.oa-stream-banner{align-items:flex-start;background:#fef3c7;border:1px solid #fde68a;border-radius:8px;color:#92400e;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-stream-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-stream-banner-text{flex:1}.oa-stream-empty{flex:1;gap:16px;justify-content:center;min-height:0}.oa-stream-empty,.oa-stream-empty-text{align-items:center;display:flex;flex-direction:column}.oa-stream-empty-text{gap:6px;text-align:center}.oa-stream-empty-title{color:#111827;font-size:16px;font-weight:700;line-height:20px}.oa-stream-empty-subtitle{color:#6b7280;font-size:14px;line-height:20px}.oa-throttle-banner{align-items:flex-start;background:#fcefee;border:1px solid #e8cecb;border-radius:8px;color:#833d09;display:flex;font-size:13px;gap:8px;line-height:1.4;padding:10px 12px}.oa-throttle-banner-icon{align-items:center;display:inline-flex;flex-shrink:0;margin-top:1px}.oa-throttle-banner-text{flex:1}.oa-starting-loader{align-items:center;display:flex;flex:1;flex-direction:column;gap:16px;height:100%;justify-content:center;min-height:0}.oa-starting-spinner{animation:oa-spinner-rotate 1.4s linear infinite;height:48px;width:48px}.oa-starting-spinner-track{stroke:#e5e7eb}.oa-starting-spinner-arc{stroke:#2563eb;stroke-dasharray:80,126;stroke-dashoffset:0;animation:oa-spinner-dash 1.4s ease-in-out infinite}@keyframes oa-spinner-rotate{to{transform:rotate(1turn)}}@keyframes oa-spinner-dash{0%{stroke-dasharray:1,126;stroke-dashoffset:0}50%{stroke-dasharray:80,126;stroke-dashoffset:-35}to{stroke-dasharray:80,126;stroke-dashoffset:-124}}.oa-starting-loader-text{color:#111827;font-size:16px;font-weight:600;line-height:20px}.oa-transfer-summary-card{background:#f9f7ff;border:1px solid #ece7f8;border-radius:12px;padding:12px 14px}.oa-transfer-summary-header{user-select:none}.oa-transfer-summary-body,.oa-transfer-summary-text{text-align:left}.oa-transfer-summary-badge{align-items:center;border-radius:999px;display:inline-flex;font-size:13px;font-weight:600;padding:2px 10px}.oa-transfer-summary-badge--positive{background:#dcfce7;color:#166534}.oa-transfer-summary-badge--neutral{background:#f3f4f6;color:#374151}.oa-transfer-summary-badge--negative{background:#ffe0db;color:#9f1239}.oa-muted{color:#6b7280}";
7764
7805
  styleInject(css_248z);
7765
7806
 
7766
7807
  function Sparkle() {
@@ -8374,6 +8415,113 @@ function TranscriptTab({
8374
8415
  });
8375
8416
  }
8376
8417
 
8418
+ /** Renders `**bold**` markdown spans; leaves the rest as plain text. */
8419
+ function renderSummaryWithBold(text) {
8420
+ const parts = text.split(/(\*\*[^*]+\*\*)/g);
8421
+ return parts.map((part, i) => {
8422
+ if (part.startsWith("**") && part.endsWith("**") && part.length > 4) {
8423
+ return jsxRuntime.jsx("strong", {
8424
+ children: part.slice(2, -2)
8425
+ }, i);
8426
+ }
8427
+ return jsxRuntime.jsx(React__namespace.default.Fragment, {
8428
+ children: part
8429
+ }, i);
8430
+ });
8431
+ }
8432
+ /** Normalizes inline bullet markers into separate left-aligned lines with bold support. */
8433
+ function formatSummaryLines(summary) {
8434
+ const normalizedSummary = summary.replace(/\s+([*-•])\s+/g, "\n$1 ");
8435
+ const lines = normalizedSummary.split("\n").map(line => line.trim()).filter(Boolean);
8436
+ return lines.map((line, index) => {
8437
+ const isBulletLine = /^[-*•]\s+/.test(line);
8438
+ const lineContent = isBulletLine ? line.replace(/^[-*•]\s+/, "") : line;
8439
+ return jsxRuntime.jsxs("span", {
8440
+ style: {
8441
+ display: "block"
8442
+ },
8443
+ children: [isBulletLine ? "• " : "", renderSummaryWithBold(lineContent)]
8444
+ }, `line-${index}`);
8445
+ });
8446
+ }
8447
+ function TransferSummaryCard({
8448
+ data
8449
+ }) {
8450
+ var _a, _b;
8451
+ const [expanded, setExpanded] = React.useState(true);
8452
+ const sentiment = (_a = data.sentiment) !== null && _a !== void 0 ? _a : "";
8453
+ const lower = sentiment.toLowerCase();
8454
+ const icon = lower.includes("positive") ? jsxRuntime.jsx(Smile, {
8455
+ size: 16
8456
+ }) : lower.includes("neutral") ? jsxRuntime.jsx(Meh, {
8457
+ size: 16
8458
+ }) : lower.includes("negative") ? jsxRuntime.jsx(Frown, {
8459
+ size: 16
8460
+ }) : null;
8461
+ const badgeClass = lower.includes("positive") ? "oa-transfer-summary-badge oa-transfer-summary-badge--positive" : lower.includes("negative") ? "oa-transfer-summary-badge oa-transfer-summary-badge--negative" : "oa-transfer-summary-badge oa-transfer-summary-badge--neutral";
8462
+ return jsxRuntime.jsxs("div", {
8463
+ className: "oa-transfer-summary-card",
8464
+ style: {
8465
+ marginBottom: 12
8466
+ },
8467
+ children: [jsxRuntime.jsxs("div", {
8468
+ className: "oa-transfer-summary-header",
8469
+ onClick: () => setExpanded(v => !v),
8470
+ style: {
8471
+ display: "flex",
8472
+ alignItems: "center",
8473
+ justifyContent: "space-between",
8474
+ cursor: "pointer"
8475
+ },
8476
+ children: [jsxRuntime.jsxs("div", {
8477
+ style: {
8478
+ display: "flex",
8479
+ alignItems: "center",
8480
+ gap: 8
8481
+ },
8482
+ children: [jsxRuntime.jsx("span", {
8483
+ style: {
8484
+ fontWeight: 700,
8485
+ fontSize: 16,
8486
+ color: "#111827"
8487
+ },
8488
+ children: "Transfer Summary"
8489
+ }), sentiment ? jsxRuntime.jsx("span", {
8490
+ className: badgeClass,
8491
+ children: jsxRuntime.jsxs("span", {
8492
+ style: {
8493
+ display: "inline-flex",
8494
+ alignItems: "center",
8495
+ gap: 4
8496
+ },
8497
+ children: [icon, sentiment]
8498
+ })
8499
+ }) : null]
8500
+ }), expanded ? jsxRuntime.jsx(ChevronUp, {
8501
+ size: 16
8502
+ }) : jsxRuntime.jsx(ChevronDown, {
8503
+ size: 16
8504
+ })]
8505
+ }), expanded ? jsxRuntime.jsx("div", {
8506
+ className: "oa-transfer-summary-body",
8507
+ style: {
8508
+ marginTop: 8
8509
+ },
8510
+ children: jsxRuntime.jsx("div", {
8511
+ className: "oa-transfer-summary-text",
8512
+ style: {
8513
+ color: "#374151",
8514
+ margin: 0,
8515
+ lineHeight: 1.5,
8516
+ fontSize: 14,
8517
+ textAlign: "left"
8518
+ },
8519
+ children: formatSummaryLines((_b = data.summary) !== null && _b !== void 0 ? _b : "")
8520
+ })
8521
+ }) : null]
8522
+ });
8523
+ }
8524
+
8377
8525
  function ExotelAIAssist(_a) {
8378
8526
  var {
8379
8527
  className,
@@ -8386,6 +8534,7 @@ function ExotelAIAssist(_a) {
8386
8534
  suggestions,
8387
8535
  transcripts,
8388
8536
  sentiment,
8537
+ transferSummary,
8389
8538
  botConfig,
8390
8539
  sendSuggestionFeedback
8391
8540
  } = useExotelAIAssist(params);
@@ -8431,7 +8580,9 @@ function ExotelAIAssist(_a) {
8431
8580
  children: [jsxRuntime.jsx(Header, {
8432
8581
  sentiment: sentiment,
8433
8582
  botConfig: botConfig
8434
- }), streamState !== "throttled" ? jsxRuntime.jsxs(Root2$1, {
8583
+ }), transferSummary ? jsxRuntime.jsx(TransferSummaryCard, {
8584
+ data: transferSummary
8585
+ }) : null, streamState !== "throttled" ? jsxRuntime.jsxs(Root2$1, {
8435
8586
  defaultValue: "suggestions",
8436
8587
  className: "oa-tabs",
8437
8588
  style: {
@@ -8516,5 +8667,6 @@ exports.ExotelAIAssist = ExotelAIAssist;
8516
8667
  exports.ExotelAIAssistApp = ExotelAIAssist;
8517
8668
  exports.ExotelAIAssistController = ExotelAIAssistController;
8518
8669
  exports.ExotelAIAssistProvider = ExotelAIAssistProvider;
8670
+ exports.TransferSummaryCard = TransferSummaryCard;
8519
8671
  exports.useExotelAIAssist = useExotelAIAssist;
8520
8672
  exports.useExotelAIAssistContext = useExotelAIAssistContext;
@@ -88,7 +88,21 @@ interface WssSuggestionEvent {
88
88
  event_type: "suggestion";
89
89
  value: SuggestionValue;
90
90
  }
91
- export type WssEvent = WssTranscriptEvent | WssSentimentEvent | WssSuggestionEvent;
91
+ /** Value payload for handover / warm-transfer summary events. */
92
+ export interface TransferSummaryValue {
93
+ summary?: string;
94
+ sentiment?: string | number | null;
95
+ }
96
+ /** Parsed transfer summary shown above the panel tabs. */
97
+ export interface TransferSummary {
98
+ summary: string;
99
+ sentiment: string | null;
100
+ }
101
+ interface WssTransferSummaryEvent {
102
+ event_type: "transfer_summary";
103
+ value: TransferSummaryValue;
104
+ }
105
+ export type WssEvent = WssTranscriptEvent | WssSentimentEvent | WssSuggestionEvent | WssTransferSummaryEvent;
92
106
  export interface InitialHandshakeResponse {
93
107
  type: string;
94
108
  config: BotConfig;
@@ -104,6 +118,8 @@ export interface ControllerEvents {
104
118
  suggestion: (data: Suggestion) => void;
105
119
  transcript: (lines: TranscriptLine[]) => void;
106
120
  sentiment: (data: Sentiment) => void;
121
+ /** Warm-transfer / handover summary pushed at stream-setup. */
122
+ transferSummary: (data: TransferSummary) => void;
107
123
  /** Internal: bot feature-flag config. Consumed by the UI component only. */
108
124
  botConfig: (config: BotConfig) => void;
109
125
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exotel-npm-dev/exotel-ai-assist",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "AI-powered real-time suggestions, transcript, and sentiment for agent calls",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,7 @@ import "../styles/index.css";
10
10
  import { Header } from "./key-components/Header";
11
11
  import { SuggestionsTab } from "./key-components/SuggestionTab";
12
12
  import { TranscriptTab } from "./key-components/TranscriptTab";
13
+ import { TransferSummaryCard } from "./key-components/TransferSummaryCard";
13
14
  import { EmptyState } from "./EmptyState";
14
15
 
15
16
  export interface ExotelAIAssistProps extends ExotelAIAssistParams {
@@ -19,7 +20,16 @@ export interface ExotelAIAssistProps extends ExotelAIAssistParams {
19
20
  }
20
21
 
21
22
  export function ExotelAIAssist({ className, onReady, ...params }: ExotelAIAssistProps): JSX.Element {
22
- const { streamState, isReady, suggestions, transcripts, sentiment, botConfig, sendSuggestionFeedback } = useExotelAIAssist(params as ExotelAIAssistParams);
23
+ const {
24
+ streamState,
25
+ isReady,
26
+ suggestions,
27
+ transcripts,
28
+ sentiment,
29
+ transferSummary,
30
+ botConfig,
31
+ sendSuggestionFeedback,
32
+ } = useExotelAIAssist(params as ExotelAIAssistParams);
23
33
 
24
34
  const onReadyRef = useRef(onReady);
25
35
  onReadyRef.current = onReady;
@@ -54,6 +64,7 @@ export function ExotelAIAssist({ className, onReady, ...params }: ExotelAIAssist
54
64
  <div className={`oa-panel${className ? ` ${className}` : ""}`}>
55
65
  <div style={{ flex: 1, width: "100%", minHeight: 0, padding: "0 16px", display: "flex", flexDirection: "column" }}>
56
66
  <Header sentiment={sentiment} botConfig={botConfig} />
67
+ {transferSummary ? <TransferSummaryCard data={transferSummary} /> : null}
57
68
 
58
69
  {streamState !== "throttled" ? (
59
70
  <Tabs.Root defaultValue="suggestions" className="oa-tabs" style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
@@ -0,0 +1,105 @@
1
+ import React, { useState } from "react";
2
+ import { ChevronUp, ChevronDown, Smile, Meh, Frown } from "lucide-react";
3
+ import { TransferSummary } from "../../types";
4
+
5
+ export interface TransferSummaryCardProps {
6
+ data: TransferSummary;
7
+ }
8
+
9
+ /** Renders `**bold**` markdown spans; leaves the rest as plain text. */
10
+ function renderSummaryWithBold(text: string): React.ReactNode[] {
11
+ const parts = text.split(/(\*\*[^*]+\*\*)/g);
12
+ return parts.map((part, i) => {
13
+ if (part.startsWith("**") && part.endsWith("**") && part.length > 4) {
14
+ return <strong key={i}>{part.slice(2, -2)}</strong>;
15
+ }
16
+ return <React.Fragment key={i}>{part}</React.Fragment>;
17
+ });
18
+ }
19
+
20
+ /** Normalizes inline bullet markers into separate left-aligned lines with bold support. */
21
+ function formatSummaryLines(summary: string): React.ReactNode[] {
22
+ const normalizedSummary = summary.replace(/\s+([*-•])\s+/g, "\n$1 ");
23
+ const lines = normalizedSummary
24
+ .split("\n")
25
+ .map((line) => line.trim())
26
+ .filter(Boolean);
27
+
28
+ return lines.map((line, index) => {
29
+ const isBulletLine = /^[-*•]\s+/.test(line);
30
+ const lineContent = isBulletLine ? line.replace(/^[-*•]\s+/, "") : line;
31
+
32
+ return (
33
+ <span key={`line-${index}`} style={{ display: "block" }}>
34
+ {isBulletLine ? "• " : ""}
35
+ {renderSummaryWithBold(lineContent)}
36
+ </span>
37
+ );
38
+ });
39
+ }
40
+
41
+ export function TransferSummaryCard({ data }: TransferSummaryCardProps): JSX.Element {
42
+ const [expanded, setExpanded] = useState(true);
43
+ const sentiment = data.sentiment ?? "";
44
+ const lower = sentiment.toLowerCase();
45
+
46
+ const icon = lower.includes("positive") ? (
47
+ <Smile size={16} />
48
+ ) : lower.includes("neutral") ? (
49
+ <Meh size={16} />
50
+ ) : lower.includes("negative") ? (
51
+ <Frown size={16} />
52
+ ) : null;
53
+
54
+ const badgeClass = lower.includes("positive")
55
+ ? "oa-transfer-summary-badge oa-transfer-summary-badge--positive"
56
+ : lower.includes("negative")
57
+ ? "oa-transfer-summary-badge oa-transfer-summary-badge--negative"
58
+ : "oa-transfer-summary-badge oa-transfer-summary-badge--neutral";
59
+
60
+ return (
61
+ <div className="oa-transfer-summary-card" style={{ marginBottom: 12 }}>
62
+ <div
63
+ className="oa-transfer-summary-header"
64
+ onClick={() => setExpanded((v) => !v)}
65
+ style={{
66
+ display: "flex",
67
+ alignItems: "center",
68
+ justifyContent: "space-between",
69
+ cursor: "pointer",
70
+ }}
71
+ >
72
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
73
+ <span style={{ fontWeight: 700, fontSize: 16, color: "#111827" }}>
74
+ Transfer Summary
75
+ </span>
76
+ {sentiment ? (
77
+ <span className={badgeClass}>
78
+ <span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
79
+ {icon}
80
+ {sentiment}
81
+ </span>
82
+ </span>
83
+ ) : null}
84
+ </div>
85
+ {expanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
86
+ </div>
87
+ {expanded ? (
88
+ <div className="oa-transfer-summary-body" style={{ marginTop: 8 }}>
89
+ <div
90
+ className="oa-transfer-summary-text"
91
+ style={{
92
+ color: "#374151",
93
+ margin: 0,
94
+ lineHeight: 1.5,
95
+ fontSize: 14,
96
+ textAlign: "left",
97
+ }}
98
+ >
99
+ {formatSummaryLines(data.summary ?? "")}
100
+ </div>
101
+ </div>
102
+ ) : null}
103
+ </div>
104
+ );
105
+ }
@@ -6,6 +6,7 @@ import {
6
6
  Suggestion,
7
7
  TranscriptLine,
8
8
  Sentiment,
9
+ TransferSummary,
9
10
  WssEvent,
10
11
  InitialHandshakeResponse,
11
12
  WssResponse,
@@ -292,6 +293,30 @@ export class ExotelAIAssistController extends EventEmitter<ControllerEvents> {
292
293
  };
293
294
  this.emit("sentiment", sentiment);
294
295
  }
296
+
297
+ if (event.event_type === "transfer_summary" && event.value) {
298
+ const summary =
299
+ typeof event.value.summary === "string" ? event.value.summary.trim() : "";
300
+ if (!summary) continue;
301
+
302
+ const rawSentiment = event.value.sentiment;
303
+ const sentimentLabel =
304
+ rawSentiment === undefined || rawSentiment === null ? null : String(rawSentiment);
305
+
306
+ const transferSummary: TransferSummary = {
307
+ summary,
308
+ sentiment: sentimentLabel,
309
+ };
310
+ this.emit("transferSummary", transferSummary);
311
+
312
+ if (sentimentLabel) {
313
+ const sentiment: Sentiment = {
314
+ label: sentimentLabel.toLowerCase() as Sentiment["label"],
315
+ timestamp: now,
316
+ };
317
+ this.emit("sentiment", sentiment);
318
+ }
319
+ }
295
320
  }
296
321
  }
297
322
 
@@ -1,6 +1,14 @@
1
1
  import { useEffect, useRef, useState, useCallback } from "react";
2
2
  import { ExotelAIAssistController } from "../controller";
3
- import { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, BotConfig, StreamState } from "../types";
3
+ import {
4
+ ExotelAIAssistParams,
5
+ Suggestion,
6
+ TranscriptLine,
7
+ Sentiment,
8
+ TransferSummary,
9
+ BotConfig,
10
+ StreamState,
11
+ } from "../types";
4
12
  import { Utils } from "../utils";
5
13
 
6
14
  const MAX_SUGGESTIONS = 50;
@@ -27,6 +35,8 @@ export interface UseExotelAIAssistReturn {
27
35
  transcripts: TranscriptLine[];
28
36
  /** Latest sentiment reading, or null before the first event. */
29
37
  sentiment: Sentiment | null;
38
+ /** Warm-transfer summary from handover-context cache, if present. */
39
+ transferSummary: TransferSummary | null;
30
40
  lastError: Error | null;
31
41
  connect: () => void;
32
42
  disconnect: () => void;
@@ -47,6 +57,7 @@ export function useExotelAIAssist(params: ExotelAIAssistParams): UseExotelAIAssi
47
57
  const [suggestions, setSuggestions] = useState<Suggestion[]>([]);
48
58
  const [transcripts, setTranscripts] = useState<TranscriptLine[]>([]);
49
59
  const [sentiment, setSentiment] = useState<Sentiment | null>(null);
60
+ const [transferSummary, setTransferSummary] = useState<TransferSummary | null>(null);
50
61
  const [botConfig, setBotConfig] = useState<BotConfig | null>(null);
51
62
  const [lastError, setLastError] = useState<Error | null>(null);
52
63
  const paramHash = Utils.hash(params);
@@ -57,6 +68,7 @@ export function useExotelAIAssist(params: ExotelAIAssistParams): UseExotelAIAssi
57
68
  setSuggestions([]);
58
69
  setTranscripts([]);
59
70
  setSentiment(null);
71
+ setTransferSummary(null);
60
72
  setBotConfig(null);
61
73
  setLastError(null);
62
74
 
@@ -69,6 +81,7 @@ export function useExotelAIAssist(params: ExotelAIAssistParams): UseExotelAIAssi
69
81
  setSuggestions([]);
70
82
  setTranscripts([]);
71
83
  setSentiment(null);
84
+ setTransferSummary(null);
72
85
  }
73
86
  });
74
87
  ctrl.on("streamState", setStreamState);
@@ -80,6 +93,7 @@ export function useExotelAIAssist(params: ExotelAIAssistParams): UseExotelAIAssi
80
93
  }),
81
94
  );
82
95
  ctrl.on("sentiment", setSentiment);
96
+ ctrl.on("transferSummary", setTransferSummary);
83
97
  ctrl.on("transcript", (lines) =>
84
98
  setTranscripts((prev) => {
85
99
  const map = new Map(prev.map((l) => [l.id, l]));
@@ -115,5 +129,18 @@ export function useExotelAIAssist(params: ExotelAIAssistParams): UseExotelAIAssi
115
129
  return sent;
116
130
  }, []);
117
131
 
118
- return { streamState, isReady, suggestions, transcripts, sentiment, botConfig, lastError, connect, disconnect, setParams, sendSuggestionFeedback };
132
+ return {
133
+ streamState,
134
+ isReady,
135
+ suggestions,
136
+ transcripts,
137
+ sentiment,
138
+ transferSummary,
139
+ botConfig,
140
+ lastError,
141
+ connect,
142
+ disconnect,
143
+ setParams,
144
+ sendSuggestionFeedback,
145
+ };
119
146
  }
package/src/index.ts CHANGED
@@ -12,4 +12,13 @@ export function unmountExotelAIAssist(container: HTMLElement): void {
12
12
  }
13
13
 
14
14
  export { ExotelAIAssistController } from "./controller";
15
- export type { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, ConnectionStatus, StreamState } from "./types";
15
+ export type {
16
+ ExotelAIAssistParams,
17
+ Suggestion,
18
+ TranscriptLine,
19
+ Sentiment,
20
+ TransferSummary,
21
+ TransferSummaryValue,
22
+ ConnectionStatus,
23
+ StreamState,
24
+ } from "./types";
@@ -3,7 +3,18 @@ export { ExotelAIAssistProvider, useExotelAIAssistContext } from "../components/
3
3
  export { useExotelAIAssist } from "../hooks/useExotelAIAssist";
4
4
  export { ExotelAIAssistController } from "../controller";
5
5
 
6
- export type { ExotelAIAssistParams, Suggestion, TranscriptLine, Sentiment, ConnectionStatus, StreamState } from "../types";
6
+ export type {
7
+ ExotelAIAssistParams,
8
+ Suggestion,
9
+ TranscriptLine,
10
+ Sentiment,
11
+ TransferSummary,
12
+ TransferSummaryValue,
13
+ ConnectionStatus,
14
+ StreamState,
15
+ } from "../types";
7
16
  export type { UseExotelAIAssistReturn } from "../hooks/useExotelAIAssist";
8
17
  export type { ExotelAIAssistProps } from "../components/ExotelAIAssistApp";
9
18
  export type { ExotelAIAssistProviderProps } from "../components/ExotelAIAssistProvider";
19
+ export { TransferSummaryCard } from "../components/key-components/TransferSummaryCard";
20
+ export type { TransferSummaryCardProps } from "../components/key-components/TransferSummaryCard";
@@ -425,6 +425,47 @@
425
425
  font-weight: 600;
426
426
  }
427
427
 
428
+ /* AIA-144 transfer summary card */
429
+ .oa-transfer-summary-card {
430
+ background: #f9f7ff;
431
+ border: 1px solid #ece7f8;
432
+ border-radius: 12px;
433
+ padding: 12px 14px;
434
+ }
435
+
436
+ .oa-transfer-summary-header {
437
+ user-select: none;
438
+ }
439
+
440
+ .oa-transfer-summary-body,
441
+ .oa-transfer-summary-text {
442
+ text-align: left;
443
+ }
444
+
445
+ .oa-transfer-summary-badge {
446
+ display: inline-flex;
447
+ align-items: center;
448
+ border-radius: 999px;
449
+ padding: 2px 10px;
450
+ font-size: 13px;
451
+ font-weight: 600;
452
+ }
453
+
454
+ .oa-transfer-summary-badge--positive {
455
+ background: #dcfce7;
456
+ color: #166534;
457
+ }
458
+
459
+ .oa-transfer-summary-badge--neutral {
460
+ background: #f3f4f6;
461
+ color: #374151;
462
+ }
463
+
464
+ .oa-transfer-summary-badge--negative {
465
+ background: #ffe0db;
466
+ color: #9f1239;
467
+ }
468
+
428
469
  /* -----------------------------------------------------------------------
429
470
  Misc helpers
430
471
  ----------------------------------------------------------------------- */
@@ -105,7 +105,24 @@ interface WssSuggestionEvent {
105
105
  value: SuggestionValue;
106
106
  }
107
107
 
108
- export type WssEvent = WssTranscriptEvent | WssSentimentEvent | WssSuggestionEvent;
108
+ /** Value payload for handover / warm-transfer summary events. */
109
+ export interface TransferSummaryValue {
110
+ summary?: string;
111
+ sentiment?: string | number | null;
112
+ }
113
+
114
+ /** Parsed transfer summary shown above the panel tabs. */
115
+ export interface TransferSummary {
116
+ summary: string;
117
+ sentiment: string | null;
118
+ }
119
+
120
+ interface WssTransferSummaryEvent {
121
+ event_type: "transfer_summary";
122
+ value: TransferSummaryValue;
123
+ }
124
+
125
+ export type WssEvent = WssTranscriptEvent | WssSentimentEvent | WssSuggestionEvent | WssTransferSummaryEvent;
109
126
 
110
127
  export interface InitialHandshakeResponse {
111
128
  type: string;
@@ -124,6 +141,8 @@ export interface ControllerEvents {
124
141
  suggestion: (data: Suggestion) => void;
125
142
  transcript: (lines: TranscriptLine[]) => void;
126
143
  sentiment: (data: Sentiment) => void;
144
+ /** Warm-transfer / handover summary pushed at stream-setup. */
145
+ transferSummary: (data: TransferSummary) => void;
127
146
  /** Internal: bot feature-flag config. Consumed by the UI component only. */
128
147
  botConfig: (config: BotConfig) => void;
129
148
  /**