Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .sdd/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@
"tier": 1,
"subWidgets": ["CallControl", "CallControlCAD", "IncomingTask", "OutdialCall", "TaskList"]
},
{
"id": "ai-assistant",
"package": "@webex/cc-ai-assistant",
"path": "packages/contact-center/ai-assistant",
"spec": "packages/contact-center/ai-assistant/ai-docs/ai-assistant-spec.md",
"responsibility": "AI Assistant widget: panel chrome, Real-time Assist requests, transcript, feedback actions.",
"coverageState": "DRAFT",
"tier": 1
},
{
"id": "ui-logging",
"package": "@webex/cc-ui-logging",
Expand Down
1 change: 1 addition & 0 deletions ai-docs/SPEC_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
| `station-login/` | Agent login: team + device selection | DRAFT | `packages/contact-center/station-login/ai-docs/station-login-spec.md` |
| `user-state/` | Agent state: state, idle codes, timer | DRAFT | `packages/contact-center/user-state/ai-docs/user-state-spec.md` |
| `task/` | Task widget bundle: CallControl, IncomingTask, OutdialCall, TaskList, CallControlCAD | DRAFT | `packages/contact-center/task/ai-docs/task-spec.md` |
| `ai-assistant/` | AI Assistant widget: chrome + Real-time Assist requests, transcript, feedback | DRAFT | `packages/contact-center/ai-assistant/ai-docs/ai-assistant-spec.md` |
| `ui-logging/` | Metrics/telemetry: `withMetrics`, `metricsLogger` | DRAFT | `packages/contact-center/ui-logging/ai-docs/ui-logging-spec.md` |
| `test-fixtures/` | Shared test mocks/helpers | DRAFT | `packages/contact-center/test-fixtures/ai-docs/test-fixtures-spec.md` |
| `@webex/widgets/` | Legacy meetings widgets (separate family) | DRAFT | `packages/@webex/widgets/ai-docs/widgets-spec.md` |
Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module.exports = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^.+\\.(css|less|scss)$': 'babel-jest',
'^.+\\.svg$': '<rootDir>/jest.file-mock.js',
'^adaptivecards$': '<rootDir>/node_modules/adaptivecards/dist/adaptivecards.js',
},
testEnvironment: 'jsdom',
testMatch: ['**/tooling/tests/**/*.js'],
Expand Down
1 change: 1 addition & 0 deletions jest.file-mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'test-file-stub';
209 changes: 209 additions & 0 deletions packages/contact-center/ai-assistant/ai-docs/ai-assistant-spec.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/contact-center/ai-assistant/bable.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const baseConfig = require('../../../babel.config.js');

module.exports = baseConfig;
20 changes: 20 additions & 0 deletions packages/contact-center/ai-assistant/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginReact from 'eslint-plugin-react';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import eslintConfigPrettier from 'eslint-config-prettier';

export default [
{files: ['**/src/**/*.{js,mjs,cjs,ts,jsx,tsx}']},
{ignores: ['.babelrc.js', '*config.{js,ts}', 'dist', 'node_modules', 'coverage']},
{languageOptions: {globals: globals.browser}},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
settings: {react: {version: 'detect'}},
},
eslintPluginPrettierRecommended,
eslintConfigPrettier,
];
6 changes: 6 additions & 0 deletions packages/contact-center/ai-assistant/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const jestConfig = require('../../../jest.config.js');

jestConfig.rootDir = '../../../';
jestConfig.testMatch = ['**/ai-assistant/tests/**/*.ts', '**/ai-assistant/tests/**/*.tsx'];

module.exports = jestConfig;
70 changes: 70 additions & 0 deletions packages/contact-center/ai-assistant/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "@webex/cc-ai-assistant",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the AI assistant spec alongside the module

This introduces a new workspace package/widget, but the change contains no ai-assistant module spec or standing-doc/router update, so the new public surface cannot be routed or validated by the repo's SDD docs. Please add the module spec/standing doc updates in the same change as the code.

AGENTS.md reference: AGENTS.md:L68-L68

Useful? React with 👍 / 👎.

