Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ff0880b
feat(cc-store): add AI summary lifecycle
rsarika Oct 8, 2026
bf6d1fd
feat(cc-components): add AI summary presentation
rsarika Oct 8, 2026
c8a3b29
fix(cc-store): simplify AI summary handling
rsarika Oct 9, 2026
9821155
refactor(cc-store): retain SDK AI summary payloads
rsarika Oct 9, 2026
c75883b
refactor(cc-store): update summary content in store
rsarika Oct 9, 2026
5b31f07
refactor(cc-store): retain SDK summary response records
rsarika Oct 9, 2026
c082890
refactor(cc-store): read SDK summary flags directly
rsarika Oct 9, 2026
79b8573
refactor(cc-store): rely on SDK summary enablement
rsarika Oct 9, 2026
199f875
refactor(cc-store): simplify summary event handling
rsarika Oct 9, 2026
8727856
refactor(cc-store): inline summary state operations
rsarika Oct 9, 2026
f3f75bb
refactor(cc-store): inline summary selector and use published SDK
rsarika Oct 9, 2026
762dcba
fix(cc-components): integrate simplified AI summary store
rsarika Oct 9, 2026
1d6eb17
refactor(cc-components): simplify AI summary presentation
rsarika Oct 10, 2026
28135ae
refactor(cc-components): reuse assistant presentation primitives
rsarika Oct 10, 2026
e8793be
Revert unrelated SDK sample changes
rsarika Oct 14, 2026
c267377
Revert store build script change
rsarika Oct 14, 2026
adf585a
refactor(cc-store): simplify AI summary state and response flow
rsarika Oct 14, 2026
dc5a89f
refactor(cc-store): separate AI summary request flows
rsarika Oct 14, 2026
5d4e1ad
fix(cc-store): restore CI install and test compatibility
rsarika Oct 14, 2026
e36e95b
Merge branch 'codex/ai-summary-store' into codex/ai-summary-components
rsarika Oct 14, 2026
a5f2adf
fix(cc-components): address AI summary review feedback
rsarika Oct 14, 2026
6ff5574
Merge remote-tracking branch 'upstream/next' into codex/ai-summary-co…
rsarika Oct 16, 2026
33e6032
fix(cc-components): address AI summary review findings
rsarika Oct 16, 2026
c35ee00
feat(cc-components): add AI summaries and fix downstream build
rsarika Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ai-docs/CONTRACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ The React entry of `@webex/cc-widgets` re-exports the widgets, including `AIAssi
| ai-assistant.AIAssistant | `@webex/cc-widgets` and `@webex/cc-ai-assistant` (React); `@webex/cc-widgets/wc` (custom element) | `AIAssistant`; `widget-cc-ai-assistant` | React component and existing custom element; wellness callbacks `onWellnessBreakOffered`, `onWellnessBreakAccepted`, `onWellnessBreakStarted`, `onWellnessBreakEnded`, `onWellnessBreakError`; optional `wellnessAudioUrl` and `wellnessBreakOverlayTarget` | Both React import paths remain available; WC entry retains the tag | `packages/contact-center/ai-assistant/ai-docs/ai-assistant-spec.md` | `packages/contact-center/ai-assistant/src/ai-assistant.types.ts`, `packages/contact-center/cc-widgets/src/index.ts`, `packages/contact-center/cc-widgets/src/wc.ts` |
| ai-assistant.wellness-types | `@webex/cc-ai-assistant` | `WellnessBreakPhase`, `WellnessBreakErrorCode`, `WellnessBreakError`, `WellnessBreakOverlayTarget` | Exported TypeScript lifecycle, error, and layout contracts | additive minor surface; literals are stable | `packages/contact-center/ai-assistant/ai-docs/ai-assistant-spec.md` | `packages/contact-center/ai-assistant/src/ai-assistant.types.ts` |
| cc-components.WellnessBreak | `@webex/cc-components` | `WellnessBreakRequestCard`, `WellnessBreakOfferCard`, `WellnessBreakModal`, `WellnessBreakError` | Props-only Agent Wellness Break presentation; modal accepts an optional animation loader supplied by AI Assistant and owns no wellness media | monorepo component surface; no SDK/store or Lottie dependency | `packages/contact-center/cc-components/ai-docs/cc-components-spec.md` | `packages/contact-center/cc-components/src/components/AIAssistant/WellnessBreak/` |
| cc-components.AIAssistantReceiverSummary | `@webex/cc-components` | `AIAssistantComponentProps.receiverSummary`, `AIAssistantReceiverSummary` | Props-only receiver view with a stable interaction `branchKey`, SDK `content`, feedback selection/pending state, and copy/feedback callbacks bound by the widget to that displayed summary. Callbacks carry no numeric revision. The component renders a display-only card and copies only visible card text; SDK requests and submission remain in the store. | additive presentation surface; consumed by the widget integration | `packages/contact-center/cc-components/ai-docs/cc-components-spec.md` (AI summary presentation) | `packages/contact-center/cc-components/src/components/AIAssistant/ai-assistant.types.ts` |
| cc-components.CallControlAISummary | `@webex/cc-components` | `CallControlComponentProps.aiSummary`, `CallControlAISummaryProps`, `ConsultTransferSummaryView`, `WrapUpSummaryView` | Optional props-only consult/transfer and wrap-up summary views; raw SDK content and local `editedSummary` are separate. Callbacks carry no numeric revision. Requests, edits and SDK submission are supplied by the widget/store. | additive optional props; `wrapupCall` supports void or Promise<boolean>; the internal AISummary component remains private | `packages/contact-center/cc-components/ai-docs/cc-components-spec.md` (AI summaries in CallControl) | `packages/contact-center/cc-components/src/components/task/task.types.ts` |
| cc-widgets.store | `@webex/cc-store` | `store` | MobX singleton (`Store.getInstance()`); `init(options: InitParams, setupEventListeners): Promise<void>`; sole SDK access point via `store.cc.*` | stable semver | `packages/contact-center/store/ai-docs/store-spec.md` | `packages/contact-center/store/src/index.ts:1,4` |
| store.ai-summary | `@webex/cc-store` | `cc.aiSummaries`, summary request/receiver-feedback/send methods, `AISummaryEntry`, `AISummaryRole`, `AISummaryResponseSubmissionResult` | Entries keyed by interaction and role retain the unchanged SDK `AISummary` in `content` and `AISummaryResponse` in `response`. Requests return `AISummary` or `undefined`; senders return `AISummaryResponseSubmissionResult`. Widget helpers own local editing, copy tracking and deferred feedback through response fields. `setReceiverSummaryFeedback(feedback, expectedSummary, task)` uses the displayed SDK payload to guard its asynchronous acknowledgement. Consumers read enablement from `task.aiSummaryCapabilities`; `task:featureEnablement` refreshes the task view. Wrap-up sends `sendPostCallSummaryResponse(wrapUpCode, originalTask)` before `task.wrapup()`. | additive; uses published `@webex/contact-center` `3.12.0-next.148` | `packages/contact-center/store/ai-docs/store-spec.md` (AI summaries) | `packages/contact-center/store/src/store.types.ts`, `packages/contact-center/store/src/storeEventsWrapper.ts` |
| store.host-init.enableWxBetterTogether | `@webex/cc-store` | `webexConfig.cc.enableWxBetterTogether` → `store.enableWxBetterTogether` | Host init boolean (default `false`), set **before** `store.init()`: read from `webexConfig.cc` on `{ webexConfig, access_token }` init or `webex.config.cc` on `{ webex }` init; read-only observable after init (Phase 1: re-init to change). Gates wxApp Mute/Keypad visibility and wxApp mute seed; SDK owns mute/DTMF API routing. | stable semver; renaming or removing the init key or store getter is breaking | `packages/contact-center/store/ai-docs/store-spec.md` (`STORE-R-030`); `packages/contact-center/task/ai-docs/task-spec.md` (WXCC-6026) | `packages/contact-center/store/src/store.ts`, `packages/contact-center/store/src/store.types.ts` (`WebexCcInitConfig`) |
Expand Down
105 changes: 105 additions & 0 deletions packages/contact-center/cc-components/ai-docs/cc-components-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,111 @@ Each component is tested in isolation with React Testing Library: render from a
| `CC-COMPONENTS-R-017` | `tests/components/task/CallControlCAD/call-control-cad.tsx` (Momentum structure/configuration, sections, immediate/confirmed Drop, pending/consult disabled state, read-only rows, focus fallback, live regions) | Momentum overlay keyboard behavior and visual placement receive final manual coverage |
| `CC-COMPONENTS-R-018` | `tests/components/task/CallControl/CallControlCustom/consult-transfer-popover.tsx`, `tests/components/task/CallControl/CallControlCustom/consult-transfer-popover.snapshot.tsx` | None |

## AI Assistant summary presentation

The internal `components/AIAssistant/AISummary` surface renders generating, unavailable, error,
and content states from props. Initiator and post-call summaries support keyed text
edits, copy and feedback. Post-call supports Retry and a read-only Outcome from SDK
`resolution`; the display labels never become SDK section keys. Receiver summaries
use copy and feedback actions around a display-only Adaptive Card. Receiver content
without an Adaptive Card renders as unavailable and exposes no summary actions.

Summary content props retain the store's SDK `AISummary` shape, including the required
`conversationId`; feedback and lifecycle values are supplied by the widget from the
store entry. Generating and error states accept absent content so widgets can pass
the store value directly without constructing an empty SDK payload. An editable
content state without a payload falls back to unavailable and exposes no actions.
Edit callbacks notify the owner without requiring a return value. Component tests
use the same SDK shape. Receiver rendering and focus
handlers live in the assistant component alongside its chrome and summary branch.
The mode-specific prop union separates editable summaries from receiver actions and
requires content and callbacks only when receiver content is available. The shared
action surface uses that SDK payload only as identity; the assistant card branch
still owns receiver rendering. Callbacks are bound by the widget to the displayed
SDK summary and accept no numeric revision. Section ordering, labels, plain-text
clipboard formatting, bullet presentation and auto-sizing native editors belong to
this component. Widget helpers keep local edits, copy counts and
deferred feedback in the SDK response record; SDK submission stays in the store.
The presentation-state union is declared locally rather than derived from a store
selector. Pending clipboard completions cannot update confirmation after the
displayed SDK payload changes.

Summary loading, Retry, editable section activation and tooltip presentation use the
same Momentum primitives as Real-time Assist and Wellness Break. Tooltip visibility
remains controlled to keep hovered content reachable and consume Escape before the
containing panel handles it; button labels and the pending-feedback description
remain unchanged. Keyed editing, clipboard formatting, confirmation state and focus
recovery stay summary-specific.

Structured section previews size the Momentum button host to their full wrapped
content and allow wrapping through its exposed `button-text` part. Each section
reserves its actual text height, including bullet lists, in consult/transfer and
post-call views. Disabled previews retain readable text and keyboard focus styling
targets the host; activating an enabled preview still opens its keyed editor.
Empty or whitespace-only section values render no preview label or row in either
editable summary mode. A focused editor stays mounted when its text is cleared;
the empty row disappears after editing ends. Values and cleared section keys remain
unchanged in the SDK payload and the owner's local response, and copy omits blank sections.

`AIAssistantComponent.receiverSummary` adds View summary to closed/minimized chrome
and opens the receiver branch independently of Real-time Assist enablement. Copy uses
visible rendered card text through an opt-in filter in the shared Adaptive Card
text extractor. Hidden receiver text blocks and descendants of hidden card
containers are excluded from summary copying; ordinary Real-time Assist copying
keeps its existing behavior. Visible nested blocks keep their text and line breaks.
The renderer uses Adaptive Cards' `supportsInteractivity: false` for receiver content, keeping
card-provided actions, select actions and inputs inert without rewriting the SDK
card. Its render callback controls whether the separate copy/feedback controls are
shown; parsing or rendering failure hides them. Valid replacement cards preserve
those controls and their focus. Replacing a display-only receiver card resets its
error boundary; ordinary Real-time Assist cards keep their existing recovery
behavior. Closing either chrome clears the receiver selection before invoking the
host callback, so external reopening returns to the ordinary assistant view.
An explicitly selected receiver summary takes precedence over retained wellness
history; an active wellness offer, transition, error or overlay retains priority.
Wellness history remains available when returning to the ordinary assistant view.
Focus capture is limited to the receiver branch and View summary trigger. It is
cleared when focus moves to an ordinary assistant or host control. Summary
components recover focus only when a focused summary control or branch is removed
and no connected control has taken focus; ordinary Real-time Assist and wellness
controls retain their existing focus behavior.

Summary presentation constants use the `AI_ASSISTANT_SUMMARY_` prefix. These components
receive data and callbacks through props and make no SDK calls.
`ai-summary.tsx` owns the rendered UI; `ai-summary.hooks.ts` owns summary interaction,
clipboard, tooltip, and focus behavior.
`AISummary` remains internal; it is not exported by the package's root React barrel.

Evidence: `components/AIAssistant/AISummary/` and `components/AIAssistant/`; tests:
`tests/components/AIAssistant/AISummary/ai-summary.tsx`,
`tests/components/AIAssistant/ai-assistant.tsx`, and
`tests/components/AIAssistant/adaptive-card-renderer.test.tsx`.

## AI summaries in CallControl

Consult/transfer and wrap-up panels forward their summary view props to the shared
`AIAssistant/AISummary` component. The widget binds callbacks to the displayed SDK
content and supplies local response edits separately through `editedSummary`, so
raw content retains its identity. These components perform no SDK operations.

The wrap-up panel owns reason search and selection. Complete Wrap-Up stays enabled
while summary generation is pending and disables completion and summary editing
while wrap-up itself is pending. Failed completion retains the selected reason and
draft. An absent summary prop preserves the existing CallControl form.

Each CallControl panel stylesheet owns its constrained flex and scroll layout;
the shared summary stylesheet owns the editor, status, and action presentation.

Summary focus recovery applies only after a focused summary control is removed and
no connected control has taken focus. Consult/transfer retains its summary layout
until the popover closes after capability revocation, preserving its focus target
and close action. React-generated heading IDs label the panels. Requests, local
response changes, retry and submission remain in the widget/store layers.

Evidence: `components/task/CallControl/call-control.tsx` and `CallControlCustom/`;
tests: `tests/components/task/CallControl/call-control.tsx`,
`CallControlCustom/consult-transfer-popover.tsx`, and `CallControlCustom/wrap-up-summary.tsx`.

## Traceability

- Repo architecture: [`ARCHITECTURE.md`](../../../../ai-docs/ARCHITECTURE.md) · Registry: [`SPEC_INDEX.md`](../../../../ai-docs/SPEC_INDEX.md) · Contracts: [`CONTRACTS.md`](../../../../ai-docs/CONTRACTS.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type {AISummaryFeedbackButton, AISummarySectionKey} from './ai-summary.types';

export const AI_ASSISTANT_SUMMARY_MESSAGES = {
viewSummary: 'View summary',
generatingTitle: 'Generating summary...',
generatingDescription: 'Just a sec—the details are coming together.',
unavailable: 'The summary is not available',
generationError: 'Having trouble generating summary',
generationErrorDescription:
'It could be a lost connection or something else. Could you check your connection or try again later?',
copySummary: 'Copy Summary',
copiedSummary: 'Copied',
attribution: 'AI-generated',
plainSummary: 'Summary',
like: 'This is helpful',
dislike: "This isn't helpful",
retry: 'Retry',
editSectionLabel: (label: string) => `Edit ${label}`,
sectionLabels: {
initialContactReason: 'Initial contact reason',
additionalContactReasons: 'Additional contact reason(s)',
additionalContext: 'Additional context',
keyActionsTaken: 'Key Actions Taken',
nextSteps: 'Next Steps',
reasonForTransferOrConsult: 'Reason for transfer or consult',
resolution: 'Outcome',
},
midCall: {
consultHeading: 'Here’s a consult summary—they’ll get a copy',
transferHeading: 'Here’s a transfer summary—they’ll get a copy.',
searchPlaceholder: 'Search by name, queue, entry point or phone number',
destinationCategory: 'Destination category',
searchDestinations: 'Search destinations',
},
postCall: {
summaryHeading: 'Summary of your conversation',
searchPlaceholder: 'Search topic',
completeAction: 'Complete Wrap-Up',
instructions: 'Choose a code, and click the summary to edit it if needed.',
chooseReason: 'Choose a reason to wrap up',
reasonSearchLabel: 'Search wrap-up reasons',
noReasonMatches: 'No wrap-up reasons match your search.',
},
feedback: {
pendingSubmission: 'Pending submission',
},
} as const;

/** Presentation order for SDK sections and the read-only resolution. */
export const AI_ASSISTANT_SUMMARY_SECTION_ORDER = [
'reasonForTransferOrConsult',
'initialContactReason',
'additionalContactReasons',
'additionalContext',
'keyActionsTaken',
'resolution',
'nextSteps',
] as const satisfies readonly (keyof typeof AI_ASSISTANT_SUMMARY_MESSAGES.sectionLabels)[];

export const AI_ASSISTANT_SUMMARY_FEEDBACK_BUTTONS = [
{feedback: 'thumbs_up', icon: 'like', label: AI_ASSISTANT_SUMMARY_MESSAGES.like},
{feedback: 'thumbs_down', icon: 'dislike', label: AI_ASSISTANT_SUMMARY_MESSAGES.dislike},
] as const satisfies readonly AISummaryFeedbackButton[];

export const AI_ASSISTANT_SUMMARY_BULLETED_SECTION_KEYS: ReadonlySet<AISummarySectionKey> = new Set([
'additionalContactReasons',
'keyActionsTaken',
'nextSteps',
]);
Loading
Loading