Skip to content

Commit 33d91ab

Browse files
authored
Merge pull request #476 from easyops-cn/steve/sidebar
Steve/sidebar
2 parents 607d3b3 + ebd5895 commit 33d91ab

File tree

13 files changed

+243
-137
lines changed

13 files changed

+243
-137
lines changed

bricks/advanced/src/jsx.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ declare global {
6060
HTMLAttributes<EoNextTable>,
6161
EoNextTable
6262
> &
63-
Omit<NextTableProps, "columns"> & {
63+
Omit<NextTableProps, "columns" | "cell"> & {
6464
columns?: Array<
6565
Omit<ColumnProp, "useBrick" | "headerBrick"> & {
6666
align?: string;
@@ -82,6 +82,19 @@ declare global {
8282
};
8383
}
8484
>;
85+
cell?: {
86+
render?: (data: {
87+
rowData: Record<string, any>;
88+
cellData: any;
89+
columnKey: string | number;
90+
}) => React.ReactNode;
91+
header?: {
92+
render?: (data: {
93+
title: string;
94+
columnKey: string | number;
95+
}) => React.ReactNode;
96+
};
97+
};
8598
onPageChange?: (
8699
event: CustomEvent<{ page: number; pageSize: number }>
87100
) => void;

bricks/ai-portal/src/chat-panel/index.tsx

Lines changed: 168 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import React, {
22
createRef,
33
forwardRef,
4+
useCallback,
45
useEffect,
56
useImperativeHandle,
7+
useMemo,
68
useRef,
79
useState,
810
} from "react";
@@ -19,7 +21,10 @@ import { http } from "@next-core/http";
1921
import styles from "./styles.module.css";
2022
import type { ChatInput } from "../chat-input";
2123
import type {
24+
ActiveImages,
2225
ChatPayload,
26+
CommandPayload,
27+
FileInfo,
2328
RequestStore,
2429
UploadOptions,
2530
} from "../shared/interfaces";
@@ -35,6 +40,9 @@ import floatingStyles from "../shared/FloatingButton.module.css";
3540
import backgroundImage from "../home-container/images/background.png";
3641
import { DONE_STATES } from "../shared/constants";
3742
import { WrappedChatInput, WrappedIcon } from "../shared/bricks";
43+
import { FilePreview } from "../shared/FilePreview/FilePreview.js";
44+
import { ImagesPreview } from "../shared/FilePreview/ImagesPreview.js";
45+
import { TaskContext, type TaskContextValue } from "../shared/TaskContext";
3846

3947
const WrappedModal = wrapBrick<
4048
Modal,
@@ -59,6 +67,8 @@ const { defineElement, property, method } = createDecorators();
5967

6068
export interface ChatPanelProps {
6169
panelTitle?: string;
70+
aiEmployeeId?: string;
71+
cmd?: CommandPayload;
6272
width?: string | number;
6373
height?: string | number;
6474
placeholder?: string;
@@ -78,6 +88,12 @@ class ChatPanel extends ReactNextElement implements ChatPanelProps {
7888
@property()
7989
accessor panelTitle: string | undefined;
8090

91+
@property()
92+
accessor aiEmployeeId: string | undefined;
93+
94+
@property({ attribute: false })
95+
accessor cmd: CommandPayload | undefined;
96+
8197
@property({ attribute: false }) accessor width: string | number | undefined;
8298

8399
@property({ attribute: false }) accessor height: string | number | undefined;
@@ -100,11 +116,23 @@ class ChatPanel extends ReactNextElement implements ChatPanelProps {
100116
this.#ref.current?.close();
101117
}
102118

119+
@method()
120+
send(payload: ChatPayload) {
121+
this.#ref.current?.send(payload);
122+
}
123+
124+
@method()
125+
showFile(file: FileInfo) {
126+
this.#ref.current?.showFile(file);
127+
}
128+
103129
render() {
104130
return (
105131
<ChatPanelComponent
106132
ref={this.#ref}
107133
panelTitle={this.panelTitle}
134+
aiEmployeeId={this.aiEmployeeId}
135+
cmd={this.cmd}
108136
width={this.width}
109137
height={this.height}
110138
placeholder={this.placeholder}
@@ -122,11 +150,15 @@ interface ChatPanelComponentProps extends ChatPanelProps {
122150
interface ChatPanelRef {
123151
open: () => void;
124152
close: () => void;
153+
send: (payload: ChatPayload) => void;
154+
showFile: (file: FileInfo) => void;
125155
}
126156

127157
function LegacyChatPanelComponent(
128158
{
129159
panelTitle,
160+
aiEmployeeId,
161+
cmd,
130162
width,
131163
height,
132164
placeholder,
@@ -137,15 +169,6 @@ function LegacyChatPanelComponent(
137169
const modalRef = useRef<Modal>(null);
138170
const inputRef = useRef<ChatInput>(null);
139171

140-
useImperativeHandle(ref, () => ({
141-
open: () => {
142-
modalRef.current?.open();
143-
},
144-
close: () => {
145-
modalRef.current?.close();
146-
},
147-
}));
148-
149172
const [submitDisabled, setSubmitDisabled] = useState(false);
150173

151174
const [conversationId, setConversationId] = useState<string | null>(null);
@@ -198,105 +221,148 @@ function LegacyChatPanelComponent(
198221
scrollContentRef
199222
);
200223

201-
const handleChatSubmit = async (e: CustomEvent<ChatPayload>) => {
202-
if (conversationId) {
203-
const { content, ...extra } = e.detail;
204-
humanInputRef.current?.(e.detail.content, undefined, extra);
205-
return;
206-
}
207-
setSubmitDisabled(true);
208-
try {
209-
const res = await http.post<{
210-
data: { conversationId: string };
211-
}>("api/gateway/logic.llm.aiops_service/api/v1/elevo/conversations", {});
212-
const conversationId = res.data.conversationId;
213-
setConversationId(conversationId);
214-
setInitialRequest({
215-
...e.detail,
216-
conversationId: conversationId,
217-
});
218-
} catch (e) {
219-
handleHttpError(e);
220-
} finally {
221-
setSubmitDisabled(false);
222-
}
223-
};
224+
const handleChatSubmit = useCallback(
225+
async (payload: ChatPayload) => {
226+
if (conversationId) {
227+
const { content, ...extra } = payload;
228+
humanInputRef.current?.(content, undefined, {
229+
...extra,
230+
...(aiEmployeeId ? { aiEmployeeId } : null),
231+
...(cmd ? { cmd } : null),
232+
});
233+
return;
234+
}
235+
setSubmitDisabled(true);
236+
try {
237+
const res = await http.post<{
238+
data: { conversationId: string };
239+
}>(
240+
"api/gateway/logic.llm.aiops_service/api/v1/elevo/conversations",
241+
{}
242+
);
243+
const conversationId = res.data.conversationId;
244+
setConversationId(conversationId);
245+
setInitialRequest({
246+
...payload,
247+
...(aiEmployeeId ? { aiEmployeeId } : null),
248+
...(cmd ? { cmd } : null),
249+
conversationId: conversationId,
250+
});
251+
} catch (e) {
252+
handleHttpError(e);
253+
} finally {
254+
setSubmitDisabled(false);
255+
}
256+
},
257+
[aiEmployeeId, cmd, conversationId, humanInputRef]
258+
);
259+
260+
const [activeFile, setActiveFile] = useState<FileInfo | null>(null);
261+
const [activeImages, setActiveImages] = useState<ActiveImages | null>(null);
262+
263+
const taskContextValue = useMemo(
264+
() =>
265+
({
266+
setActiveFile,
267+
setActiveImages,
268+
}) as TaskContextValue,
269+
[]
270+
);
271+
272+
useImperativeHandle(
273+
ref,
274+
() => ({
275+
open: () => {
276+
modalRef.current?.open();
277+
},
278+
close: () => {
279+
modalRef.current?.close();
280+
},
281+
send: (payload: ChatPayload) => {
282+
handleChatSubmit(payload);
283+
},
284+
showFile: (file: FileInfo) => {
285+
setActiveFile(file);
286+
},
287+
}),
288+
[handleChatSubmit]
289+
);
224290

225291
return (
226-
<WrappedModal
227-
modalTitle={panelTitle}
228-
width={width}
229-
height={height}
230-
themeVariant="elevo"
231-
maskClosable
232-
noFooter
233-
headerBordered
234-
fullscreenButton
235-
background={`fixed url(${backgroundImage}) center center / cover no-repeat`}
236-
onOpen={() => {
237-
setTimeout(() => {
238-
inputRef.current?.focus();
239-
}, 100);
240-
}}
241-
ref={modalRef}
242-
>
243-
<div className={styles.panel}>
244-
{!conversationId ? (
245-
<div className={styles.main} />
246-
) : conversationAvailable && depsReady ? (
247-
<div className={styles.main}>
248-
<div className={styles.chat} ref={scrollContainerRef}>
249-
<div className={styles.messages} ref={scrollContentRef}>
250-
{messages.map((msg, index, list) => (
251-
<div className={styles.message} key={index}>
252-
{msg.role === "user" ? (
253-
<UserMessage
254-
content={msg.content}
255-
cmd={msg.cmd}
256-
files={msg.files}
257-
/>
258-
) : (
259-
<AssistantMessage
260-
chunks={msg.chunks}
261-
scopeState={conversation.state}
262-
isLatest={index === list.length - 1}
263-
/>
264-
)}
265-
</div>
266-
))}
292+
<TaskContext.Provider value={taskContextValue}>
293+
<WrappedModal
294+
modalTitle={panelTitle}
295+
width={width}
296+
height={height}
297+
themeVariant="elevo"
298+
maskClosable
299+
noFooter
300+
headerBordered
301+
fullscreenButton
302+
background={`fixed url(${backgroundImage}) center center / cover no-repeat`}
303+
onOpen={() => {
304+
setTimeout(() => {
305+
inputRef.current?.focus();
306+
}, 100);
307+
}}
308+
ref={modalRef}
309+
>
310+
<div className={styles.panel}>
311+
{!conversationId ? (
312+
<div className={styles.main} />
313+
) : conversationAvailable && depsReady ? (
314+
<div className={styles.main}>
315+
<div className={styles.chat} ref={scrollContainerRef}>
316+
<div className={styles.messages} ref={scrollContentRef}>
317+
{messages.map((msg, index, list) => (
318+
<div className={styles.message} key={index}>
319+
{msg.role === "user" ? (
320+
<UserMessage content={msg.content} files={msg.files} />
321+
) : (
322+
<AssistantMessage
323+
chunks={msg.chunks}
324+
scopeState={conversation.state}
325+
isLatest={index === list.length - 1}
326+
/>
327+
)}
328+
</div>
329+
))}
330+
</div>
267331
</div>
332+
<button
333+
className={`${scrollStyles["scroll-down"]} ${floatingStyles["floating-button"]}`}
334+
style={{ bottom: "30px" }}
335+
hidden={!scrollable}
336+
onClick={scrollToBottom}
337+
>
338+
<WrappedIcon lib="antd" icon="down" />
339+
</button>
268340
</div>
269-
<button
270-
className={`${scrollStyles["scroll-down"]} ${floatingStyles["floating-button"]}`}
271-
style={{ bottom: "30px" }}
272-
hidden={!scrollable}
273-
onClick={scrollToBottom}
274-
>
275-
<WrappedIcon lib="antd" icon="down" />
276-
</button>
277-
</div>
278-
) : (
279-
<div className={styles["loading-icon"]}>
280-
<WrappedIcon
281-
lib="antd"
282-
theme="outlined"
283-
icon="loading-3-quarters"
284-
spinning
341+
) : (
342+
<div className={styles["loading-icon"]}>
343+
<WrappedIcon
344+
lib="antd"
345+
theme="outlined"
346+
icon="loading-3-quarters"
347+
spinning
348+
/>
349+
</div>
350+
)}
351+
<div className={styles.input}>
352+
<WrappedChatInput
353+
ref={inputRef}
354+
placeholder={placeholder}
355+
suggestionsPlacement="top"
356+
submitDisabled={submitDisabled || !canChat}
357+
supportsTerminate
358+
uploadOptions={uploadOptions}
359+
onChatSubmit={(e) => handleChatSubmit(e.detail)}
285360
/>
286361
</div>
287-
)}
288-
<div className={styles.input}>
289-
<WrappedChatInput
290-
ref={inputRef}
291-
placeholder={placeholder}
292-
suggestionsPlacement="top"
293-
submitDisabled={submitDisabled || !canChat}
294-
supportsTerminate
295-
uploadOptions={uploadOptions}
296-
onChatSubmit={handleChatSubmit}
297-
/>
298362
</div>
299-
</div>
300-
</WrappedModal>
363+
</WrappedModal>
364+
{activeFile && <FilePreview file={activeFile} fromModal />}
365+
{activeImages && <ImagesPreview images={activeImages} fromModal />}
366+
</TaskContext.Provider>
301367
);
302368
}

bricks/ai-portal/src/chat-stream/Aside/FlowApp/FlowApp.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ function ActivityDetail({ activity }: ActivityDetailProps) {
151151
{msg.role === "user" ? (
152152
<UserMessage
153153
content={msg.content}
154+
mentionedAiEmployeeId={msg.mentionedAiEmployeeId}
154155
cmd={msg.cmd}
155156
files={msg.files}
156157
/>

0 commit comments

Comments
 (0)