Skip to content

Commit d384d65

Browse files
authored
Merge pull request #455 from easyops-cn/steve/service-flow
Steve/service-flow
2 parents f14fd2d + 392e4f9 commit d384d65

File tree

14 files changed

+170
-123
lines changed

14 files changed

+170
-123
lines changed

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

Lines changed: 62 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import React, {
66
useState,
77
useImperativeHandle,
88
createRef,
9-
useMemo,
109
} from "react";
1110
import { createPortal } from "react-dom";
1211
import { createDecorators, type EventEmitter } from "@next-core/element";
@@ -25,6 +24,7 @@ import styleText from "./styles.shadow.css";
2524
import {
2625
getChatCommand,
2726
setChatCommand,
27+
type ChatCommand,
2828
} from "../data-providers/set-chat-command.js";
2929
import type {
3030
ChatPayload,
@@ -40,6 +40,7 @@ import UploadedFilesStyleText from "../shared/FileUpload/UploadedFiles.shadow.cs
4040
import { WrappedActions, WrappedIcon } from "./bricks.js";
4141
import { useFilesUploading } from "../shared/useFilesUploading.js";
4242
import GlobalDragOverlay from "../shared/FileUpload/GlobalDragOverlay.js";
43+
import { getInitialContent } from "../shared/ReadableCommand/ReadableCommand.js";
4344

4445
initializeI18n(NS, locales);
4546

@@ -132,13 +133,6 @@ class ChatBox extends ReactNextElement implements ChatBoxProps {
132133
this.#chatSubmit.emit(payload);
133134
};
134135

135-
@event({ type: "command.select" })
136-
accessor #commandSelect!: EventEmitter<CommandPayload | null>;
137-
138-
#handleCommandSelect = (command: CommandPayload | null) => {
139-
this.#commandSelect.emit(command);
140-
};
141-
142136
@method()
143137
setValue(value: string) {
144138
this.ref.current?.setValue(value);
@@ -165,7 +159,6 @@ class ChatBox extends ReactNextElement implements ChatBoxProps {
165159
uploadOptions={this.uploadOptions}
166160
onMessageSubmit={this.#handleMessageSubmit}
167161
onChatSubmit={this.#handleChatSubmit}
168-
onCommandSelect={this.#handleCommandSelect}
169162
root={this}
170163
ref={this.ref}
171164
/>
@@ -177,7 +170,6 @@ interface ChatBoxComponentProps extends ChatBoxProps {
177170
root: HTMLElement;
178171
onMessageSubmit: (value: string) => void;
179172
onChatSubmit: (payload: ChatPayload) => void;
180-
onCommandSelect: (command: CommandPayload | null) => void;
181173
ref?: React.Ref<ChatBoxRef>;
182174
}
183175

@@ -201,7 +193,6 @@ function LegacyChatBoxComponent(
201193
uploadOptions,
202194
onMessageSubmit,
203195
onChatSubmit,
204-
onCommandSelect,
205196
}: ChatBoxComponentProps,
206197
ref: React.Ref<ChatBoxRef>
207198
) {
@@ -254,6 +245,18 @@ function LegacyChatBoxComponent(
254245
}
255246
}, [uploadEnabled, resetFiles]);
256247

248+
const [initialMention, setInitialMention] = useState<string | null>(null);
249+
const [initialCommand, setInitialCommand] = useState<ChatCommand | null>(
250+
null
251+
);
252+
useEffect(() => {
253+
const command = getChatCommand();
254+
if (command) {
255+
setChatCommand(null);
256+
}
257+
setInitialCommand(command);
258+
}, []);
259+
257260
useEffect(() => {
258261
const store = window.__elevo_try_it_out;
259262
if (store) {
@@ -262,6 +265,14 @@ function LegacyChatBoxComponent(
262265
if (typeof store?.content === "string") {
263266
valueRef.current = store.content;
264267
setValue(store.content);
268+
if (store?.cmd) {
269+
setInitialCommand({
270+
command: getInitialContent(store.cmd).slice(1),
271+
payload: store.cmd,
272+
});
273+
} else if (store?.mentionedAiEmployeeId) {
274+
setInitialMention(store.mentionedAiEmployeeId);
275+
}
265276
}
266277
}, []);
267278

@@ -473,12 +484,11 @@ function LegacyChatBoxComponent(
473484
) {
474485
setCommandText("");
475486
setCommand(null);
476-
onCommandSelect(null);
477487
setCommands(propCommands);
478488
setCommandPrefix("/");
479489
setCommandPopover(null);
480490
}
481-
}, [commandPrefix, commandText, onCommandSelect, value, propCommands]);
491+
}, [commandPrefix, commandText, value, propCommands]);
482492

483493
const handleSubmitClick = useCallback(() => {
484494
doSubmit(valueRef.current);
@@ -503,20 +513,7 @@ function LegacyChatBoxComponent(
503513
setMentionOverlay(null);
504514
return;
505515
}
506-
const rects = getContentRectsInTextarea(
507-
element,
508-
"",
509-
// Ignore the last space
510-
mentionedText.slice(0, -1)
511-
);
512-
setMentionOverlay(
513-
rects.map((rect) => ({
514-
left: rect.left - 1,
515-
top: rect.top - 1,
516-
width: rect.width + 4,
517-
height: rect.height + 4,
518-
}))
519-
);
516+
setMentionOverlay(getOverlayRects(element, mentionedText));
520517
}, [mentionedText, hasFiles]);
521518

522519
useEffect(() => {
@@ -525,55 +522,15 @@ function LegacyChatBoxComponent(
525522
setCommandOverlay(null);
526523
return;
527524
}
528-
const rects = getContentRectsInTextarea(
529-
element,
530-
"",
531-
// Ignore the last space
532-
commandText.slice(0, -1)
533-
);
534-
setCommandOverlay(
535-
rects.map((rect) => ({
536-
left: rect.left - 1,
537-
top: rect.top - 1,
538-
width: rect.width + 4,
539-
height: rect.height + 4,
540-
}))
541-
);
525+
setCommandOverlay(getOverlayRects(element, commandText));
542526
}, [commandText, hasFiles]);
543527

544-
const chatCommand = useMemo(() => {
545-
const command = getChatCommand();
546-
if (command) {
547-
setChatCommand(null);
548-
}
549-
return command;
550-
}, []);
551-
552-
useEffect(() => {
553-
if (chatCommand) {
554-
setCommand(chatCommand.payload);
555-
onCommandSelect(chatCommand.payload);
556-
}
557-
}, [chatCommand, onCommandSelect]);
558-
559528
useEffect(() => {
560529
const element = textareaRef.current?.element;
561-
if (chatCommand && element) {
562-
const commandStr = `/${chatCommand.command} `;
563-
const rects = getContentRectsInTextarea(
564-
element,
565-
"",
566-
// Ignore the last space
567-
commandStr.slice(0, -1)
568-
);
569-
setCommandOverlay(
570-
rects.map((rect) => ({
571-
left: rect.left - 1,
572-
top: rect.top - 1,
573-
width: rect.width + 4,
574-
height: rect.height + 4,
575-
}))
576-
);
530+
if (initialCommand && element) {
531+
setCommand(initialCommand.payload);
532+
const commandStr = `/${initialCommand.command} `;
533+
setCommandOverlay(getOverlayRects(element, commandStr));
577534
valueRef.current = commandStr;
578535
selectionRef.current = {
579536
start: commandStr.length,
@@ -582,7 +539,23 @@ function LegacyChatBoxComponent(
582539
setValue(commandStr);
583540
setCommandText(commandStr);
584541
}
585-
}, [chatCommand]);
542+
}, [initialCommand]);
543+
544+
useEffect(() => {
545+
const element = textareaRef.current?.element;
546+
if (initialMention && element) {
547+
setMentioned(initialMention);
548+
const mentionStr = `${getInitialContent(undefined, initialMention)} `;
549+
setMentionOverlay(getOverlayRects(element, mentionStr));
550+
valueRef.current = mentionStr;
551+
selectionRef.current = {
552+
start: mentionStr.length,
553+
end: mentionStr.length,
554+
};
555+
setValue(mentionStr);
556+
setMentionedText(mentionStr);
557+
}
558+
}, [initialMention]);
586559

587560
const handleMention = useCallback(
588561
(action: SimpleAction) => {
@@ -632,11 +605,10 @@ function LegacyChatBoxComponent(
632605
setCommand(action.payload!);
633606
setCommandPopover(null);
634607
setCommandPrefix("/");
635-
onCommandSelect(action.payload!);
636608
}
637609
textareaRef.current?.focus();
638610
},
639-
[commandPopover, commandPrefix, onCommandSelect]
611+
[commandPopover, commandPrefix]
640612
);
641613

642614
const handleKeyDown = useCallback(
@@ -882,6 +854,21 @@ function getActiveActionKeys(
882854
return [meaningfulActions[index].key!];
883855
}
884856

857+
function getOverlayRects(element: HTMLTextAreaElement, content: string) {
858+
const rects = getContentRectsInTextarea(
859+
element,
860+
"",
861+
// Ignore the last space
862+
content.slice(0, -1)
863+
);
864+
return rects.map((rect) => ({
865+
left: rect.left - 1,
866+
top: rect.top - 1,
867+
width: rect.width + 4,
868+
height: rect.height + 4,
869+
}));
870+
}
871+
885872
function getCommandPopover(
886873
commands: Command[],
887874
textarea: HTMLTextAreaElement,

bricks/ai-portal/src/chat-stream/ChatStream.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,8 @@ export function ChatStreamComponent(
220220
[humanInputRef]
221221
);
222222

223-
const requirementMessage = messages[0];
224-
const userInput = useMemo(() => {
225-
if (requirementMessage?.role === "user") {
226-
return requirementMessage.content;
227-
}
228-
}, [requirementMessage]);
223+
const firstMessage = messages[0];
224+
const userMessage = firstMessage?.role === "user" ? firstMessage : null;
229225

230226
const [activeImages, setActiveImages] = useState<ActiveImages | null>(null);
231227

@@ -272,9 +268,13 @@ export function ChatStreamComponent(
272268
"_blank"
273269
);
274270
if (win) {
275-
win.__elevo_try_it_out = {
276-
content: userInput,
277-
};
271+
win.__elevo_try_it_out = userMessage
272+
? {
273+
content: userMessage.content,
274+
cmd: userMessage.cmd,
275+
mentionedAiEmployeeId: userMessage.mentionedAiEmployeeId,
276+
}
277+
: {};
278278
}
279279
},
280280
activeFile,
@@ -312,7 +312,7 @@ export function ChatStreamComponent(
312312

313313
skipToResults,
314314
watchAgain,
315-
userInput,
315+
userMessage,
316316
tryItOutUrl,
317317
activeFile,
318318
activeImages,

bricks/ai-portal/src/chat-stream/useConversationStream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function useConversationStream(
5252
for (const chunk of chunks) {
5353
if (chunk.type === "job") {
5454
const job = chunk.job;
55-
if (job.toolCall) {
55+
if (job.toolCall && !job.ignoreDetails) {
5656
lastDetail = {
5757
type: "job",
5858
id: job.id,

bricks/ai-portal/src/cruise-canvas/CruiseCanvas.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -610,13 +610,8 @@ export function CruiseCanvasComponent(
610610
});
611611
}, []);
612612

613-
const requirementNode = rawNodes?.[0];
614-
615-
const userInput = useMemo(() => {
616-
if (requirementNode?.type === "requirement") {
617-
return requirementNode.content;
618-
}
619-
}, [requirementNode]);
613+
const firstNode = rawNodes?.[0];
614+
const requirementNode = firstNode?.type === "requirement" ? firstNode : null;
620615

621616
const [activeImages, setActiveImages] = useState<ActiveImages | null>(null);
622617

@@ -672,9 +667,13 @@ export function CruiseCanvasComponent(
672667
"_blank"
673668
);
674669
if (win) {
675-
win.__elevo_try_it_out = {
676-
content: userInput,
677-
};
670+
win.__elevo_try_it_out = requirementNode
671+
? {
672+
content: requirementNode.content,
673+
cmd: requirementNode.cmd,
674+
mentionedAiEmployeeId: requirementNode.mentionedAiEmployeeId,
675+
}
676+
: {};
678677
}
679678
},
680679
separateInstructions,
@@ -719,7 +718,7 @@ export function CruiseCanvasComponent(
719718
skipToResults,
720719
watchAgain,
721720
setCentered,
722-
userInput,
721+
requirementNode,
723722
tryItOutUrl,
724723
separateInstructions,
725724
activeFile,

bricks/ai-portal/src/data-providers/set-chat-command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createProviderClass } from "@next-core/utils/general";
22
import type { CommandPayload } from "../shared/interfaces.js";
33

44
export interface ChatCommand {
5-
command: string;
5+
command?: string;
66
payload: CommandPayload;
77
}
88

bricks/ai-portal/src/shared/ReadableCommand/ReadableCommand.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function ReadableCommand({
4646
);
4747
}
4848

49-
function getInitialContent(
49+
export function getInitialContent(
5050
cmd?: CommandPayload,
5151
mentionedAiEmployeeId?: string
5252
): string {

bricks/ai-portal/src/shared/interfaces.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ export interface Job {
116116

117117
// @ 的数字人 ID
118118
mentionedAiEmployeeId?: string;
119+
120+
// 忽略该容器在 chat 模式下的自动详情展示,但仍可主动点击查看详情
121+
ignoreDetails?: boolean;
119122
}
120123

121124
export type HumanAction = HumanActionConfirm | HumanActionSelect;

bricks/form/src/form-item/index.spec.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe("eo-form-item", () => {
4040

4141
expect(mockCurElement.$bindFormItem).toBeTruthy();
4242

43-
expect(mockFormStore.subscribe).toHaveBeenCalledTimes(5);
43+
expect(mockFormStore.subscribe).toHaveBeenCalledTimes(6);
4444

4545
expect(mockFormStore.setFieldsValueByInitData).toHaveBeenCalledWith("test");
4646

@@ -66,14 +66,14 @@ describe("eo-form-item", () => {
6666
},
6767
});
6868

69-
expect(mockFormStore.subscribe).toHaveBeenCalledTimes(5);
69+
expect(mockFormStore.subscribe).toHaveBeenCalledTimes(6);
7070

7171
act(() => {
7272
document.body.removeChild(element);
7373
});
7474

7575
expect(mockFormStore.removeField).toHaveBeenCalledTimes(1);
76-
expect(mockFormStore.unsubscribe).toHaveBeenCalledTimes(5);
76+
expect(mockFormStore.unsubscribe).toHaveBeenCalledTimes(6);
7777
expect(element.shadowRoot?.childNodes.length).toBe(0);
7878
});
7979
});

0 commit comments

Comments
 (0)