1- // client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx
2- import { UICompBuilder } from "comps/generators" ;
3- import { NameConfig , withExposingConfigs } from "comps/generators/withExposing" ;
4- import { chatChildrenMap } from "./chatCompTypes" ;
5- import { ChatView } from "./chatView" ;
6- import { ChatPropertyView } from "./chatPropertyView" ;
7- import { useEffect , useState } from "react" ;
8- import { changeChildAction } from "lowcoder-core" ;
9-
10- // Build the component
11- let ChatTmpComp = new UICompBuilder (
12- chatChildrenMap ,
13- ( props , dispatch ) => {
14- useEffect ( ( ) => {
15- if ( Boolean ( props . tableName ) ) return ;
16-
17- // Generate a unique database name for this ChatApp instance
18- const generateUniqueTableName = ( ) => {
19- const timestamp = Date . now ( ) ;
20- const randomId = Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) ;
21- return `TABLE_${ timestamp } ` ;
22- } ;
23-
24- const tableName = generateUniqueTableName ( ) ;
25- dispatch ( changeChildAction ( 'tableName' , tableName , true ) ) ;
26- } , [ props . tableName ] ) ;
27-
28- if ( ! props . tableName ) {
29- return null ; // Don't render until we have a unique DB name
30- }
31- return < ChatView { ...props } chatQuery = { props . chatQuery . value } /> ;
32- }
33- )
34- . setPropertyViewFn ( ( children ) => < ChatPropertyView children = { children } /> )
35- . build ( ) ;
36-
37- ChatTmpComp = class extends ChatTmpComp {
38- override autoHeight ( ) : boolean {
39- return this . children . autoHeight . getView ( ) ;
40- }
41- } ;
42-
43- // Export the component
44- export const ChatComp = withExposingConfigs ( ChatTmpComp , [
45- new NameConfig ( "text" , "Chat component text" ) ,
1+ // client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx
2+
3+ import { UICompBuilder } from "comps/generators" ;
4+ import { NameConfig , withExposingConfigs } from "comps/generators/withExposing" ;
5+ import { StringControl } from "comps/controls/codeControl" ;
6+ import { arrayObjectExposingStateControl , stringExposingStateControl } from "comps/controls/codeStateControl" ;
7+ import { withDefault } from "comps/generators" ;
8+ import { BoolControl } from "comps/controls/boolControl" ;
9+ import { dropdownControl } from "comps/controls/dropdownControl" ;
10+ import QuerySelectControl from "comps/controls/querySelectControl" ;
11+ import { eventHandlerControl , EventConfigType } from "comps/controls/eventHandlerControl" ;
12+ import { ChatCore } from "./components/ChatCore" ;
13+ import { ChatPropertyView } from "./chatPropertyView" ;
14+ import { createChatStorage } from "./utils/storageFactory" ;
15+ import { QueryHandler , createMessageHandler } from "./handlers/messageHandlers" ;
16+ import { useMemo , useRef , useEffect } from "react" ;
17+ import { changeChildAction } from "lowcoder-core" ;
18+ import { ChatMessage } from "./types/chatTypes" ;
19+ import { trans } from "i18n" ;
20+
21+ import "@assistant-ui/styles/index.css" ;
22+ import "@assistant-ui/styles/markdown.css" ;
23+
24+ // ============================================================================
25+ // CHAT-SPECIFIC EVENTS
26+ // ============================================================================
27+
28+ export const componentLoadEvent : EventConfigType = {
29+ label : trans ( "chat.componentLoad" ) ,
30+ value : "componentLoad" ,
31+ description : trans ( "chat.componentLoadDesc" ) ,
32+ } ;
33+
34+ export const messageSentEvent : EventConfigType = {
35+ label : trans ( "chat.messageSent" ) ,
36+ value : "messageSent" ,
37+ description : trans ( "chat.messageSentDesc" ) ,
38+ } ;
39+
40+ export const messageReceivedEvent : EventConfigType = {
41+ label : trans ( "chat.messageReceived" ) ,
42+ value : "messageReceived" ,
43+ description : trans ( "chat.messageReceivedDesc" ) ,
44+ } ;
45+
46+ export const threadCreatedEvent : EventConfigType = {
47+ label : trans ( "chat.threadCreated" ) ,
48+ value : "threadCreated" ,
49+ description : trans ( "chat.threadCreatedDesc" ) ,
50+ } ;
51+
52+ export const threadUpdatedEvent : EventConfigType = {
53+ label : trans ( "chat.threadUpdated" ) ,
54+ value : "threadUpdated" ,
55+ description : trans ( "chat.threadUpdatedDesc" ) ,
56+ } ;
57+
58+ export const threadDeletedEvent : EventConfigType = {
59+ label : trans ( "chat.threadDeleted" ) ,
60+ value : "threadDeleted" ,
61+ description : trans ( "chat.threadDeletedDesc" ) ,
62+ } ;
63+
64+ const ChatEventOptions = [
65+ componentLoadEvent ,
66+ messageSentEvent ,
67+ messageReceivedEvent ,
68+ threadCreatedEvent ,
69+ threadUpdatedEvent ,
70+ threadDeletedEvent ,
71+ ] as const ;
72+
73+ export const ChatEventHandlerControl = eventHandlerControl ( ChatEventOptions ) ;
74+
75+ // ============================================================================
76+ // SIMPLIFIED CHILDREN MAP - WITH EVENT HANDLERS
77+ // ============================================================================
78+
79+
80+ export function addSystemPromptToHistory (
81+ conversationHistory : ChatMessage [ ] ,
82+ systemPrompt : string
83+ ) : Array < { role : string ; content : string ; timestamp : number } > {
84+ // Format conversation history for use in queries
85+ const formattedHistory = conversationHistory . map ( msg => ( {
86+ role : msg . role ,
87+ content : msg . text ,
88+ timestamp : msg . timestamp
89+ } ) ) ;
90+
91+ // Create system message (always exists since we have default)
92+ const systemMessage = [ {
93+ role : "system" as const ,
94+ content : systemPrompt ,
95+ timestamp : Date . now ( ) - 1000000 // Ensure it's always first chronologically
96+ } ] ;
97+
98+ // Return complete history with system prompt prepended
99+ return [ ...systemMessage , ...formattedHistory ] ;
100+ }
101+
102+
103+ function generateUniqueTableName ( ) : string {
104+ return `chat${ Math . floor ( 1000 + Math . random ( ) * 9000 ) } ` ;
105+ }
106+
107+ const ModelTypeOptions = [
108+ { label : trans ( "chat.handlerTypeQuery" ) , value : "query" } ,
109+ { label : trans ( "chat.handlerTypeN8N" ) , value : "n8n" } ,
110+ ] as const ;
111+
112+ export const chatChildrenMap = {
113+ // Storage
114+ // Storage (add the hidden property here)
115+ _internalDbName : withDefault ( StringControl , "" ) ,
116+ // Message Handler Configuration
117+ handlerType : dropdownControl ( ModelTypeOptions , "query" ) ,
118+ chatQuery : QuerySelectControl , // Only used for "query" type
119+ modelHost : withDefault ( StringControl , "" ) , // Only used for "n8n" type
120+ systemPrompt : withDefault ( StringControl , trans ( "chat.defaultSystemPrompt" ) ) ,
121+ streaming : BoolControl . DEFAULT_TRUE ,
122+
123+ // UI Configuration
124+ placeholder : withDefault ( StringControl , trans ( "chat.defaultPlaceholder" ) ) ,
125+
126+ // Database Information (read-only)
127+ databaseName : withDefault ( StringControl , "" ) ,
128+
129+ // Event Handlers
130+ onEvent : ChatEventHandlerControl ,
131+
132+ // Exposed Variables (not shown in Property View)
133+ currentMessage : stringExposingStateControl ( "currentMessage" , "" ) ,
134+ conversationHistory : stringExposingStateControl ( "conversationHistory" , "[]" ) ,
135+ } ;
136+
137+ // ============================================================================
138+ // CLEAN CHATCOMP - USES NEW ARCHITECTURE
139+ // ============================================================================
140+
141+ const ChatTmpComp = new UICompBuilder (
142+ chatChildrenMap ,
143+ ( props , dispatch ) => {
144+
145+ const uniqueTableName = useRef < string > ( ) ;
146+ // Generate unique table name once (with persistence)
147+ if ( ! uniqueTableName . current ) {
148+ // Use persisted name if exists, otherwise generate new one
149+ uniqueTableName . current = props . _internalDbName || generateUniqueTableName ( ) ;
150+
151+ // Save the name for future refreshes
152+ if ( ! props . _internalDbName ) {
153+ dispatch ( changeChildAction ( "_internalDbName" , uniqueTableName . current , false ) ) ;
154+ }
155+
156+ // Update the database name in the props for display
157+ const dbName = `ChatDB_${ uniqueTableName . current } ` ;
158+ dispatch ( changeChildAction ( "databaseName" , dbName , false ) ) ;
159+ }
160+ // Create storage with unique table name
161+ const storage = useMemo ( ( ) =>
162+ createChatStorage ( uniqueTableName . current ! ) ,
163+ [ ]
164+ ) ;
165+
166+ // Create message handler based on type
167+ const messageHandler = useMemo ( ( ) => {
168+ const handlerType = props . handlerType ;
169+
170+ if ( handlerType === "query" ) {
171+ return new QueryHandler ( {
172+ chatQuery : props . chatQuery . value ,
173+ dispatch,
174+ streaming : props . streaming ,
175+ } ) ;
176+ } else if ( handlerType === "n8n" ) {
177+ return createMessageHandler ( "n8n" , {
178+ modelHost : props . modelHost ,
179+ systemPrompt : props . systemPrompt ,
180+ streaming : props . streaming
181+ } ) ;
182+ } else {
183+ // Fallback to mock handler
184+ return createMessageHandler ( "mock" , {
185+ chatQuery : props . chatQuery . value ,
186+ dispatch,
187+ streaming : props . streaming
188+ } ) ;
189+ }
190+ } , [
191+ props . handlerType ,
192+ props . chatQuery ,
193+ props . modelHost ,
194+ props . systemPrompt ,
195+ props . streaming ,
196+ dispatch ,
197+ ] ) ;
198+
199+ // Handle message updates for exposed variable
200+ const handleMessageUpdate = ( message : string ) => {
201+ dispatch ( changeChildAction ( "currentMessage" , message , false ) ) ;
202+ // Trigger messageSent event
203+ props . onEvent ( "messageSent" ) ;
204+ } ;
205+
206+ // Handle conversation history updates for exposed variable
207+ // Handle conversation history updates for exposed variable
208+ const handleConversationUpdate = ( conversationHistory : any [ ] ) => {
209+ // Use utility function to create complete history with system prompt
210+ const historyWithSystemPrompt = addSystemPromptToHistory (
211+ conversationHistory ,
212+ props . systemPrompt
213+ ) ;
214+
215+ // Expose the complete history (with system prompt) for use in queries
216+ dispatch ( changeChildAction ( "conversationHistory" , JSON . stringify ( historyWithSystemPrompt ) , false ) ) ;
217+
218+ // Trigger messageReceived event when bot responds
219+ const lastMessage = conversationHistory [ conversationHistory . length - 1 ] ;
220+ if ( lastMessage && lastMessage . role === 'assistant' ) {
221+ props . onEvent ( "messageReceived" ) ;
222+ }
223+ } ;
224+
225+ // Cleanup on unmount
226+ useEffect ( ( ) => {
227+ return ( ) => {
228+ const tableName = uniqueTableName . current ;
229+ if ( tableName ) {
230+ storage . cleanup ( ) ;
231+ }
232+ } ;
233+ } , [ ] ) ;
234+
235+ return (
236+ < ChatCore
237+ storage = { storage }
238+ messageHandler = { messageHandler }
239+ placeholder = { props . placeholder }
240+ onMessageUpdate = { handleMessageUpdate }
241+ onConversationUpdate = { handleConversationUpdate }
242+ onEvent = { props . onEvent }
243+ />
244+ ) ;
245+ }
246+ )
247+ . setPropertyViewFn ( ( children ) => < ChatPropertyView children = { children } /> )
248+ . build ( ) ;
249+
250+ // ============================================================================
251+ // EXPORT WITH EXPOSED VARIABLES
252+ // ============================================================================
253+
254+ export const ChatComp = withExposingConfigs ( ChatTmpComp , [
255+ new NameConfig ( "currentMessage" , "Current user message" ) ,
256+ new NameConfig ( "conversationHistory" , "Full conversation history as JSON array (includes system prompt for API calls)" ) ,
257+ new NameConfig ( "databaseName" , "Database name for SQL queries (ChatDB_<componentName>)" ) ,
46258] ) ;
0 commit comments