Skip to content

Commit 0a8cb01

Browse files
authored
feat: allow multiple workspaces management (carbon-design-system#898)
1 parent 4c77766 commit 0a8cb01

File tree

18 files changed

+1226
-58
lines changed

18 files changed

+1226
-58
lines changed

demo/src/customSendMessage/doPreviewCard.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
*/
99

1010
import { ChatInstance, MessageResponseTypes } from "@carbon/ai-chat";
11+
import { v4 as uuid } from "uuid";
1112

1213
function doPreviewCard(
1314
instance: ChatInstance,
1415
preferredLocation?: "start" | "end",
1516
) {
17+
const workspaceId = uuid();
18+
1619
instance.messaging.addMessage({
1720
output: {
1821
generic: [
@@ -22,13 +25,13 @@ function doPreviewCard(
2225
},
2326
{
2427
title: "Optimizing excess inventory",
25-
subtitle: `Created on: ${new Date().toLocaleDateString()}`,
28+
subtitle: `Workspace id ${workspaceId} Created on: ${new Date().toLocaleDateString()}`,
2629
response_type: MessageResponseTypes.PREVIEW_CARD,
2730
workspace_options: {
2831
preferredLocation,
2932
},
33+
workspace_id: workspaceId,
3034
additional_data: {
31-
id: "some unique ID for the workspace",
3235
data: "some additional data for the workspace",
3336
},
3437
},

demo/src/react/DemoApp.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@
3939

4040
.sidebar--closing {
4141
right: calc(calc(320px + 1rem) * -1);
42+
width: 320px;
4243
}
4344

4445
.sidebar--closed {
4546
right: calc(calc(320px + 1rem) * -1);
47+
width: 320px;
4648
visibility: hidden;
4749
}
4850

@@ -78,8 +80,14 @@
7880
transition: left 100ms, width 300ms cubic-bezier(0.2, 0, 0.38, 0.9), visibility 0s 100ms;
7981
}
8082

83+
[dir="rtl"] .sidebar--closing {
84+
left: calc(calc(320px + 1rem) * -1);
85+
width: 320px;
86+
}
87+
8188
[dir="rtl"] .sidebar--closed {
8289
right: auto;
8390
left: calc(calc(320px + 1rem) * -1);
91+
width: 320px;
8492
transition: left 100ms, visibility 0s 0s;
8593
}

demo/src/react/WorkspaceWriteableElementExample.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ function WorkspaceWriteableElementExample({
4545
instance,
4646
parentStateText,
4747
}: WorkspaceExampleProps) {
48+
const workspaceId = instance?.getState()?.workspace?.workspaceID || "unknown";
49+
4850
const handleClose = () => {
4951
panel?.close();
5052
};
@@ -159,7 +161,7 @@ function WorkspaceWriteableElementExample({
159161
hideCloseButton
160162
/>
161163
<WorkspaceShellHeader
162-
titleText="Optimizing excess inventory plan"
164+
titleText={`Optimizing excess inventory plan (ID: ${workspaceId.substring(0, 8)}...)`}
163165
subTitleText={`Created on: ${new Date().toLocaleDateString()}`}
164166
>
165167
<div slot="header-description">

demo/src/web-components/demo-app.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,24 @@ export class DemoApp extends LitElement {
105105
106106
.sidebar--closing {
107107
right: calc(calc(320px + 1rem) * -1);
108+
width: 320px;
108109
}
109110
110111
.sidebar--closed {
111112
right: calc(calc(320px + 1rem) * -1);
113+
width: 320px;
112114
visibility: hidden;
113115
}
114116
115117
/* RTL support */
118+
[dir="rtl"] .sidebar {
119+
right: auto;
120+
left: 0;
121+
transition:
122+
left 100ms,
123+
visibility 0s 100ms;
124+
}
125+
116126
[dir="rtl"] .sidebar--expanded {
117127
left: 0;
118128
right: auto;
@@ -131,6 +141,20 @@ export class DemoApp extends LitElement {
131141
width 300ms cubic-bezier(0.2, 0, 0.38, 0.9),
132142
visibility 0s 100ms;
133143
}
144+
145+
[dir="rtl"] .sidebar--closing {
146+
left: calc(calc(320px + 1rem) * -1);
147+
width: 320px;
148+
}
149+
150+
[dir="rtl"] .sidebar--closed {
151+
right: auto;
152+
left: calc(calc(320px + 1rem) * -1);
153+
width: 320px;
154+
transition:
155+
left 100ms,
156+
visibility 0s 0s;
157+
}
134158
`;
135159

136160
@property({ type: Object })

demo/src/web-components/workspace-writeable-element-example.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ class WorkspaceWriteableElementExample extends LitElement {
136136
}
137137

138138
render() {
139+
const workspaceId =
140+
this.instance?.getState()?.workspace?.workspaceID || "unknown";
141+
139142
return html` <cds-aichat-workspace-shell>
140143
<cds-aichat-toolbar
141144
slot="toolbar"
@@ -163,7 +166,7 @@ class WorkspaceWriteableElementExample extends LitElement {
163166
>
164167
</cds-inline-notification>
165168
<cds-aichat-workspace-shell-header
166-
title-text="Optimizing excess inventory plan"
169+
title-text=${`Optimizing excess inventory plan (ID: ${workspaceId.substring(0, 8)}...)`}
167170
subtitle-text=${`Created on: ${new Date().toLocaleDateString()}`}
168171
>
169172
<div slot="header-description">

packages/ai-chat/src/chat/components-legacy/responseTypes/previewCard/PreviewCardComponent.tsx

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@
88
* @license
99
*/
1010

11-
import React from "react";
11+
import React, { useCallback, useMemo } from "react";
1212
import { Card, CardFooter } from "@carbon/ai-chat-components/es/react/card.js";
1313
import Maximize16 from "@carbon/icons/es/maximize/16.js";
1414
import View16 from "@carbon/icons/es/view/16.js";
1515

16-
// import { HasRequestFocus } from "../../../../types/utilities/HasRequestFocus";
1716
import { useServiceManager } from "../../../hooks/useServiceManager";
18-
import { BusEventType } from "../../../../types/events/eventBusTypes";
1917
import { PanelType } from "../../../../types/instance/apiTypes";
2018
import { LocalMessageItem } from "../../../../types/messaging/LocalMessageItem";
2119
import { AppState } from "../../../../types/state/AppState";
@@ -24,6 +22,7 @@ import {
2422
PreviewCardItem,
2523
MessageResponse,
2624
} from "../../../../types/messaging/Messages";
25+
import actions from "../../../store/actions";
2726

2827
interface PreviewCardComponentProps {
2928
localMessageItem: LocalMessageItem;
@@ -34,65 +33,74 @@ interface PreviewCardComponentProps {
3433
* This component renders the preview card response type. which triggers the workflow.
3534
*/
3635
function PreviewCardComponent(props: PreviewCardComponentProps) {
37-
const item = props.localMessageItem.item as PreviewCardItem;
36+
const { localMessageItem, fullMessage } = props;
37+
const item = localMessageItem.item as PreviewCardItem;
38+
const { title, subtitle, workspace_id, workspace_options, additional_data } =
39+
item;
40+
3841
const serviceManager = useServiceManager();
39-
const isWorkspaceOpen = useSelector(
40-
(state: AppState) => state.workspacePanelState.isOpen,
42+
const { isOpen, workspaceID } = useSelector(
43+
(state: AppState) => state.workspacePanelState,
4144
);
4245
const panel = serviceManager.instance.customPanels.getPanel(
4346
PanelType.WORKSPACE,
4447
);
4548

46-
const handleClick = () => {
47-
if (!isWorkspaceOpen) {
48-
serviceManager.eventBus.fire(
49-
{
50-
type: BusEventType.WORKSPACE_PRE_OPEN,
51-
data: {
52-
message: props.localMessageItem,
53-
fullMessage: props.fullMessage,
54-
},
55-
additional_data: item.additional_data,
56-
},
57-
serviceManager.instance,
58-
);
59-
panel.open(item.workspace_options);
60-
serviceManager.eventBus.fire(
61-
{
62-
type: BusEventType.WORKSPACE_OPEN,
63-
data: {
64-
message: props.localMessageItem,
65-
fullMessage: props.fullMessage,
66-
},
67-
additional_data: item.additional_data,
68-
},
69-
serviceManager.instance,
70-
);
71-
}
72-
};
49+
const isViewing = isOpen && workspaceID === workspace_id;
50+
51+
const handleClick = useCallback(() => {
52+
// Store workspace panel data in Redux before opening
53+
serviceManager.store.dispatch(
54+
actions.setWorkspacePanelData({
55+
workspaceID: workspace_id,
56+
localMessageItem,
57+
fullMessage,
58+
additionalData: additional_data,
59+
}),
60+
);
61+
62+
// Open the panel - it will fire WORKSPACE_PRE_OPEN and WORKSPACE_OPEN events
63+
// If a workspace is already open, it will be closed first (handled in CustomPanelInstance)
64+
panel.open({
65+
...workspace_options,
66+
workspaceId: workspace_id,
67+
additionalData: additional_data,
68+
});
69+
}, [
70+
workspace_id,
71+
workspace_options,
72+
additional_data,
73+
localMessageItem,
74+
fullMessage,
75+
serviceManager.store,
76+
panel,
77+
]);
78+
79+
const footerActions = useMemo(
80+
() => [
81+
{
82+
icon: isViewing ? View16 : Maximize16,
83+
id: "docs",
84+
kind: "ghost",
85+
label: isViewing ? "Viewing" : "View details",
86+
payload: { test: "value" },
87+
isViewing,
88+
},
89+
],
90+
[isViewing],
91+
);
7392

7493
return (
7594
<Card
7695
data-rounded
7796
class="cds-aichat-preview-card cds-aichat-preview-card__sm"
7897
>
7998
<div slot="body">
80-
<h5 className="cds-aichat-preview-card--title">{item.title}</h5>
81-
<p className="cds-aichat-preview-card--subtitle">{item.subtitle}</p>
99+
<h5 className="cds-aichat-preview-card--title">{title}</h5>
100+
<p className="cds-aichat-preview-card--subtitle">{subtitle}</p>
82101
</div>
83102
<CardFooter
84-
actions={[
85-
{
86-
icon: isWorkspaceOpen ? View16 : Maximize16,
87-
id: "docs",
88-
kind: "ghost",
89-
label: isWorkspaceOpen ? "Viewing" : "View details",
90-
payload: {
91-
test: "value",
92-
},
93-
isViewing: isWorkspaceOpen,
94-
},
95-
]}
103+
actions={footerActions}
96104
onFooterAction={handleClick}
97105
size="md"
98106
/>

packages/ai-chat/src/chat/services/ChatActionsImpl.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,9 +340,20 @@ class ChatActionsImpl {
340340
preferredLocation:
341341
state.workspacePanelState.options.preferredLocation,
342342
},
343+
workspaceID: state.workspacePanelState.workspaceID,
344+
additionalData: state.workspacePanelState.additionalData,
343345
},
344346
});
345347

348+
const workspace = deepFreeze({
349+
isOpen: Boolean(state.workspacePanelState.isOpen),
350+
options: {
351+
preferredLocation: state.workspacePanelState.options.preferredLocation,
352+
},
353+
workspaceID: state.workspacePanelState.workspaceID,
354+
additionalData: state.workspacePanelState.additionalData,
355+
});
356+
346357
return deepFreeze({
347358
...rest,
348359
humanAgent,
@@ -352,6 +363,7 @@ class ChatActionsImpl {
352363
activeResponseId: assistantMessageState.activeResponseId ?? null,
353364
input,
354365
customPanels,
366+
workspace,
355367
});
356368
}
357369

0 commit comments

Comments
 (0)