Skip to content

Commit 1de8ae6

Browse files
committed
add unique storage / expose convo history
1 parent 2925949 commit 1de8ae6

File tree

7 files changed

+91
-32
lines changed

7 files changed

+91
-32
lines changed

client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { UICompBuilder } from "comps/generators";
44
import { NameConfig, withExposingConfigs } from "comps/generators/withExposing";
55
import { StringControl } from "comps/controls/codeControl";
6-
import { stringExposingStateControl } from "comps/controls/codeStateControl";
6+
import { arrayObjectExposingStateControl, stringExposingStateControl } from "comps/controls/codeStateControl";
77
import { withDefault } from "comps/generators";
88
import { BoolControl } from "comps/controls/boolControl";
99
import { dropdownControl } from "comps/controls/dropdownControl";
@@ -12,7 +12,7 @@ import { ChatCore } from "./components/ChatCore";
1212
import { ChatPropertyView } from "./chatPropertyView";
1313
import { createChatStorage } from "./utils/storageFactory";
1414
import { QueryHandler, createMessageHandler } from "./handlers/messageHandlers";
15-
import { useMemo } from "react";
15+
import { useMemo, useRef, useEffect } from "react";
1616
import { changeChildAction } from "lowcoder-core";
1717

1818
import "@assistant-ui/styles/index.css";
@@ -22,15 +22,19 @@ import "@assistant-ui/styles/markdown.css";
2222
// SIMPLIFIED CHILDREN MAP - ONLY ESSENTIAL PROPS
2323
// ============================================================================
2424

25+
function generateUniqueTableName(): string {
26+
return `chat${Math.floor(1000 + Math.random() * 9000)}`;
27+
}
28+
2529
const ModelTypeOptions = [
2630
{ label: "Query", value: "query" },
2731
{ label: "N8N Workflow", value: "n8n" },
2832
] as const;
2933

