11import 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";
1921import styles from "./styles.module.css" ;
2022import type { ChatInput } from "../chat-input" ;
2123import 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";
3540import backgroundImage from "../home-container/images/background.png" ;
3641import { DONE_STATES } from "../shared/constants" ;
3742import { 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
3947const WrappedModal = wrapBrick <
4048 Modal ,
@@ -59,6 +67,8 @@ const { defineElement, property, method } = createDecorators();
5967
6068export 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 {
122150interface ChatPanelRef {
123151 open : ( ) => void ;
124152 close : ( ) => void ;
153+ send : ( payload : ChatPayload ) => void ;
154+ showFile : ( file : FileInfo ) => void ;
125155}
126156
127157function 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}
0 commit comments