@flamingo-stack/openframe-frontend-core 0.0.537-snapshot.20260814101439 → 0.0.537
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/dist/chat-protocol/events.d.ts +1 -15
- package/dist/chat-protocol/events.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +0 -15
- package/dist/chat-protocol/index.cjs.map +1 -1
- package/dist/chat-protocol/index.js +0 -15
- package/dist/chat-protocol/index.js.map +1 -1
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
- package/dist/{chunk-FDKMA7H4.js → chunk-3GXMS4OP.js} +2 -2
- package/dist/{chunk-O56ABJ43.js → chunk-5JPSQOZQ.js} +2 -2
- package/dist/{chunk-YXKFS6XF.js → chunk-7EHGP4QK.js} +4094 -4250
- package/dist/chunk-7EHGP4QK.js.map +1 -0
- package/dist/{chunk-MXB347SP.js → chunk-7FXZHI5D.js} +2 -2
- package/dist/{chunk-LR7NVXH7.cjs → chunk-7WNJ7RFO.cjs} +14 -14
- package/dist/{chunk-LR7NVXH7.cjs.map → chunk-7WNJ7RFO.cjs.map} +1 -1
- package/dist/{chunk-5CA7WS2F.js → chunk-C4LEH3VE.js} +2 -2
- package/dist/{chunk-KAQG3TDR.cjs → chunk-CATY4DVR.cjs} +87 -87
- package/dist/{chunk-KAQG3TDR.cjs.map → chunk-CATY4DVR.cjs.map} +1 -1
- package/dist/{chunk-ODENAPJ4.js → chunk-CQQXB25K.js} +2 -2
- package/dist/{chunk-APQOHCJH.js → chunk-CZHCXCWL.js} +2 -2
- package/dist/{chunk-F3PJTTFA.cjs → chunk-DHSVBGOX.cjs} +9 -9
- package/dist/{chunk-F3PJTTFA.cjs.map → chunk-DHSVBGOX.cjs.map} +1 -1
- package/dist/{chunk-YMBPNIRC.js → chunk-HRC56BQW.js} +5 -5
- package/dist/{chunk-QODV5KPW.cjs → chunk-KKZ2QMQD.cjs} +26 -26
- package/dist/{chunk-QODV5KPW.cjs.map → chunk-KKZ2QMQD.cjs.map} +1 -1
- package/dist/{chunk-DQAEKYH2.js → chunk-LFMIAPBP.js} +2 -2
- package/dist/{chunk-NX3RTMFX.cjs → chunk-MDP3YKFI.cjs} +5 -5
- package/dist/{chunk-NX3RTMFX.cjs.map → chunk-MDP3YKFI.cjs.map} +1 -1
- package/dist/{chunk-M6Q6BD4I.js → chunk-MOIHWYSA.js} +2 -2
- package/dist/{chunk-3NXYQJBY.cjs → chunk-OYTM3YXO.cjs} +7 -7
- package/dist/{chunk-3NXYQJBY.cjs.map → chunk-OYTM3YXO.cjs.map} +1 -1
- package/dist/{chunk-WPXQ2UBV.cjs → chunk-QPPG3KHW.cjs} +3 -3
- package/dist/{chunk-WPXQ2UBV.cjs.map → chunk-QPPG3KHW.cjs.map} +1 -1
- package/dist/{chunk-OPFMSQ5Q.js → chunk-SGNWSUL3.js} +6 -6
- package/dist/{chunk-OTDF4VUJ.cjs → chunk-SKIM263K.cjs} +37 -37
- package/dist/{chunk-OTDF4VUJ.cjs.map → chunk-SKIM263K.cjs.map} +1 -1
- package/dist/{chunk-QY7FHRWF.js → chunk-TCSSYTDN.js} +4 -4
- package/dist/{chunk-PT34VDFM.cjs → chunk-V4D5MPB7.cjs} +4 -4
- package/dist/{chunk-PT34VDFM.cjs.map → chunk-V4D5MPB7.cjs.map} +1 -1
- package/dist/{chunk-UV5LYSUJ.cjs → chunk-VSIHO7DJ.cjs} +61 -61
- package/dist/{chunk-UV5LYSUJ.cjs.map → chunk-VSIHO7DJ.cjs.map} +1 -1
- package/dist/{chunk-SCW6UZ3F.js → chunk-WHUEUACV.js} +2 -2
- package/dist/{chunk-64GAFOBF.cjs → chunk-WJIBB3KT.cjs} +31 -31
- package/dist/{chunk-64GAFOBF.cjs.map → chunk-WJIBB3KT.cjs.map} +1 -1
- package/dist/{chunk-W3MCQKDV.cjs → chunk-YEKXTTAC.cjs} +26 -26
- package/dist/{chunk-W3MCQKDV.cjs.map → chunk-YEKXTTAC.cjs.map} +1 -1
- package/dist/{chunk-4OHZCP7L.js → chunk-YIDGB5KP.js} +4 -4
- package/dist/{chunk-FQ53MYZ4.cjs → chunk-YMPRMZDD.cjs} +37 -193
- package/dist/chunk-YMPRMZDD.cjs.map +1 -0
- package/dist/{chunk-K5RWDOAC.cjs → chunk-YPI3JIR6.cjs} +11 -11
- package/dist/{chunk-K5RWDOAC.cjs.map → chunk-YPI3JIR6.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -8
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +0 -2
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +1 -7
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
- package/dist/components/chat/ticket-escalated-message.d.ts +8 -2
- package/dist/components/chat/ticket-escalated-message.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +2 -51
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts +1 -13
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +132 -138
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -17
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +34 -34
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +2 -8
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +1 -7
- package/dist/index.cjs +2 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -7
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +0 -44
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -32
- package/src/chat-protocol/events.ts +0 -16
- package/src/chat-protocol/nats-decoder.ts +0 -27
- package/src/components/chat/chat-message-enhanced.tsx +0 -3
- package/src/components/chat/index.ts +0 -2
- package/src/components/chat/stream/chat-stream-reducer.ts +0 -19
- package/src/components/chat/stream/message-mutations.ts +0 -5
- package/src/components/chat/ticket-escalated-message.tsx +32 -10
- package/src/components/chat/types/message.types.ts +1 -60
- package/src/components/chat/utils/message-segment-accumulator.ts +0 -41
- package/src/components/chat/utils/process-historical-messages.ts +0 -31
- package/dist/chunk-FQ53MYZ4.cjs.map +0 -1
- package/dist/chunk-YXKFS6XF.js.map +0 -1
- package/dist/components/chat/ai-assistant-info.d.ts +0 -31
- package/dist/components/chat/ai-assistant-info.d.ts.map +0 -1
- package/dist/components/chat/ticket-event-message.d.ts +0 -24
- package/dist/components/chat/ticket-event-message.d.ts.map +0 -1
- package/src/components/chat/__tests__/ticket-event.test.ts +0 -194
- package/src/components/chat/ai-assistant-info.tsx +0 -71
- package/src/components/chat/ticket-event-message.tsx +0 -79
- package/src/stories/TicketEventMessage.stories.tsx +0 -100
- /package/dist/{chunk-FDKMA7H4.js.map → chunk-3GXMS4OP.js.map} +0 -0
- /package/dist/{chunk-O56ABJ43.js.map → chunk-5JPSQOZQ.js.map} +0 -0
- /package/dist/{chunk-MXB347SP.js.map → chunk-7FXZHI5D.js.map} +0 -0
- /package/dist/{chunk-5CA7WS2F.js.map → chunk-C4LEH3VE.js.map} +0 -0
- /package/dist/{chunk-ODENAPJ4.js.map → chunk-CQQXB25K.js.map} +0 -0
- /package/dist/{chunk-APQOHCJH.js.map → chunk-CZHCXCWL.js.map} +0 -0
- /package/dist/{chunk-YMBPNIRC.js.map → chunk-HRC56BQW.js.map} +0 -0
- /package/dist/{chunk-DQAEKYH2.js.map → chunk-LFMIAPBP.js.map} +0 -0
- /package/dist/{chunk-M6Q6BD4I.js.map → chunk-MOIHWYSA.js.map} +0 -0
- /package/dist/{chunk-OPFMSQ5Q.js.map → chunk-SGNWSUL3.js.map} +0 -0
- /package/dist/{chunk-QY7FHRWF.js.map → chunk-TCSSYTDN.js.map} +0 -0
- /package/dist/{chunk-SCW6UZ3F.js.map → chunk-WHUEUACV.js.map} +0 -0
- /package/dist/{chunk-4OHZCP7L.js.map → chunk-YIDGB5KP.js.map} +0 -0
package/package.json
CHANGED
|
@@ -406,50 +406,6 @@ exports[`decodeNatsChunk — golden corpus > maps the full recorded corpus to no
|
|
|
406
406
|
},
|
|
407
407
|
"ticket_escalated_missing_reason": null,
|
|
408
408
|
"ticket_escalated_missing_ticket_id": null,
|
|
409
|
-
"ticket_event_blank_reason_dropped": {
|
|
410
|
-
"actorId": undefined,
|
|
411
|
-
"actorName": undefined,
|
|
412
|
-
"actorType": undefined,
|
|
413
|
-
"kind": "RESOLVED",
|
|
414
|
-
"reason": undefined,
|
|
415
|
-
"type": "ticket-event",
|
|
416
|
-
},
|
|
417
|
-
"ticket_event_missing_kind": null,
|
|
418
|
-
"ticket_event_reopened_with_reason": {
|
|
419
|
-
"actorId": "user-42",
|
|
420
|
-
"actorName": "John Smith",
|
|
421
|
-
"actorType": "CLIENT",
|
|
422
|
-
"kind": "REOPENED",
|
|
423
|
-
"reason": "The printer stopped working again",
|
|
424
|
-
"seq": 413,
|
|
425
|
-
"type": "ticket-event",
|
|
426
|
-
},
|
|
427
|
-
"ticket_event_resolved": {
|
|
428
|
-
"actorId": "fae",
|
|
429
|
-
"actorName": "Fae",
|
|
430
|
-
"actorType": "AI",
|
|
431
|
-
"kind": "RESOLVED",
|
|
432
|
-
"reason": undefined,
|
|
433
|
-
"seq": 412,
|
|
434
|
-
"type": "ticket-event",
|
|
435
|
-
},
|
|
436
|
-
"ticket_event_stream_seq_wins": {
|
|
437
|
-
"actorId": undefined,
|
|
438
|
-
"actorName": undefined,
|
|
439
|
-
"actorType": undefined,
|
|
440
|
-
"kind": "RESOLVED",
|
|
441
|
-
"reason": undefined,
|
|
442
|
-
"seq": 500,
|
|
443
|
-
"type": "ticket-event",
|
|
444
|
-
},
|
|
445
|
-
"ticket_event_unknown_kind": {
|
|
446
|
-
"actorId": undefined,
|
|
447
|
-
"actorName": "Roman Smith",
|
|
448
|
-
"actorType": undefined,
|
|
449
|
-
"kind": "ON_HOLD",
|
|
450
|
-
"reason": undefined,
|
|
451
|
-
"type": "ticket-event",
|
|
452
|
-
},
|
|
453
409
|
"token_usage": {
|
|
454
410
|
"contextSize": 180000,
|
|
455
411
|
"inputTokensSize": 1200,
|
|
@@ -257,38 +257,6 @@ const CORPUS: Record<string, unknown> = {
|
|
|
257
257
|
ticket_escalated_missing_reason: { type: 'TICKET_ESCALATED', ticketId: 'ticket-1' },
|
|
258
258
|
ticket_escalated_missing_ticket_id: { type: 'TICKET_ESCALATED', reason: 'INACTIVITY' },
|
|
259
259
|
|
|
260
|
-
// TICKET_EVENT — standalone lifecycle receipt. `kind` is an OPEN string:
|
|
261
|
-
// unknown kinds must decode (neutral render), only a missing/blank kind is
|
|
262
|
-
// malformed. The chunk names its JetStream sequence `sequenceId` in the
|
|
263
|
-
// payload; it backfills `seq` when the transport didn't stamp `streamSeq`.
|
|
264
|
-
ticket_event_resolved: {
|
|
265
|
-
type: 'TICKET_EVENT',
|
|
266
|
-
kind: 'RESOLVED',
|
|
267
|
-
actorId: 'fae',
|
|
268
|
-
actorName: 'Fae',
|
|
269
|
-
actorType: 'AI',
|
|
270
|
-
sequenceId: 412,
|
|
271
|
-
},
|
|
272
|
-
ticket_event_reopened_with_reason: {
|
|
273
|
-
type: 'TICKET_EVENT',
|
|
274
|
-
kind: 'REOPENED',
|
|
275
|
-
actorId: 'user-42',
|
|
276
|
-
actorName: 'John Smith',
|
|
277
|
-
actorType: 'CLIENT',
|
|
278
|
-
reason: 'The printer stopped working again',
|
|
279
|
-
sequenceId: 413,
|
|
280
|
-
},
|
|
281
|
-
ticket_event_unknown_kind: { type: 'TICKET_EVENT', kind: 'ON_HOLD', actorName: 'Roman Smith' },
|
|
282
|
-
ticket_event_missing_kind: { type: 'TICKET_EVENT', actorName: 'Fae' },
|
|
283
|
-
ticket_event_blank_reason_dropped: { type: 'TICKET_EVENT', kind: 'RESOLVED', reason: ' ' },
|
|
284
|
-
// Transport-stamped streamSeq wins over the payload's sequenceId copy.
|
|
285
|
-
ticket_event_stream_seq_wins: {
|
|
286
|
-
type: 'TICKET_EVENT',
|
|
287
|
-
kind: 'RESOLVED',
|
|
288
|
-
streamSeq: 500,
|
|
289
|
-
sequenceId: 499,
|
|
290
|
-
},
|
|
291
|
-
|
|
292
260
|
approval_result_approved: {
|
|
293
261
|
type: 'APPROVAL_RESULT',
|
|
294
262
|
approvalRequestId: 'req-1',
|
|
@@ -251,21 +251,6 @@ export interface TicketEscalatedEvent extends ChatStreamEventBase {
|
|
|
251
251
|
text?: string
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
/** Ticket lifecycle receipt (`TICKET_EVENT`) — the ticket was resolved,
|
|
255
|
-
* reopened, etc. `kind` is an OPEN vocabulary (RESOLVED/REOPENED today):
|
|
256
|
-
* an unknown kind still decodes and renders as a neutral line rather than
|
|
257
|
-
* being dropped, so the backend can add kinds without a client release.
|
|
258
|
-
* Arrives standalone (outside MESSAGE_START/END), like `ticket-escalated`. */
|
|
259
|
-
export interface TicketEventEvent extends ChatStreamEventBase {
|
|
260
|
-
type: 'ticket-event'
|
|
261
|
-
kind: string
|
|
262
|
-
actorId?: string
|
|
263
|
-
actorName?: string
|
|
264
|
-
/** Who acted — e.g. an AI agent vs a human technician. Open string. */
|
|
265
|
-
actorType?: string
|
|
266
|
-
reason?: string
|
|
267
|
-
}
|
|
268
|
-
|
|
269
254
|
/** Per-turn metadata. Raw wire values pass through UNVALIDATED — the
|
|
270
255
|
* consumer replicates the legacy truthiness/typeof gates (so a
|
|
271
256
|
* malformed frame degrades identically to the pre-SSOT parser). */
|
|
@@ -380,7 +365,6 @@ export type ChatStreamEvent =
|
|
|
380
365
|
| EscalationOfferEvent
|
|
381
366
|
| EscalationOfferResolvedEvent
|
|
382
367
|
| TicketEscalatedEvent
|
|
383
|
-
| TicketEventEvent
|
|
384
368
|
| ChatMetadataEvent
|
|
385
369
|
| UsageEvent
|
|
386
370
|
| TokenUsageEvent
|
|
@@ -370,33 +370,6 @@ export function decodeNatsChunk(chunk: unknown): ChatStreamEvent | null {
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
case MESSAGE_TYPE.TICKET_EVENT: {
|
|
374
|
-
// `kind` is the only required field, and deliberately an OPEN string:
|
|
375
|
-
// unknown kinds must render (as a neutral line), not be dropped.
|
|
376
|
-
const kind = typeof data.kind === 'string' ? data.kind.trim() : ''
|
|
377
|
-
if (!kind) return null
|
|
378
|
-
// This chunk names its own JetStream sequence `sequenceId` in the
|
|
379
|
-
// payload (the persisted row's `lastChunkStreamSeq` equals it — the
|
|
380
|
-
// dedupe key). Prefer the transport-stamped `streamSeq` envelope like
|
|
381
|
-
// every other chunk; fall back to the payload copy when absent.
|
|
382
|
-
const eventSeq: { seq?: number } =
|
|
383
|
-
seq.seq !== undefined
|
|
384
|
-
? seq
|
|
385
|
-
: typeof data.sequenceId === 'number'
|
|
386
|
-
? { seq: data.sequenceId }
|
|
387
|
-
: {}
|
|
388
|
-
return {
|
|
389
|
-
type: 'ticket-event',
|
|
390
|
-
kind,
|
|
391
|
-
actorId: typeof data.actorId === 'string' ? data.actorId : undefined,
|
|
392
|
-
actorName: typeof data.actorName === 'string' ? data.actorName : undefined,
|
|
393
|
-
actorType: typeof data.actorType === 'string' ? data.actorType : undefined,
|
|
394
|
-
reason:
|
|
395
|
-
typeof data.reason === 'string' && data.reason.trim() ? data.reason : undefined,
|
|
396
|
-
...eventSeq,
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
373
|
case MESSAGE_TYPE.ERROR:
|
|
401
374
|
return {
|
|
402
375
|
type: 'error',
|
|
@@ -10,7 +10,6 @@ import { ApprovalRequestMessage } from "./approval-request-message"
|
|
|
10
10
|
import { ApprovalBatchMessage } from "./approval-batch-message"
|
|
11
11
|
import { EscalationOfferMessage } from "./escalation-offer-message"
|
|
12
12
|
import { TicketEscalatedMessage } from "./ticket-escalated-message"
|
|
13
|
-
import { TicketEventMessage } from "./ticket-event-message"
|
|
14
13
|
import { ErrorMessageDisplay } from "./error-message-display"
|
|
15
14
|
import { ContextCompactionDisplay } from "./context-compaction-display"
|
|
16
15
|
import { ThinkingDisplay } from "./thinking-display"
|
|
@@ -651,8 +650,6 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
651
650
|
)
|
|
652
651
|
} else if (segment.type === 'ticket_escalated') {
|
|
653
652
|
return <TicketEscalatedMessage key={index} data={segment.data} timestamp={timestamp} />
|
|
654
|
-
} else if (segment.type === 'ticket_event') {
|
|
655
|
-
return <TicketEventMessage key={index} data={segment.data} timestamp={timestamp} />
|
|
656
653
|
} else if (segment.type === 'error') {
|
|
657
654
|
return (
|
|
658
655
|
<ErrorMessageDisplay
|
|
@@ -5,8 +5,6 @@ export * from './approval-request-message'
|
|
|
5
5
|
export * from './approval-batch-message'
|
|
6
6
|
export * from './escalation-offer-message'
|
|
7
7
|
export * from './ticket-escalated-message'
|
|
8
|
-
export * from './ticket-event-message'
|
|
9
|
-
export * from './ai-assistant-info'
|
|
10
8
|
export * from './context-compaction-display'
|
|
11
9
|
export * from './expand-chevron'
|
|
12
10
|
export * from './thinking-display'
|
|
@@ -1364,25 +1364,6 @@ export function createChatStreamReducer(
|
|
|
1364
1364
|
break
|
|
1365
1365
|
}
|
|
1366
1366
|
|
|
1367
|
-
case 'ticket-event': {
|
|
1368
|
-
// Standalone chunk outside MESSAGE_START/END — same routing as
|
|
1369
|
-
// `ticket-escalated`. `seq` rides onto the segment: it is the event's
|
|
1370
|
-
// dedupe identity (see `addTicketEvent`).
|
|
1371
|
-
const before = accumulator.getSegments().length
|
|
1372
|
-
const segments = accumulator.addTicketEvent(
|
|
1373
|
-
{
|
|
1374
|
-
kind: event.kind,
|
|
1375
|
-
actorId: event.actorId,
|
|
1376
|
-
actorName: event.actorName,
|
|
1377
|
-
actorType: event.actorType,
|
|
1378
|
-
reason: event.reason,
|
|
1379
|
-
},
|
|
1380
|
-
seq,
|
|
1381
|
-
)
|
|
1382
|
-
applyAccumulated(before, segments)
|
|
1383
|
-
break
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
1367
|
case 'error': {
|
|
1387
1368
|
let message: string | undefined
|
|
1388
1369
|
if (event.details) {
|
|
@@ -96,11 +96,6 @@ function upsertKey(seg: MessageSegment): string | null {
|
|
|
96
96
|
return `offer:${seg.data.offerId}`
|
|
97
97
|
case 'ticket_escalated':
|
|
98
98
|
return `escalated:${seg.data.ticketId}`
|
|
99
|
-
case 'ticket_event':
|
|
100
|
-
// The stream sequence is the event's only stable id; a seq-less segment
|
|
101
|
-
// (hydrated history) is pushed raw — `addTicketEvent`'s payload-equality
|
|
102
|
-
// upsert owns that case.
|
|
103
|
-
return seg.streamSeq !== undefined ? `ticket-event:${seg.streamSeq}` : null
|
|
104
99
|
default:
|
|
105
100
|
return null
|
|
106
101
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { forwardRef, type HTMLAttributes } from "react"
|
|
4
|
+
import { cn } from "../../utils/cn"
|
|
5
|
+
import { formatTime } from "../../utils/format-date"
|
|
4
6
|
import { InfoCircleIcon } from "../icons-v2-generated"
|
|
5
|
-
import { AiAssistantInfo } from "./ai-assistant-info"
|
|
6
7
|
import type { TicketEscalatedSegment } from "./types/message.types"
|
|
7
8
|
|
|
8
9
|
const TITLE = "Handed Off to a Technician"
|
|
@@ -18,25 +19,46 @@ export interface TicketEscalatedMessageProps extends HTMLAttributes<HTMLDivEleme
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Handoff receipt — the conversation now belongs to a human technician.
|
|
21
|
-
* Figma `ai-assistant-info` (type=escalation)
|
|
22
|
-
* truncation trade-offs live in the shared `AiAssistantInfo` shell.
|
|
22
|
+
* Figma `ai-assistant-info` (type=escalation).
|
|
23
23
|
*
|
|
24
24
|
* The body is whatever the backend authored, so the reason it gives stays
|
|
25
25
|
* server-side; the client never composes an explanation and never branches on
|
|
26
26
|
* `data.reason`, which is why a reason added server-side needs no change here.
|
|
27
|
+
*
|
|
28
|
+
* That body WRAPS, deliberately diverging from the mock's single-line ellipsis:
|
|
29
|
+
* the mock's 72px comes from its placeholder copy fitting one line, while the
|
|
30
|
+
* real wire text is a full sentence, and clipping the only explanation of why
|
|
31
|
+
* the chat was handed off is not recoverable on touch. The title still
|
|
32
|
+
* truncates — it is a constant, and holding one line keeps the timestamp from
|
|
33
|
+
* being pushed out of the row at narrow widths.
|
|
27
34
|
*/
|
|
28
35
|
const TicketEscalatedMessage = forwardRef<HTMLDivElement, TicketEscalatedMessageProps>(
|
|
29
|
-
({ data, timestamp, ...props }, ref) => {
|
|
36
|
+
({ className, data, timestamp, ...props }, ref) => {
|
|
30
37
|
const body = data.text?.trim() || DEFAULT_BODY
|
|
31
38
|
return (
|
|
32
|
-
<
|
|
39
|
+
<div
|
|
33
40
|
ref={ref}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
className={cn(
|
|
42
|
+
"mb-[var(--spacing-system-xsf)] flex items-start gap-[var(--spacing-system-s)] rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-s)]",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
38
45
|
{...props}
|
|
39
|
-
|
|
46
|
+
>
|
|
47
|
+
<div className="flex size-12 shrink-0 items-center justify-center rounded-md border border-ods-border bg-ods-bg">
|
|
48
|
+
<InfoCircleIcon className="size-6 text-ods-flamingo-pink" />
|
|
49
|
+
</div>
|
|
50
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
51
|
+
<div className="flex w-full items-start">
|
|
52
|
+
<p className="text-h4 min-w-0 flex-1 truncate text-ods-text-primary" title={TITLE}>
|
|
53
|
+
{TITLE}
|
|
54
|
+
</p>
|
|
55
|
+
{timestamp && (
|
|
56
|
+
<span className="text-h6 shrink-0 text-right text-ods-text-secondary">{formatTime(timestamp)}</span>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
<p className="text-h4 w-full break-words text-ods-text-secondary">{body}</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
40
62
|
)
|
|
41
63
|
},
|
|
42
64
|
)
|
|
@@ -18,7 +18,6 @@ export const MESSAGE_TYPE = {
|
|
|
18
18
|
APPROVAL_RESULT: 'APPROVAL_RESULT',
|
|
19
19
|
ESCALATION_OFFER: 'ESCALATION_OFFER',
|
|
20
20
|
TICKET_ESCALATED: 'TICKET_ESCALATED',
|
|
21
|
-
TICKET_EVENT: 'TICKET_EVENT',
|
|
22
21
|
ERROR: 'ERROR',
|
|
23
22
|
MESSAGE_START: 'MESSAGE_START',
|
|
24
23
|
MESSAGE_END: 'MESSAGE_END',
|
|
@@ -159,34 +158,6 @@ export interface TicketEscalatedData {
|
|
|
159
158
|
text?: string
|
|
160
159
|
}
|
|
161
160
|
|
|
162
|
-
// ========== Ticket Event Types ==========
|
|
163
|
-
|
|
164
|
-
/** Known `TicketEventData.kind` values. The vocabulary is OPEN on the wire:
|
|
165
|
-
* an unknown kind still renders (as a neutral info line) rather than being
|
|
166
|
-
* dropped, so the backend can add lifecycle kinds without a client release. */
|
|
167
|
-
export const TICKET_EVENT_KIND = {
|
|
168
|
-
RESOLVED: 'RESOLVED',
|
|
169
|
-
REOPENED: 'REOPENED',
|
|
170
|
-
} as const
|
|
171
|
-
|
|
172
|
-
export type TicketEventKind =
|
|
173
|
-
| typeof TICKET_EVENT_KIND[keyof typeof TICKET_EVENT_KIND]
|
|
174
|
-
| (string & {})
|
|
175
|
-
|
|
176
|
-
/** Ticket lifecycle receipt (`TICKET_EVENT`) — the ticket was resolved,
|
|
177
|
-
* reopened, etc. Same field names live (NATS chunk) and persisted
|
|
178
|
-
* (GraphQL `TicketEventData` inside `messageData`), so ONE mapper covers
|
|
179
|
-
* both paths. All actor fields are optional: the event stays renderable
|
|
180
|
-
* when the backend can't attribute it. */
|
|
181
|
-
export interface TicketEventData {
|
|
182
|
-
kind: TicketEventKind
|
|
183
|
-
actorId?: string
|
|
184
|
-
actorName?: string
|
|
185
|
-
/** Who acted — e.g. an AI agent vs a human. Open string like `kind`. */
|
|
186
|
-
actorType?: string
|
|
187
|
-
reason?: string
|
|
188
|
-
}
|
|
189
|
-
|
|
190
161
|
/**
|
|
191
162
|
* Single tool call inside a batch approval request.
|
|
192
163
|
* Mirrors backend PendingToolCallDto.
|
|
@@ -339,23 +310,6 @@ export type TicketEscalatedSegment = {
|
|
|
339
310
|
data: TicketEscalatedData
|
|
340
311
|
}
|
|
341
312
|
|
|
342
|
-
/**
|
|
343
|
-
* Ticket lifecycle receipt (resolved / reopened / an unknown future kind).
|
|
344
|
-
* Arrives as a standalone `TICKET_EVENT` chunk outside MESSAGE_START/END —
|
|
345
|
-
* same delivery shape as `ticket_escalated`.
|
|
346
|
-
*
|
|
347
|
-
* `streamSeq` is the chunk's JetStream sequence, kept ON the segment because
|
|
348
|
-
* it is the event's only stable identity: the accumulator upserts on it so a
|
|
349
|
-
* redelivered chunk (catch-up over hydrated history) replaces rather than
|
|
350
|
-
* stacks a second card, while two genuinely distinct events of the same kind
|
|
351
|
-
* (resolve → reopen → resolve) keep distinct sequences and both render.
|
|
352
|
-
*/
|
|
353
|
-
export type TicketEventSegment = {
|
|
354
|
-
type: 'ticket_event'
|
|
355
|
-
data: TicketEventData
|
|
356
|
-
streamSeq?: number
|
|
357
|
-
}
|
|
358
|
-
|
|
359
313
|
export type ErrorSegment = {
|
|
360
314
|
type: 'error'
|
|
361
315
|
title: string
|
|
@@ -368,7 +322,7 @@ export type ContextCompactionSegment = {
|
|
|
368
322
|
summary?: string
|
|
369
323
|
}
|
|
370
324
|
|
|
371
|
-
export type MessageSegment = TextSegment | ThinkingSegment | GuideSegment | AskSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | EscalationOfferSegment | TicketEscalatedSegment |
|
|
325
|
+
export type MessageSegment = TextSegment | ThinkingSegment | GuideSegment | AskSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | EscalationOfferSegment | TicketEscalatedSegment | ErrorSegment | ContextCompactionSegment
|
|
372
326
|
|
|
373
327
|
export type MessageContent = string | MessageSegment[]
|
|
374
328
|
|
|
@@ -473,18 +427,6 @@ export interface TicketEscalatedMessageData extends MessageDataBase {
|
|
|
473
427
|
text?: string
|
|
474
428
|
}
|
|
475
429
|
|
|
476
|
-
/** Persisted ticket lifecycle row — same field names as the live chunk
|
|
477
|
-
* (see `TicketEventData`), decoded by the SAME mapper on replay. Fields are
|
|
478
|
-
* wire-shaped (`| null`): every one is a nullable GraphQL scalar. */
|
|
479
|
-
export interface TicketEventMessageData extends MessageDataBase {
|
|
480
|
-
type: 'TICKET_EVENT'
|
|
481
|
-
kind?: string | null
|
|
482
|
-
actorId?: string | null
|
|
483
|
-
actorName?: string | null
|
|
484
|
-
actorType?: string | null
|
|
485
|
-
reason?: string | null
|
|
486
|
-
}
|
|
487
|
-
|
|
488
430
|
export interface ErrorMessageData extends MessageDataBase {
|
|
489
431
|
type: 'ERROR'
|
|
490
432
|
error?: string
|
|
@@ -531,7 +473,6 @@ export type MessageData =
|
|
|
531
473
|
| ApprovalResultMessageData
|
|
532
474
|
| EscalationOfferMessageData
|
|
533
475
|
| TicketEscalatedMessageData
|
|
534
|
-
| TicketEventMessageData
|
|
535
476
|
| ErrorMessageData
|
|
536
477
|
| AIMetadataMessageData
|
|
537
478
|
| SystemMessageData
|
|
@@ -20,8 +20,6 @@ import type {
|
|
|
20
20
|
EscalationOfferSegment,
|
|
21
21
|
TicketEscalatedData,
|
|
22
22
|
TicketEscalatedSegment,
|
|
23
|
-
TicketEventData,
|
|
24
|
-
TicketEventSegment,
|
|
25
23
|
ContextCompactionSegment,
|
|
26
24
|
ErrorSegment,
|
|
27
25
|
PendingApproval,
|
|
@@ -537,42 +535,6 @@ export class MessageSegmentAccumulator {
|
|
|
537
535
|
return this.getSegments()
|
|
538
536
|
}
|
|
539
537
|
|
|
540
|
-
/**
|
|
541
|
-
* Add a ticket lifecycle receipt (resolved / reopened / unknown kind).
|
|
542
|
-
*
|
|
543
|
-
* Upsert identity is the chunk's stream sequence when BOTH sides know it;
|
|
544
|
-
* otherwise full payload equality. History-hydrated segments come back
|
|
545
|
-
* seq-less (the persisted row's seq lives on the message, not the
|
|
546
|
-
* `messageData`), so a JetStream catch-up redelivery of the same event must
|
|
547
|
-
* still match its hydrated twin — payload equality is what catches that.
|
|
548
|
-
* Two genuinely distinct events (resolve → reopen → resolve) differ in seq
|
|
549
|
-
* or payload and both render.
|
|
550
|
-
*/
|
|
551
|
-
addTicketEvent(data: TicketEventData, streamSeq?: number): MessageSegment[] {
|
|
552
|
-
const segment: TicketEventSegment = {
|
|
553
|
-
type: 'ticket_event',
|
|
554
|
-
data,
|
|
555
|
-
...(streamSeq !== undefined ? { streamSeq } : {}),
|
|
556
|
-
}
|
|
557
|
-
const existingIndex = this.segments.findIndex((s) => {
|
|
558
|
-
if (s.type !== 'ticket_event') return false
|
|
559
|
-
if (s.streamSeq !== undefined && streamSeq !== undefined) return s.streamSeq === streamSeq
|
|
560
|
-
return (
|
|
561
|
-
s.data.kind === data.kind &&
|
|
562
|
-
s.data.actorId === data.actorId &&
|
|
563
|
-
s.data.actorName === data.actorName &&
|
|
564
|
-
s.data.actorType === data.actorType &&
|
|
565
|
-
s.data.reason === data.reason
|
|
566
|
-
)
|
|
567
|
-
})
|
|
568
|
-
if (existingIndex !== -1) {
|
|
569
|
-
this.segments[existingIndex] = segment
|
|
570
|
-
return this.getSegments()
|
|
571
|
-
}
|
|
572
|
-
this.segments.push(segment)
|
|
573
|
-
return this.getSegments()
|
|
574
|
-
}
|
|
575
|
-
|
|
576
538
|
/**
|
|
577
539
|
* Update status of an existing approval segment (single, batch, or
|
|
578
540
|
* escalation offer).
|
|
@@ -718,9 +680,6 @@ export class MessageSegmentAccumulator {
|
|
|
718
680
|
case 'ticket_escalated':
|
|
719
681
|
this.addTicketEscalated(segment.data)
|
|
720
682
|
break
|
|
721
|
-
case 'ticket_event':
|
|
722
|
-
this.addTicketEvent(segment.data, segment.streamSeq)
|
|
723
|
-
break
|
|
724
683
|
case 'error':
|
|
725
684
|
this.addError(segment.title, segment.details)
|
|
726
685
|
break
|
|
@@ -243,23 +243,6 @@ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent
|
|
|
243
243
|
}
|
|
244
244
|
return null
|
|
245
245
|
|
|
246
|
-
// Same completeness gate as the live decoder: `kind` (an OPEN string —
|
|
247
|
-
// unknown kinds render as a neutral line) is the only required field.
|
|
248
|
-
// Field names match the chunk, so ONE mapping covers both paths; the
|
|
249
|
-
// typeof gates also fold the row's GraphQL nulls to undefined.
|
|
250
|
-
case MESSAGE_TYPE.TICKET_EVENT: {
|
|
251
|
-
const kind = 'kind' in data && typeof data.kind === 'string' ? data.kind.trim() : ''
|
|
252
|
-
if (!kind) return null
|
|
253
|
-
return {
|
|
254
|
-
type: 'ticket-event',
|
|
255
|
-
kind,
|
|
256
|
-
actorId: typeof data.actorId === 'string' ? data.actorId : undefined,
|
|
257
|
-
actorName: typeof data.actorName === 'string' ? data.actorName : undefined,
|
|
258
|
-
actorType: typeof data.actorType === 'string' ? data.actorType : undefined,
|
|
259
|
-
reason: typeof data.reason === 'string' && data.reason.trim() ? data.reason : undefined,
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
246
|
case MESSAGE_TYPE.ERROR:
|
|
264
247
|
if ('error' in data) {
|
|
265
248
|
return {
|
|
@@ -353,20 +336,6 @@ function applyHistoryEvent(
|
|
|
353
336
|
})
|
|
354
337
|
break
|
|
355
338
|
|
|
356
|
-
// Seq-less on purpose: the persisted row's sequence lives on the message
|
|
357
|
-
// (`lastChunkStreamSeq`), not in `messageData`. A live redelivery of the
|
|
358
|
-
// same event matches this segment by payload equality (see
|
|
359
|
-
// `addTicketEvent`).
|
|
360
|
-
case 'ticket-event':
|
|
361
|
-
accumulator.addTicketEvent({
|
|
362
|
-
kind: event.kind,
|
|
363
|
-
actorId: event.actorId,
|
|
364
|
-
actorName: event.actorName,
|
|
365
|
-
actorType: event.actorType,
|
|
366
|
-
reason: event.reason,
|
|
367
|
-
})
|
|
368
|
-
break
|
|
369
|
-
|
|
370
339
|
case 'escalation-offer-resolved':
|
|
371
340
|
// Recorded, not applied here: `applyOfferResolutions` runs after every
|
|
372
341
|
// flush and covers the same-bubble case as well as the cross-bubble one.
|