@spektra-cloudevents/application-copilot-widget 1.2.0 → 2.0.0

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/README.md CHANGED
@@ -1,75 +1,126 @@
1
- # @spektra/application-copilot-widget
2
-
3
- A framework-agnostic, **CSS-library agnostic** copilot widget. It renders entirely
4
- inside a **Shadow DOM**, so the host app's styles (Bootstrap, Angular Material,
5
- Tailwind, etc.) cannot affect the widget and the widget's styles cannot leak out.
6
- It works in Angular, React, Vue, or plain HTML.
7
-
8
- ## Build
9
-
10
- ```bash
11
- cd packages/application-copilot-widget
12
- npm install
13
- npm run build # outputs dist/ (ESM + CJS + types) via tsup
14
- ```
15
-
16
- ## Use (Angular example)
17
-
18
- ```ts
19
- import { ApplicationCopilotWidget } from '@spektra/application-copilot-widget';
20
-
21
- ApplicationCopilotWidget.init({
22
- projectKey: 'cloud-events',
23
- environment: 'stage',
24
- apiBaseUrl: environment.applicationCopilotApi,
25
- signalRHubUrl: `${environment.applicationCopilotApi}/hubs/application-copilot`,
26
- tenantUniqueName: this.tenantUniqueName,
27
- getAuthToken: () => this.authService.accessToken, // sent as Authorization: Bearer
28
- // devUserEmail: 'someone@partner.com', // local testing only
29
-
30
- getContext: () => ({
31
- projectKey: 'cloud-events',
32
- environment: 'stage',
33
- page: { pageKey: 'create-update-event', activeTab: this.activeTab, componentHint: 'ScheduleComponent' },
34
- entities: { eventRequestId: this.eventRequestId },
35
- uiState: {
36
- disabledFields: [
37
- ...(this.scheduleForm.get('startDateTime')?.disabled ? ['eventStartDate'] : []),
38
- ...(this.scheduleForm.get('endDateTime')?.disabled ? ['eventEndDate'] : [])
39
- ]
40
- },
41
- // Hint values for the Schedule rule (backend re-verifies the role):
42
- clientSnapshot: {
43
- sectionMode: this.sectionMode,
44
- isDraftMode: this.isDraftMode,
45
- isBudgetStatusClosed: this.isBudgetStatusClosed,
46
- showPendingRegistrationBanner: this.showPendingRegistrationBanner,
47
- eventPriorDays: this.editEventObj?.EventPriorDays,
48
- controlSetting: {
49
- canEdit: this.startDateControlSetting?.CanEdit,
50
- priorDays: this.startDateControlSetting?.PriorDays
51
- }
52
- },
53
- contextVersion: this.contextVersion,
54
- capturedAt: new Date().toISOString()
55
- })
56
- });
57
- ```
58
-
59
- ## Public API
60
-
61
- ```ts
62
- ApplicationCopilotWidget.init(config);
63
- ApplicationCopilotWidget.open();
64
- ApplicationCopilotWidget.ask('Why are the dates disabled?');
65
- ApplicationCopilotWidget.setContext(context);
66
- ApplicationCopilotWidget.captureError(errorContext);
67
- ApplicationCopilotWidget.close();
68
- ```
69
-
70
- ## Why Shadow DOM
71
-
72
- The widget mounts a single host element and attaches `attachShadow({ mode: 'open' })`.
73
- All markup and CSS live inside that shadow root, and `:host { all: initial }` neutralises
74
- inherited styles. The result: pixel-identical rendering regardless of the host's CSS
75
- framework, with zero risk of class-name collisions in either direction.
1
+ # @spektra-cloudevents/application-copilot-widget
2
+
3
+ A framework-agnostic, **CSS-library agnostic** copilot widget. It renders entirely
4
+ inside a **Shadow DOM**, so the host app's styles (Bootstrap, Angular Material,
5
+ Tailwind, etc.) cannot affect the widget and the widget's styles cannot leak out.
6
+ It works in Angular, React, Vue, or plain HTML.
7
+
8
+ ## Breaking changes in 2.0.0
9
+
10
+ - The `environment` field was removed from `WidgetConfig` and `ApplicationContext`, and the
11
+ exported `Environment` type was removed. The Copilot API no longer uses an environment
12
+ parameter; each deployment has its own configuration.
13
+ - Migration: delete `environment` from the `init()` config and from the object returned by
14
+ `getContext()`, remove `devUserEmail`, and provide `getAuthToken` (see below).
15
+ - `devUserEmail` was removed and `getAuthToken` is now **required**: the Copilot API identifies the user only by
16
+ the validated bearer token (no `X-User-Email` header). The same token is used for the SignalR hub.
17
+ - Sessions are opened for `tenantUniqueName` and bound to the signed-in user; the API verifies tenant membership,
18
+ Copilot enablement and role on every request.
19
+ - Requires the Copilot API from Story 9453 B2 (token validation). Release the API before hosts upgrade to 2.x.
20
+
21
+ ## Additions in 2.0.0 (Story 9453 B4, all optional / backwards compatible)
22
+
23
+ - **Knowledge-base references**: `CopilotAnswer.references: { title, url }[]` are shown under the answer as links.
24
+ Only absolute `https:` URLs are rendered (others are dropped); links are built with DOM APIs and open with
25
+ `target="_blank" rel="noopener noreferrer"`. Answer text is escaped and keeps line breaks (`white-space: pre-wrap`).
26
+ - **Support-ticket offer**: when an answer has `supportTicketEligible: true` the widget shows `supportTicketPrompt`
27
+ and a "Raise a support ticket" action. Accepting fetches the server draft
28
+ (`GET /api/sessions/{id}/support-ticket/draft?requestId=`), shows an editable subject/description, and Send posts
29
+ `POST /api/sessions/{id}/support-ticket`; the response `message` is shown as confirmation. If the draft endpoint
30
+ is unavailable the widget falls back to a locally built draft. Older servers that do not send
31
+ `supportTicketEligible` keep the previous client-side heuristic.
32
+ - **"This didn't help"**: shown under answers; resends the last question with `notHelpful: true`.
33
+ - **Sessions**: new `resetSession()`. The widget also resets automatically when `init()` is called with a different
34
+ `tenantUniqueName`, when the configured tenant or the signed-in user (read from the token's `oid`/`sub`/email claim
35
+ for change detection only — never trusted) differs from the one the session was opened for, and when the API
36
+ answers **403 or 404** for a session-scoped call. A reset clears the conversation.
37
+ - **`onOpenSupport?: () => void`**: when set, an "Open support" action appears with the ticket offer and confirmation.
38
+ The widget closes its panel first (synchronously) and then calls the callback, so the host can open its own
39
+ support UI (e.g. Zoho ASAP for the current tenant). `onOpenTicket` also closes the panel before it is called.
40
+ - **Responsive**: below 480px the panel is full width; it is never taller than `calc(100vh - 32px)`; long text wraps
41
+ so there is no horizontal scroll at 375 / 768 / 1440px.
42
+ - **`showKnowledgeUpload?: boolean`** (default `false`): the "Add to knowledge base" button in the panel header is
43
+ hidden unless the host sets this, e.g. only for users who may manage the knowledge base (CloudEvents Admin: System
44
+ Admin and Partner Admin). Re-applied on every `init()`. UI only: the Copilot API authorizes every upload.
45
+ **Behaviour change:** `promptUpload()` only opens the file picker when `showKnowledgeUpload` is true; otherwise it
46
+ just opens the panel.
47
+ - New exported types: `CopilotReference`, `SupportTicketDraft`, `SupportTicketResult`.
48
+
49
+ ## Build
50
+
51
+ ```bash
52
+ npm install
53
+ npm run build # outputs dist/ (ESM + CJS + types) via tsup
54
+ npx tsc --noEmit # type-check only
55
+ ```
56
+
57
+ ## Use (Angular example)
58
+
59
+ ```ts
60
+ import { ApplicationCopilotWidget } from '@spektra-cloudevents/application-copilot-widget';
61
+
62
+ ApplicationCopilotWidget.init({
63
+ projectKey: 'cloud-events',
64
+ apiBaseUrl: copilotApiUrl,
65
+ signalRHubUrl: `${copilotApiUrl}/hubs/application-copilot`,
66
+ tenantUniqueName: this.tenantUniqueName,
67
+ getAuthToken: () => this.currentBearerToken(), // required: sent as Authorization: Bearer (and to SignalR)
68
+
69
+ getContext: () => ({
70
+ projectKey: 'cloud-events',
71
+ page: { pageKey: 'create-update-event', activeTab: this.activeTab, componentHint: 'ScheduleComponent' },
72
+ entities: { eventRequestId: this.eventRequestId },
73
+ uiState: {
74
+ disabledFields: [
75
+ ...(this.scheduleForm.get('startDateTime')?.disabled ? ['eventStartDate'] : []),
76
+ ...(this.scheduleForm.get('endDateTime')?.disabled ? ['eventEndDate'] : [])
77
+ ]
78
+ },
79
+ // Hints only. The backend re-verifies anything security-relevant (role, visibility).
80
+ clientSnapshot: {
81
+ sectionMode: this.sectionMode,
82
+ isBudgetStatusClosed: this.isBudgetStatusClosed
83
+ },
84
+ contextVersion: this.contextVersion,
85
+ capturedAt: new Date().toISOString()
86
+ }),
87
+
88
+ // Optional host hooks
89
+ onNavigate: (path) => this.router.navigateByUrl(path),
90
+ onOpenTicket: (ticket) => this.openSupportTicket(ticket),
91
+ onOpenSupport: () => this.supportWidget.open(), // widget closes its panel before calling this
92
+ showKnowledgeUpload: this.canManageKnowledgeBase // optional, default false: KB upload button only for KB admins
93
+ });
94
+
95
+ // On tenant switch / sign-out:
96
+ ApplicationCopilotWidget.resetSession();
97
+ ```
98
+
99
+ ## Public API
100
+
101
+ ```ts
102
+ ApplicationCopilotWidget.init(config); // mount once with a WidgetConfig
103
+ ApplicationCopilotWidget.open();
104
+ ApplicationCopilotWidget.close();
105
+ ApplicationCopilotWidget.show(); // show the launcher (e.g. after login / tenant check)
106
+ ApplicationCopilotWidget.hide(); // hide launcher and panel
107
+ ApplicationCopilotWidget.ask('Why are the dates disabled?');
108
+ ApplicationCopilotWidget.setContext(context);
109
+ ApplicationCopilotWidget.captureError(errorContext);
110
+ ApplicationCopilotWidget.notify(text, suggestion?); // show a proactive message, optional suggested question
111
+ ApplicationCopilotWidget.promptUpload(); // open the KB upload picker (only with showKnowledgeUpload; server enforces admin rights)
112
+ ApplicationCopilotWidget.resetSession(); // drop the session + conversation; next question opens a new session
113
+ ```
114
+
115
+ ## Backend endpoints used
116
+
117
+ `POST /api/sessions`, `POST /api/sessions/{id}/messages` (optional `notHelpful: true`), `POST /api/sessions/{id}/intake/ask`,
118
+ `GET /api/sessions/{id}/support-ticket/draft?requestId=`, `POST /api/sessions/{id}/support-ticket`,
119
+ `POST /api/knowledge/upload`, and the optional SignalR hub.
120
+
121
+ ## Why Shadow DOM
122
+
123
+ The widget mounts a single host element and attaches `attachShadow({ mode: 'open' })`.
124
+ All markup and CSS live inside that shadow root, and `:host { all: initial }` neutralises
125
+ inherited styles. The result: pixel-identical rendering regardless of the host's CSS
126
+ framework, with zero risk of class-name collisions in either direction.