Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 70 additions & 21 deletions client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { UICompBuilder } from "comps/generators";
import { NameConfig, withExposingConfigs } from "comps/generators/withExposing";
import { StringControl } from "comps/controls/codeControl";
import { arrayObjectExposingStateControl, stringExposingStateControl } from "comps/controls/codeStateControl";
import { JSONObject } from "util/jsonTypes";
import { withDefault } from "comps/generators";
import { BoolControl } from "comps/controls/boolControl";
import { dropdownControl } from "comps/controls/dropdownControl";
import QuerySelectControl from "comps/controls/querySelectControl";
import { eventHandlerControl, EventConfigType } from "comps/controls/eventHandlerControl";
import { AutoHeightControl } from "comps/controls/autoHeightControl";
import { ChatCore } from "./components/ChatCore";
import { ChatPropertyView } from "./chatPropertyView";
import { createChatStorage } from "./utils/storageFactory";
Expand All @@ -17,6 +19,17 @@ import { useMemo, useRef, useEffect } from "react";
import { changeChildAction } from "lowcoder-core";
import { ChatMessage } from "./types/chatTypes";
import { trans } from "i18n";
import { styleControl } from "comps/controls/styleControl";
import {
ChatStyle,
ChatSidebarStyle,
ChatMessagesStyle,
ChatInputStyle,
ChatSendButtonStyle,
ChatNewThreadButtonStyle,
ChatThreadItemStyle,
} from "comps/controls/styleControlConstants";
import { AnimationStyle } from "comps/controls/styleControlConstants";

import "@assistant-ui/styles/index.css";
import "@assistant-ui/styles/markdown.css";
Expand Down Expand Up @@ -147,15 +160,31 @@ export const chatChildrenMap = {
// UI Configuration
placeholder: withDefault(StringControl, trans("chat.defaultPlaceholder")),

// Layout Configuration
autoHeight: AutoHeightControl,
leftPanelWidth: withDefault(StringControl, "250px"),

// Database Information (read-only)
databaseName: withDefault(StringControl, ""),

// Event Handlers
onEvent: ChatEventHandlerControl,

// Style Controls
style: styleControl(ChatStyle),
sidebarStyle: styleControl(ChatSidebarStyle),
messagesStyle: styleControl(ChatMessagesStyle),
inputStyle: styleControl(ChatInputStyle),
sendButtonStyle: styleControl(ChatSendButtonStyle),
newThreadButtonStyle: styleControl(ChatNewThreadButtonStyle),
threadItemStyle: styleControl(ChatThreadItemStyle),
animationStyle: styleControl(AnimationStyle),

// Exposed Variables (not shown in Property View)
currentMessage: stringExposingStateControl("currentMessage", ""),
conversationHistory: stringExposingStateControl("conversationHistory", "[]"),
// Use arrayObjectExposingStateControl for proper Lowcoder pattern
// This exposes: conversationHistory.value, setConversationHistory(), clearConversationHistory(), resetConversationHistory()
conversationHistory: arrayObjectExposingStateControl("conversationHistory", [] as JSONObject[]),
};

// ============================================================================
Expand Down Expand Up @@ -221,30 +250,32 @@ const ChatTmpComp = new UICompBuilder(
]);

// Handle message updates for exposed variable
// Using Lowcoder pattern: props.currentMessage.onChange() instead of dispatch(changeChildAction(...))
const handleMessageUpdate = (message: string) => {
dispatch(changeChildAction("currentMessage", message, false));
props.currentMessage.onChange(message);
// Trigger messageSent event
props.onEvent("messageSent");
};

// Handle conversation history updates for exposed variable
// Handle conversation history updates for exposed variable
const handleConversationUpdate = (conversationHistory: any[]) => {
// Use utility function to create complete history with system prompt
const historyWithSystemPrompt = addSystemPromptToHistory(
conversationHistory,
props.systemPrompt
);

// Expose the complete history (with system prompt) for use in queries
dispatch(changeChildAction("conversationHistory", JSON.stringify(historyWithSystemPrompt), false));

// Trigger messageReceived event when bot responds
const lastMessage = conversationHistory[conversationHistory.length - 1];
if (lastMessage && lastMessage.role === 'assistant') {
props.onEvent("messageReceived");
}
};
// Using Lowcoder pattern: props.conversationHistory.onChange() instead of dispatch(changeChildAction(...))
const handleConversationUpdate = (messages: ChatMessage[]) => {
// Use utility function to create complete history with system prompt
const historyWithSystemPrompt = addSystemPromptToHistory(
messages,
props.systemPrompt
);

// Update using proper Lowcoder pattern - calling onChange on the control
// This properly updates the exposed variable and triggers reactivity
props.conversationHistory.onChange(historyWithSystemPrompt as JSONObject[]);

// Trigger messageReceived event when bot responds
const lastMessage = messages[messages.length - 1];
if (lastMessage && lastMessage.role === 'assistant') {
props.onEvent("messageReceived");
}
};

// Cleanup on unmount
useEffect(() => {
Expand All @@ -261,22 +292,40 @@ const handleConversationUpdate = (conversationHistory: any[]) => {
storage={storage}
messageHandler={messageHandler}
placeholder={props.placeholder}
autoHeight={props.autoHeight}
sidebarWidth={props.leftPanelWidth}
onMessageUpdate={handleMessageUpdate}
onConversationUpdate={handleConversationUpdate}
onEvent={props.onEvent}
style={props.style}
sidebarStyle={props.sidebarStyle}
messagesStyle={props.messagesStyle}
inputStyle={props.inputStyle}
sendButtonStyle={props.sendButtonStyle}
newThreadButtonStyle={props.newThreadButtonStyle}
threadItemStyle={props.threadItemStyle}
animationStyle={props.animationStyle}
/>
);
}
)
.setPropertyViewFn((children) => <ChatPropertyView children={children} />)
.build();