3034
export const chatChildrenMap = {
3135
// Storage
32-
tableName: withDefault(StringControl, "default"),
33-
36+
// Storage (add the hidden property here)
37+
_internalDbName: withDefault(StringControl, ""),
3438
// Message Handler Configuration
3539
handlerType: dropdownControl(ModelTypeOptions, "query"),
3640
chatQuery: QuerySelectControl, // Only used for "query" type
@@ -41,8 +45,12 @@ export const chatChildrenMap = {
4145
// UI Configuration
4246
placeholder: withDefault(StringControl, "Chat Component"),
4347

48+
// Database Information (read-only)
49+
databaseName: withDefault(StringControl, ""),
50+
4451
// Exposed Variables (not shown in Property View)
4552
currentMessage: stringExposingStateControl("currentMessage", ""),
53+
conversationHistory: stringExposingStateControl("conversationHistory", "[]"),
4654
};
4755

4856
// ============================================================================
@@ -52,10 +60,27 @@ export const chatChildrenMap = {
5260
const ChatTmpComp = new UICompBuilder(
5361
chatChildrenMap,
5462
(props, dispatch) => {
55-
// Create storage from tableName
56-
const storage = useMemo(() =>
57-
createChatStorage(props.tableName),
58-
[props.tableName]
63+
64+
const uniqueTableName = useRef<string>();
65+
66+
// Generate unique table name once (with persistence)
67+
if (!uniqueTableName.current) {
68+
// Use persisted name if exists, otherwise generate new one
69+
uniqueTableName.current = props._internalDbName || generateUniqueTableName();
70+
71+
// Save the name for future refreshes
72+
if (!props._internalDbName) {
73+
dispatch(changeChildAction("_internalDbName", uniqueTableName.current, false));
74+
}
75+
76+
// Update the database name in the props for display
77+
const dbName = `ChatDB_${uniqueTableName.current}`;
78+
dispatch(changeChildAction("databaseName", dbName, false));
79+
}
80+
// Create storage with unique table name
81+
const storage = useMemo(() =>
82+
createChatStorage(uniqueTableName.current!),
83+
[]
5984
);
6085

6186
// Create message handler based on type
@@ -96,11 +121,35 @@ const ChatTmpComp = new UICompBuilder(
96121
dispatch(changeChildAction("currentMessage", message, false));
97122
};
98123

124+
// Handle conversation history updates for exposed variable
125+
const handleConversationUpdate = (conversationHistory: any[]) => {
126+
// Format conversation history for use in queries
127+
const formattedHistory = conversationHistory.map(msg => ({
128+
role: msg.role,
129+
content: msg.text,
130+
timestamp: msg.timestamp
131+
}));
132+
dispatch(changeChildAction("conversationHistory", JSON.stringify(formattedHistory), false));
133+
};
134+
135+
// Cleanup on unmount
136+
useEffect(() => {
137+
console.log("cleanup on unmount");
138+
return () => {
139+
console.log("cleanup on unmount");
140+
const tableName = uniqueTableName.current;
141+
if (tableName) {
142+
storage.cleanup();
143+
}
144+
};
145+
}, []);
146+
99147
return (
100148
<ChatCore
101149
storage={storage}
102150
messageHandler={messageHandler}
103151
onMessageUpdate={handleMessageUpdate}
152+
onConversationUpdate={handleConversationUpdate}
104153
/>
105154
);
106155
}
@@ -114,4 +163,5 @@ const ChatTmpComp = new UICompBuilder(
114163

115164
export const ChatComp = withExposingConfigs(ChatTmpComp, [
116165
new NameConfig("currentMessage", "Current user message"),
166+
new NameConfig("conversationHistory", "Full conversation history as JSON array"),
117167
]);

client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ export const ChatPropertyView = React.memo((props: any) => {
1919
placeholder: "Enter placeholder text..."
2020
})}
2121

22-
{children.tableName.propertyView({
23-
label: "Storage Table",
24-
placeholder: "default",
25-
tooltip: "Storage identifier - use same value to share conversations between components"
22+
{children.databaseName.propertyView({
23+
label: "Database Name",
24+
placeholder: "Database will be auto-generated...",
25+
tooltip: "Read-only: Auto-generated database name for data persistence. You can reference this in queries if needed.",
26+
disabled: true
2627
})}
28+
2729
</Section>
2830

2931
{/* Message Handler Configuration */}

client/packages/lowcoder/src/comps/comps/chatComp/components/ChatCore.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import { ChatCoreProps } from "../types/chatTypes";
99
// CHAT CORE - THE SHARED FOUNDATION
1010
// ============================================================================
1111

12-
export function ChatCore({ storage, messageHandler, onMessageUpdate }: ChatCoreProps) {
12+
export function ChatCore({ storage, messageHandler, onMessageUpdate, onConversationUpdate }: ChatCoreProps) {
1313
return (
1414
<ChatProvider storage={storage}>
1515
<ChatCoreMain
1616
messageHandler={messageHandler}
1717
onMessageUpdate={onMessageUpdate}
18+
onConversationUpdate={onConversationUpdate}
1819
/>
1920
</ChatProvider>
2021
);

client/packages/lowcoder/src/comps/comps/chatComp/components/ChatCoreMain.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,12 @@ const ChatContainer = styled.div`
6060
interface ChatCoreMainProps {
6161
messageHandler: MessageHandler;
6262
onMessageUpdate?: (message: string) => void;
63+
onConversationUpdate?: (conversationHistory: ChatMessage[]) => void;
6364
}
6465

6566
const generateId = () => Math.random().toString(36).substr(2, 9);
6667

67-
export function ChatCoreMain({ messageHandler, onMessageUpdate }: ChatCoreMainProps) {
68+
export function ChatCoreMain({ messageHandler, onMessageUpdate, onConversationUpdate }: ChatCoreMainProps) {
6869
const { state, actions } = useChatContext();
6970
const [isRunning, setIsRunning] = useState(false);
7071

@@ -73,6 +74,14 @@ export function ChatCoreMain({ messageHandler, onMessageUpdate }: ChatCoreMainPr
7374
// Get messages for current thread
7475
const currentMessages = actions.getCurrentMessages();
7576

77+
78+
console.log("CURRENT MESSAGES", currentMessages);
79+
80+
// Notify parent component of conversation changes
81+
React.useEffect(() => {
82+
onConversationUpdate?.(currentMessages);
83+
}, [currentMessages]);
84+
7685
// Convert custom format to ThreadMessageLike (same as your current implementation)
7786
const convertMessage = (message: ChatMessage): ThreadMessageLike => ({
7887
role: message.role,
@@ -106,6 +115,8 @@ export function ChatCoreMain({ messageHandler, onMessageUpdate }: ChatCoreMainPr
106115
try {
107116
// Use the message handler (no more complex logic here!)
108117
const response = await messageHandler.sendMessage(userMessage.text);
118+
119+
console.log("AI RESPONSE", response);
109120

110121
const assistantMessage: ChatMessage = {
111122
id: generateId(),

client/packages/lowcoder/src/comps/comps/chatComp/handlers/messageHandlers.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,7 @@ export class QueryHandler implements MessageHandler {
7575
)
7676
);
7777

78-
// Extract reply text from the query result (same logic as your current implementation)
79-
let content: string;
80-
if (typeof result === "string") {
81-
content = result;
82-
} else if (result && typeof result === "object") {
83-
content =
84-
(result as any).response ??
85-
(result as any).message ??
86-
(result as any).content ??
87-
JSON.stringify(result);
88-
} else {
89-
content = String(result);
90-
}
91-
92-
return { content };
78+
return result.message
9379
} catch (e: any) {
9480
throw new Error(e?.message || "Query execution failed");
9581
}

client/packages/lowcoder/src/comps/comps/chatComp/types/chatTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface ChatMessage {
3535
deleteMessages(threadId: string): Promise<void>;
3636
clearAllData(): Promise<void>;
3737
resetDatabase(): Promise<void>;
38+
cleanup(): Promise<void>;
3839
}
3940

4041
// ============================================================================
@@ -75,6 +76,7 @@ export interface ChatMessage {
7576
storage: ChatStorage;
7677
messageHandler: MessageHandler;
7778
onMessageUpdate?: (message: string) => void;
79+
onConversationUpdate?: (conversationHistory: ChatMessage[]) => void;
7880
}
7981

8082
export interface ChatPanelProps {

client/packages/lowcoder/src/comps/comps/chatComp/utils/storageFactory.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@ import { ChatMessage, ChatThread, ChatStorage } from "../types/chatTypes";
99

1010
export function createChatStorage(tableName: string): ChatStorage {
1111
const dbName = `ChatDB_${tableName}`;
12-
const threadsTable = `${tableName}_threads`;
13-
const messagesTable = `${tableName}_messages`;
12+
const threadsTable = `${dbName}.${tableName}_threads`;
13+
const messagesTable = `${dbName}.${tableName}_messages`;
1414

1515
return {
1616
async initialize() {
1717
try {
1818
// Create database with localStorage backend
1919
await alasql.promise(`CREATE LOCALSTORAGE DATABASE IF NOT EXISTS ${dbName}`);
2020
await alasql.promise(`ATTACH LOCALSTORAGE DATABASE ${dbName}`);
21-
await alasql.promise(`USE ${dbName}`);
2221

2322
// Create threads table
2423
await alasql.promise(`
@@ -176,6 +175,14 @@ export function createChatStorage(tableName: string): ChatStorage {
176175
console.error("Failed to reset database:", error);
177176
throw error;
178177
}
178+
},
179+
async cleanup() {
180+
try {
181+
await alasql.promise(`DROP LOCALSTORAGE DATABASE IF EXISTS ${dbName}`);
182+
} catch (error) {
183+
console.error("Failed to cleanup database:", error);
184+
throw error;
185+
}
179186
}
180187
};
181188
}

0 commit comments

Comments
 (0)