"description": "Webex Contact Center Widgets: AI Assistant",
"license": "Cisco's General Terms (https://www.cisco.com/site/us/en/about/legal/contract-experience/index.html)",
"version": "1.28.0-ccwidgets.126",
"main": "dist/index.js",
"types": "dist/types/index.d.ts",
"publishConfig": {
"access": "public"
},
"files": [
"dist/",
"package.json"
],
"scripts": {
"clean": "rm -rf dist && rm -rf node_modules",
"clean:dist": "rm -rf dist",
"build": "yarn run -T tsc",
"build:src": "yarn run clean:dist && webpack",
"build:watch": "webpack --watch",
"test:unit": "tsc --project tsconfig.test.json && jest --coverage",
"test:styles": "eslint",
"deploy:npm": "yarn npm publish"
},
"dependencies": {
"@webex/cc-components": "workspace:*",
"@webex/cc-store": "workspace:*",
"@webex/cc-ui-logging": "workspace:*",
"mobx-react-lite": "^4.1.0",
"react-error-boundary": "^6.0.0",
"typescript": "5.6.3"
},
"devDependencies": {
"@babel/core": "7.25.2",
"@babel/preset-env": "7.25.4",
"@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.25.9",
"@eslint/js": "^9.20.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.2",
"@testing-library/react": "16.0.1",
"@types/jest": "29.5.14",
"@types/react-test-renderer": "18",
"@webex/test-fixtures": "workspace:*",
"babel-jest": "29.7.0",
"babel-loader": "9.2.1",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.0.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.37.4",
"file-loader": "6.2.0",
"globals": "^16.0.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"prettier": "^3.5.1",
"ts-loader": "9.5.1",
"typescript-eslint": "^8.24.1",
"webpack": "5.94.0",
"webpack-cli": "5.1.4",
"webpack-merge": "6.0.1"
},
"peerDependencies": {
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
}
41 changes: 41 additions & 0 deletions packages/contact-center/ai-assistant/src/ai-assistant.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type {RealTimeAssistPayload} from '@webex/cc-store';

/**
* Public props for the `AIAssistant` widget. All callbacks are optional —
* the host can opt into any subset.
*/
export interface IAIAssistantProps {
/** Fired when the launcher is clicked and the panel opens. */
onOpen?: () => void;
/** Fired when the agent minimizes the panel to its collapsed bar. */
onMinimize?: () => void;
/** Fired when the minimized bar is restored to the full panel. */
onRestore?: () => void;
/** Fired when the panel is closed back to the launcher. */
onClose?: () => void;
/** Fired when the fullscreen affordance is toggled. Host owns layout. */
onFullScreenToggle?: (isFullScreen: boolean) => void;
/** Fired each time a fresh real-time assist response arrives for the active task. */
onRealTimeAssistReceived?: (payload: RealTimeAssistPayload) => void;
/** Optional extra class applied to the widget root. */
className?: string;
}

export interface UseAiAssistantInput extends IAIAssistantProps {
interactionId?: string;
agentId: string;
isFeatureEnabled: boolean;
realTimeAssist: RealTimeAssistPayload[];
}

export type UseAIAssistantChromeInput = Pick<
UseAiAssistantInput,
'onOpen' | 'onMinimize' | 'onRestore' | 'onClose' | 'onFullScreenToggle'
>;

export type UseRealTimeAssistInput = Pick<
UseAiAssistantInput,
'interactionId' | 'agentId' | 'isFeatureEnabled' | 'realTimeAssist' | 'onRealTimeAssistReceived'
>;

export type UserMessage = {id: string; text: string; sentAt: number};
47 changes: 47 additions & 0 deletions packages/contact-center/ai-assistant/src/ai-assistant/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import store from '@webex/cc-store';
import {observer} from 'mobx-react-lite';
import {ErrorBoundary} from 'react-error-boundary';

import {AIAssistantComponent} from '@webex/cc-components';
import {useAiAssistant, REAL_TIME_ASSIST_FLAG} from '../helper';
import {IAIAssistantProps} from '../ai-assistant.types';

const AIAssistantInternal: React.FunctionComponent<IAIAssistantProps> = observer((props) => {
const {currentTask, agentId, agentProfile, featureFlags, realTimeAssist} = store;
const interactionId = currentTask?.data?.interactionId;
const isFeatureEnabled = Boolean(featureFlags?.[REAL_TIME_ASSIST_FLAG]);
const activeRealTimeAssist = interactionId ? realTimeAssist?.[interactionId] || [] : [];

const hookProps = useAiAssistant({
...props,
interactionId,
agentId,
isFeatureEnabled,
realTimeAssist: activeRealTimeAssist,
});

return (
<AIAssistantComponent
{...hookProps}
isFeatureEnabled={isFeatureEnabled}
hasActiveInteraction={Boolean(interactionId)}
agentName={agentProfile?.agentName}
logger={store.logger}
className={props.className}
/>
);
});

const AIAssistant: React.FunctionComponent<IAIAssistantProps> = (props) => (
<ErrorBoundary
fallbackRender={() => <></>}
onError={(error: Error) => {
if (store.onErrorCallback) store.onErrorCallback('AIAssistant', error);
}}
>
<AIAssistantInternal {...props} />
</ErrorBoundary>
);

export {AIAssistant};
Loading
Loading