// Override autoHeight to support AUTO/FIXED height mode
const ChatCompWithAutoHeight = class extends ChatTmpComp {
override autoHeight(): boolean {
return this.children.autoHeight.getView();
}
};

// ============================================================================
// EXPORT WITH EXPOSED VARIABLES
// ============================================================================

export const ChatComp = withExposingConfigs(ChatTmpComp, [
export const ChatComp = withExposingConfigs(ChatCompWithAutoHeight, [
new NameConfig("currentMessage", "Current user message"),
new NameConfig("conversationHistory", "Full conversation history as JSON array (includes system prompt for API calls)"),
// conversationHistory is now a proper array (not JSON string) - supports setConversationHistory(), clearConversationHistory(), resetConversationHistory()
new NameConfig("conversationHistory", "Full conversation history array with system prompt (use directly in API calls, no JSON.parse needed)"),
new NameConfig("databaseName", "Database name for SQL queries (ChatDB_<componentName>)"),
]);
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import React, { useMemo } from "react";
import { Section, sectionNames, DocLink } from "lowcoder-design";
import { placeholderPropertyView } from "../../utils/propertyUtils";
import { trans } from "i18n";
import { hiddenPropertyView } from "comps/utils/propertyUtils";

// ============================================================================
// CLEAN PROPERTY VIEW - FOCUSED ON ESSENTIAL CONFIGURATION
Expand Down Expand Up @@ -55,19 +55,28 @@ export const ChatPropertyView = React.memo((props: any) => {
tooltip: trans("chat.systemPromptTooltip"),
})}

{children.streaming.propertyView({
{children.streaming.propertyView({
label: trans("chat.streaming"),
tooltip: trans("chat.streamingTooltip"),
})}
</Section>

{/* UI Configuration */}
<Section name={trans("chat.uiConfiguration")}>
{children.placeholder.propertyView({
label: trans("chat.placeholderLabel"),
placeholder: trans("chat.defaultPlaceholder"),
tooltip: trans("chat.placeholderTooltip"),
})}
{children.placeholder.propertyView({
label: trans("chat.placeholderLabel"),
placeholder: trans("chat.defaultPlaceholder"),
tooltip: trans("chat.placeholderTooltip"),
})}
</Section>

{/* Layout Section - Height Mode & Sidebar Width */}
<Section name={sectionNames.layout}>
{children.autoHeight.getPropertyView()}
{children.leftPanelWidth.propertyView({
label: trans("chat.leftPanelWidth"),
tooltip: trans("chat.leftPanelWidthTooltip"),
})}
</Section>

{/* Database Section */}
Expand All @@ -84,6 +93,39 @@ export const ChatPropertyView = React.memo((props: any) => {
{children.onEvent.getPropertyView()}
</Section>

{/* STYLE SECTIONS */}
<Section name={sectionNames.style}>
{children.style.getPropertyView()}
</Section>

<Section name={trans("chat.sidebarStyle")}>
{children.sidebarStyle.getPropertyView()}
</Section>

<Section name={trans("chat.messagesStyle")}>
{children.messagesStyle.getPropertyView()}
</Section>

<Section name={trans("chat.inputStyle")}>
{children.inputStyle.getPropertyView()}
</Section>

<Section name={trans("chat.sendButtonStyle")}>
{children.sendButtonStyle.getPropertyView()}
</Section>

<Section name={trans("chat.newThreadButtonStyle")}>
{children.newThreadButtonStyle.getPropertyView()}
</Section>

<Section name={trans("chat.threadItemStyle")}>
{children.threadItemStyle.getPropertyView()}
</Section>

<Section name={sectionNames.animationStyle} hasTooltip={true}>
{children.animationStyle.getPropertyView()}
</Section>

</>
), [children]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,39 @@ export function ChatCore({
storage,
messageHandler,
placeholder,
autoHeight,
sidebarWidth,
onMessageUpdate,
onConversationUpdate,
onEvent
onEvent,
style,
sidebarStyle,
messagesStyle,
inputStyle,
sendButtonStyle,
newThreadButtonStyle,
threadItemStyle,
animationStyle
}: ChatCoreProps) {
return (
<TooltipProvider>
<ChatProvider storage={storage}>
<ChatCoreMain
messageHandler={messageHandler}
placeholder={placeholder}
onMessageUpdate={onMessageUpdate}
onConversationUpdate={onConversationUpdate}
placeholder={placeholder}
autoHeight={autoHeight}
sidebarWidth={sidebarWidth}
onMessageUpdate={onMessageUpdate}
onConversationUpdate={onConversationUpdate}
onEvent={onEvent}
style={style}
sidebarStyle={sidebarStyle}
messagesStyle={messagesStyle}
inputStyle={inputStyle}
sendButtonStyle={sendButtonStyle}
newThreadButtonStyle={newThreadButtonStyle}
threadItemStyle={threadItemStyle}
animationStyle={animationStyle}
/>
</ChatProvider>
</TooltipProvider>
Expand Down
Loading
Loading