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>)" ) ,
46
258
] ) ;
0 commit comments