From d5d987307842fc6d3b91801c43b8c8c7b099c184 Mon Sep 17 00:00:00 2001 From: FARAN Date: Mon, 30 Jun 2025 21:08:59 +0500 Subject: [PATCH 01/18] init chat component --- .../lowcoder/src/comps/comps/chatComp.tsx | 50 +++++++++++++++++++ client/packages/lowcoder/src/comps/index.tsx | 14 ++++++ .../lowcoder/src/comps/uiCompRegistry.ts | 1 + .../src/pages/editor/editorConstants.tsx | 1 + 4 files changed, 66 insertions(+) create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp.tsx diff --git a/client/packages/lowcoder/src/comps/comps/chatComp.tsx b/client/packages/lowcoder/src/comps/comps/chatComp.tsx new file mode 100644 index 0000000000..cf12c941b6 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp.tsx @@ -0,0 +1,50 @@ +import { StringControl } from "comps/controls/codeControl"; +import { UICompBuilder, withDefault } from "comps/generators"; +import { NameConfig, withExposingConfigs } from "comps/generators/withExposing"; +import { Section, sectionNames } from "lowcoder-design"; +import { trans } from "i18n"; +import React from "react"; + +// Simple children map with just basic properties +const childrenMap = { + text: withDefault(StringControl, "Chat Component Placeholder"), +}; + +// Basic view - just a simple div for now +const ChatView = React.memo((props: any) => { + return ( +
+ {props.text} +
+ ); +}); + +// Basic property view +const ChatPropertyView = React.memo((props: any) => { + return ( +
+ {props.children.text.propertyView({ + label: "Text", + })} +
+ ); +}); + +// Build the component +const ChatTmpComp = new UICompBuilder(childrenMap, (props) => ( + +)) + .setPropertyViewFn((children) => ) + .build(); + +// Export the component +export const ChatComp = withExposingConfigs(ChatTmpComp, [ + new NameConfig("text", "Chat component text"), +]); diff --git a/client/packages/lowcoder/src/comps/index.tsx b/client/packages/lowcoder/src/comps/index.tsx index 2395f4f290..1d3b7314f5 100644 --- a/client/packages/lowcoder/src/comps/index.tsx +++ b/client/packages/lowcoder/src/comps/index.tsx @@ -193,6 +193,7 @@ import { DrawerComp } from "./hooks/drawerComp"; import { ModalComp } from "./hooks/modalComp"; import { defaultCollapsibleContainerData } from "./comps/containerComp/collapsibleContainerComp"; import { ContainerComp as FloatTextContainerComp } from "./comps/containerComp/textContainerComp"; +import { ChatComp } from "./comps/chatComp"; type Registry = { [key in UICompType]?: UICompManifest; @@ -1669,6 +1670,19 @@ export var uiCompMap: Registry = { h: 20, }, }, + chat: { + name: "Chat", + enName: "Chat", + description: "Chat Component", + categories: ["collaboration"], + icon: CommentCompIcon, // Use existing icon for now + keywords: "chat,conversation", + comp: ChatComp, + layoutInfo: { + w: 12, + h: 20, + }, + }, // Integration diff --git a/client/packages/lowcoder/src/comps/uiCompRegistry.ts b/client/packages/lowcoder/src/comps/uiCompRegistry.ts index 4c320de479..48fb2079b2 100644 --- a/client/packages/lowcoder/src/comps/uiCompRegistry.ts +++ b/client/packages/lowcoder/src/comps/uiCompRegistry.ts @@ -169,6 +169,7 @@ export type UICompType = | "columnLayout" | "ganttChart" | "kanban" + | "chat" // Added by Faran ; diff --git a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx index a931455d4b..d18705af10 100644 --- a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx @@ -306,4 +306,5 @@ export const CompStateIcon: { sunburstChart: , themeriverChart: , basicChart: , + chat: , } as const; From d1d9b9267663f4e6b8e722da2061c459450c8d77 Mon Sep 17 00:00:00 2001 From: FARAN Date: Mon, 30 Jun 2025 23:39:09 +0500 Subject: [PATCH 02/18] refactor component structure --- .../lowcoder/src/comps/comps/chatComp.tsx | 50 ------------------- .../src/comps/comps/chatComp/chatComp.tsx | 19 +++++++ .../src/comps/comps/chatComp/chatCompTypes.ts | 11 ++++ .../comps/comps/chatComp/chatPropertyView.tsx | 15 ++++++ .../src/comps/comps/chatComp/chatView.tsx | 18 +++++++ .../src/comps/comps/chatComp/index.ts | 3 ++ 6 files changed, 66 insertions(+), 50 deletions(-) delete mode 100644 client/packages/lowcoder/src/comps/comps/chatComp.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/index.ts diff --git a/client/packages/lowcoder/src/comps/comps/chatComp.tsx b/client/packages/lowcoder/src/comps/comps/chatComp.tsx deleted file mode 100644 index cf12c941b6..0000000000 --- a/client/packages/lowcoder/src/comps/comps/chatComp.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { StringControl } from "comps/controls/codeControl"; -import { UICompBuilder, withDefault } from "comps/generators"; -import { NameConfig, withExposingConfigs } from "comps/generators/withExposing"; -import { Section, sectionNames } from "lowcoder-design"; -import { trans } from "i18n"; -import React from "react"; - -// Simple children map with just basic properties -const childrenMap = { - text: withDefault(StringControl, "Chat Component Placeholder"), -}; - -// Basic view - just a simple div for now -const ChatView = React.memo((props: any) => { - return ( -
- {props.text} -
- ); -}); - -// Basic property view -const ChatPropertyView = React.memo((props: any) => { - return ( -
- {props.children.text.propertyView({ - label: "Text", - })} -
- ); -}); - -// Build the component -const ChatTmpComp = new UICompBuilder(childrenMap, (props) => ( - -)) - .setPropertyViewFn((children) => ) - .build(); - -// Export the component -export const ChatComp = withExposingConfigs(ChatTmpComp, [ - new NameConfig("text", "Chat component text"), -]); diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx new file mode 100644 index 0000000000..e9c395dd2d --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx @@ -0,0 +1,19 @@ +// client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx +import { UICompBuilder } from "comps/generators"; +import { NameConfig, withExposingConfigs } from "comps/generators/withExposing"; +import { chatChildrenMap } from "./chatCompTypes"; +import { ChatView } from "./chatView"; +import { ChatPropertyView } from "./chatPropertyView"; + +// Build the component +const ChatTmpComp = new UICompBuilder( + chatChildrenMap, + (props) => +) + .setPropertyViewFn((children) => ) + .build(); + +// Export the component +export const ChatComp = withExposingConfigs(ChatTmpComp, [ + new NameConfig("text", "Chat component text"), +]); \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts b/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts new file mode 100644 index 0000000000..a58a0eda55 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts @@ -0,0 +1,11 @@ +// client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts +import { StringControl } from "comps/controls/codeControl"; +import { withDefault } from "comps/generators"; + +export const chatChildrenMap = { + text: withDefault(StringControl, "Chat Component Placeholder"), +}; + +export type ChatCompProps = { + text: string; +}; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx new file mode 100644 index 0000000000..e31f6268de --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx @@ -0,0 +1,15 @@ +// client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx +import React from "react"; +import { Section, sectionNames } from "lowcoder-design"; + +export const ChatPropertyView = React.memo((props: any) => { + return ( +
+ {props.children.text.propertyView({ + label: "Text" + })} +
+ ); +}); + +ChatPropertyView.displayName = 'ChatPropertyView'; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx new file mode 100644 index 0000000000..61538c08c3 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx @@ -0,0 +1,18 @@ +// client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx +import React from "react"; +import { ChatCompProps } from "./chatCompTypes"; + +export const ChatView = React.memo((props: ChatCompProps) => { + return ( +
+ {props.text} +
+ ); +}); + +ChatView.displayName = 'ChatView'; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/index.ts b/client/packages/lowcoder/src/comps/comps/chatComp/index.ts new file mode 100644 index 0000000000..32064185b5 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/index.ts @@ -0,0 +1,3 @@ +// client/packages/lowcoder/src/comps/comps/chatComp/index.ts +export { ChatComp } from "./chatComp"; +export type { ChatCompProps } from "./chatCompTypes"; \ No newline at end of file From 0c132c13958fc37474581c8818e09e952e8ecfe2 Mon Sep 17 00:00:00 2001 From: FARAN Date: Tue, 1 Jul 2025 00:42:41 +0500 Subject: [PATCH 03/18] install dependencies --- client/packages/lowcoder/package.json | 11 +- .../src/comps/comps/chatComp/chatView.tsx | 84 +- client/yarn.lock | 953 +++++++++++++++++- 3 files changed, 1032 insertions(+), 16 deletions(-) diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index 7137e23b6b..3460b19ca3 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -6,7 +6,12 @@ "main": "src/index.sdk.ts", "types": "src/index.sdk.ts", "dependencies": { + "@ai-sdk/openai": "^1.3.22", "@ant-design/icons": "^5.3.0", + "@assistant-ui/react": "^0.10.24", + "@assistant-ui/react-ai-sdk": "^0.10.14", + "@assistant-ui/react-markdown": "^0.10.5", + "@assistant-ui/styles": "^0.1.13", "@bany/curl-to-json": "^1.2.8", "@codemirror/autocomplete": "^6.11.1", "@codemirror/commands": "^6.3.2", @@ -28,6 +33,8 @@ "@jsonforms/core": "^3.5.1", "@lottiefiles/dotlottie-react": "^0.13.0", "@manaflair/redux-batch": "^1.0.0", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", "@rjsf/antd": "^5.24.9", "@rjsf/core": "^5.24.9", "@rjsf/utils": "^5.24.9", @@ -37,6 +44,7 @@ "@types/react-signature-canvas": "^1.0.2", "@types/react-test-renderer": "^18.0.0", "@types/react-virtualized": "^9.21.21", + "ai": "^4.3.16", "alasql": "^4.6.6", "animate.css": "^4.1.1", "antd": "^5.25.2", @@ -61,6 +69,7 @@ "loglevel": "^1.8.0", "lowcoder-core": "workspace:^", "lowcoder-design": "workspace:^", + "lucide-react": "^0.525.0", "mime": "^3.0.0", "moment": "^2.29.4", "numbro": "^2.3.6", @@ -98,7 +107,7 @@ "regenerator-runtime": "^0.13.9", "rehype-raw": "^6.1.1", "rehype-sanitize": "^5.0.1", - "remark-gfm": "^4.0.0", + "remark-gfm": "^4.0.1", "resize-observer-polyfill": "^1.5.1", "simplebar-react": "^3.2.4", "sql-formatter": "^8.2.0", diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx index 61538c08c3..6ca388777f 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx @@ -2,16 +2,84 @@ import React from "react"; import { ChatCompProps } from "./chatCompTypes"; +// Import assistant-ui components and proper runtime +import { + AssistantRuntimeProvider, + ThreadPrimitive, + ComposerPrimitive +} from "@assistant-ui/react"; +import { useChatRuntime } from "@assistant-ui/react-ai-sdk"; +import "@assistant-ui/styles/index.css"; +import "@assistant-ui/styles/markdown.css"; + export const ChatView = React.memo((props: ChatCompProps) => { + // Create proper runtime using useChatRuntime + const runtime = useChatRuntime({ + api: "/api/chat", // We'll create this endpoint later + }); + return ( -
- {props.text} -
+ +
+
+

🚀 Assistant-UI with Vercel AI SDK!

+ + {/* Test Thread with real runtime */} +
+ +
+
+

+ {props.text} - Runtime Working! 🎉 +

+
+
+
+
+ + {/* Test Composer with real runtime */} +
+ +
+ + {/* Property status */} +
+ ✅ Test Status:
+ Text: {props.text}
+ Runtime: Vercel AI SDK ✅ +
+
+
+
); }); diff --git a/client/yarn.lock b/client/yarn.lock index b3885ff806..de7a2c227b 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -45,6 +45,71 @@ __metadata: languageName: node linkType: hard +"@ai-sdk/openai@npm:^1.3.22": + version: 1.3.22 + resolution: "@ai-sdk/openai@npm:1.3.22" + dependencies: + "@ai-sdk/provider": 1.1.3 + "@ai-sdk/provider-utils": 2.2.8 + peerDependencies: + zod: ^3.0.0 + checksum: 5572a349c93cb4953e4afcb837870eef876f022b8a931a52820a8d51106c67382cd77c1defc8725d43d8e03d5baf48c52d4b198c8fa68e1a724408567fc2d62a + languageName: node + linkType: hard + +"@ai-sdk/provider-utils@npm:2.2.8": + version: 2.2.8 + resolution: "@ai-sdk/provider-utils@npm:2.2.8" + dependencies: + "@ai-sdk/provider": 1.1.3 + nanoid: ^3.3.8 + secure-json-parse: ^2.7.0 + peerDependencies: + zod: ^3.23.8 + checksum: 15487a4b4f1cc4eb72d7fc7afb71506f7bf439b538ef98b0c189a2b6d0dd72f10614c716c2206390b2624d6aeeb0799a0dad86010f6a505bbd9bd1b1d76adc60 + languageName: node + linkType: hard + +"@ai-sdk/provider@npm:1.1.3, @ai-sdk/provider@npm:^1.1.3": + version: 1.1.3 + resolution: "@ai-sdk/provider@npm:1.1.3" + dependencies: + json-schema: ^0.4.0 + checksum: 197b5907aaca7d96b0d114c3456d46fa1134e6d98bb22617c2bd28b3592c1ab79d524ea894209cedc0e74695ee250b2a32de7d084122fd047565b64cbba009bb + languageName: node + linkType: hard + +"@ai-sdk/react@npm:*, @ai-sdk/react@npm:1.2.12": + version: 1.2.12 + resolution: "@ai-sdk/react@npm:1.2.12" + dependencies: + "@ai-sdk/provider-utils": 2.2.8 + "@ai-sdk/ui-utils": 1.2.11 + swr: ^2.2.5 + throttleit: 2.1.0 + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + checksum: 2bb15f4e9416c6cf17e66581435497e9ed46d8e19be657a8e3507c847017f9aa0fb9c64b989890d5b9614468df6ee94e271bd96ef65212fd193e2e356a84c54b + languageName: node + linkType: hard + +"@ai-sdk/ui-utils@npm:*, @ai-sdk/ui-utils@npm:1.2.11": + version: 1.2.11 + resolution: "@ai-sdk/ui-utils@npm:1.2.11" + dependencies: + "@ai-sdk/provider": 1.1.3 + "@ai-sdk/provider-utils": 2.2.8 + zod-to-json-schema: ^3.24.1 + peerDependencies: + zod: ^3.23.8 + checksum: 8ea01d026923aae73a06810f33ce24020fc7f001331ac0b801d2a8be576d42353abdd3344278dcfdb4eb43914c38b4fb7d3305354a6cd2ba0cb5359f1fd01a49 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.2.0": version: 2.3.0 resolution: "@ampproject/remapping@npm:2.3.0" @@ -143,6 +208,113 @@ __metadata: languageName: node linkType: hard +"@assistant-ui/react-ai-sdk@npm:^0.10.14": + version: 0.10.14 + resolution: "@assistant-ui/react-ai-sdk@npm:0.10.14" + dependencies: + "@ai-sdk/react": "*" + "@ai-sdk/ui-utils": "*" + "@assistant-ui/react-edge": 0.2.12 + "@radix-ui/react-use-callback-ref": ^1.1.1 + "@types/json-schema": ^7.0.15 + zod: ^3.25.64 + zustand: ^5.0.5 + peerDependencies: + "@assistant-ui/react": ^0.10.24 + "@types/react": "*" + react: ^18 || ^19 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 1deb3310ca1cf3b901e4afe06e1fd7256759f2b55e8e658ec56a2075636bdd61a670cf373b3a1f2b9877674fc62df9c18c79852c0a3a05f97063cc57b15b9cf7 + languageName: node + linkType: hard + +"@assistant-ui/react-edge@npm:0.2.12": + version: 0.2.12 + resolution: "@assistant-ui/react-edge@npm:0.2.12" + dependencies: + "@ai-sdk/provider": ^1.1.3 + assistant-stream: ^0.2.17 + json-schema: ^0.4.0 + zod: ^3.25.64 + zod-to-json-schema: ^3.24.5 + peerDependencies: + "@assistant-ui/react": "*" + "@types/react": "*" + "@types/react-dom": "*" + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 6d533f26e533b7d177689ce3aa8d6b6fe0077d1f77d9f5b3707bae09bf084b7ba629a242e92fdf7f99328edc7521f3d0845587e961f19bce55c820ece7bba828 + languageName: node + linkType: hard + +"@assistant-ui/react-markdown@npm:^0.10.5": + version: 0.10.5 + resolution: "@assistant-ui/react-markdown@npm:0.10.5" + dependencies: + "@radix-ui/react-primitive": ^2.1.3 + "@radix-ui/react-use-callback-ref": ^1.1.1 + "@types/hast": ^3.0.4 + classnames: ^2.5.1 + react-markdown: ^10.1.0 + peerDependencies: + "@assistant-ui/react": ^0.10.24 + "@types/react": "*" + react: ^18 || ^19 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 85991a87a04f34c68b0290dfa6061e760b90bdcef694f7b938138b34836eaafe7dd0159faa54e262c227007dd8b912e5cab53329a99c26561a91a0a17d14a2ac + languageName: node + linkType: hard + +"@assistant-ui/react@npm:^0.10.24": + version: 0.10.24 + resolution: "@assistant-ui/react@npm:0.10.24" + dependencies: + "@radix-ui/primitive": ^1.1.2 + "@radix-ui/react-compose-refs": ^1.1.2 + "@radix-ui/react-context": ^1.1.2 + "@radix-ui/react-popover": ^1.1.14 + "@radix-ui/react-primitive": ^2.1.3 + "@radix-ui/react-slot": ^1.2.3 + "@radix-ui/react-use-callback-ref": ^1.1.1 + "@radix-ui/react-use-escape-keydown": ^1.1.1 + "@standard-schema/spec": ^1.0.0 + assistant-cloud: 0.0.2 + assistant-stream: ^0.2.17 + json-schema: ^0.4.0 + nanoid: 5.1.5 + react-textarea-autosize: ^8.5.9 + zod: ^3.25.64 + zustand: ^5.0.5 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 4f9483464ac24caf7271161683dc8a8ab17c2263b16ebb5f1a4da75ea1ecda93048f93c7b06c90202e76215d72beb190408d3833ab54e4d19bdbd0ee2523a5f3 + languageName: node + linkType: hard + +"@assistant-ui/styles@npm:^0.1.13": + version: 0.1.13 + resolution: "@assistant-ui/styles@npm:0.1.13" + checksum: edbe7f3aa144eb823830f662cc1f44cfe3a53fff05f1ec918a7e0a692cad86a276069c7834531102ff37125070cc42dfa84380c8daeee3cdbceae0d42c517f64 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.5, @babel/code-frame@npm:^7.27.1": version: 7.27.1 resolution: "@babel/code-frame@npm:7.27.1" @@ -2181,6 +2353,15 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.7.2": + version: 1.7.2 + resolution: "@floating-ui/core@npm:1.7.2" + dependencies: + "@floating-ui/utils": ^0.2.10 + checksum: aea540ea0101daf83e5beb2769af81f0532dcb8514dbee9d4c0a06576377d56dbfd4e5c3b031359594a26649734d1145bbd5524e8a573a745a5fcadc6b307906 + languageName: node + linkType: hard + "@floating-ui/dom@npm:^1.4.2": version: 1.7.0 resolution: "@floating-ui/dom@npm:1.7.0" @@ -2191,6 +2372,35 @@ __metadata: languageName: node linkType: hard +"@floating-ui/dom@npm:^1.7.2": + version: 1.7.2 + resolution: "@floating-ui/dom@npm:1.7.2" + dependencies: + "@floating-ui/core": ^1.7.2 + "@floating-ui/utils": ^0.2.10 + checksum: 232d6668693cfecec038f3fb1f5398eace340427a9108701b895136c18d303d8bdd6237dce224626abf56a5a4592db776fbd0d187aa0f72c729bfca14a474b65 + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.0.0": + version: 2.1.4 + resolution: "@floating-ui/react-dom@npm:2.1.4" + dependencies: + "@floating-ui/dom": ^1.7.2 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: be2cc094b0c5cd7f6a06c6c58944e4f070e231bdc8d9f84fc8246eedf91e1545a8a9e6d8560664054de126aae6520da57a30b7d81433cb2625641a08eea8f029 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.10": + version: 0.2.10 + resolution: "@floating-ui/utils@npm:0.2.10" + checksum: ffc4c24a46a665cfd0337e9aaf7de8415b572f8a0f323af39175e4b575582aed13d172e7f049eedeece9eaf022bad019c140a2d192580451984ae529bdf1285c + languageName: node + linkType: hard + "@floating-ui/utils@npm:^0.2.9": version: 0.2.9 resolution: "@floating-ui/utils@npm:0.2.9" @@ -3137,6 +3347,13 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/api@npm:1.9.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 9e88e59d53ced668f3daaecfd721071c5b85a67dd386f1c6f051d1be54375d850016c881f656ffbe9a03bedae85f7e89c2f2b635313f9c9b195ad033cdc31020 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -3158,6 +3375,423 @@ __metadata: languageName: node linkType: hard +"@radix-ui/primitive@npm:1.1.2, @radix-ui/primitive@npm:^1.1.2": + version: 1.1.2 + resolution: "@radix-ui/primitive@npm:1.1.2" + checksum: 6cb2ac097faf77b7288bdfd87d92e983e357252d00ee0d2b51ad8e7897bf9f51ec53eafd7dd64c613671a2b02cb8166177bc3de444a6560ec60835c363321c18 + languageName: node + linkType: hard + +"@radix-ui/react-arrow@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-arrow@npm:1.1.7" + dependencies: + "@radix-ui/react-primitive": 2.1.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 6cdf74f06090f8994cdf6d3935a44ea3ac309163a4f59c476482c4907e8e0775f224045030abf10fa4f9e1cb7743db034429249b9e59354988e247eeb0f4fdcf + languageName: node + linkType: hard + +"@radix-ui/react-compose-refs@npm:1.1.2, @radix-ui/react-compose-refs@npm:^1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-compose-refs@npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 9a91f0213014ffa40c5b8aae4debb993be5654217e504e35aa7422887eb2d114486d37e53c482d0fffb00cd44f51b5269fcdf397b280c71666fa11b7f32f165d + languageName: node + linkType: hard + +"@radix-ui/react-context@npm:1.1.2, @radix-ui/react-context@npm:^1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-context@npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 6d08437f23df362672259e535ae463e70bf7a0069f09bfa06c983a5a90e15250bde19da1d63ef8e3da06df1e1b4f92afa9d28ca6aa0297bb1c8aaf6ca83d28c5 + languageName: node + linkType: hard + +"@radix-ui/react-dismissable-layer@npm:1.1.10": + version: 1.1.10 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.10" + dependencies: + "@radix-ui/primitive": 1.1.2 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-use-callback-ref": 1.1.1 + "@radix-ui/react-use-escape-keydown": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: c4f31e8e93ae979a1bcd60726f8ebe7b79f23baafcd1d1e65f62cff6b322b2c6ff6132d82f2e63737f9955a8f04407849036f5b64b478e9a5678747d835957d8 + languageName: node + linkType: hard + +"@radix-ui/react-focus-guards@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-focus-guards@npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 618658e2b98575198b94ccfdd27f41beb37f83721c9a04617e848afbc47461124ae008d703d713b9644771d96d4852e49de322cf4be3b5f10a4f94d200db5248 + languageName: node + linkType: hard + +"@radix-ui/react-focus-scope@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-focus-scope@npm:1.1.7" + dependencies: + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-use-callback-ref": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: bb642d192d3da8431f8b39f64959b493a7ba743af8501b76699ef93357c96507c11fb76d468824b52b0e024eaee130a641f3a213268ac7c9af34883b45610c9b + languageName: node + linkType: hard + +"@radix-ui/react-id@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-id@npm:1.1.1" + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 8d68e200778eb3038906870fc869b3d881f4a46715fb20cddd9c76cba42fdaaa4810a3365b6ec2daf0f185b9201fc99d009167f59c7921bc3a139722c2e976db + languageName: node + linkType: hard + +"@radix-ui/react-popover@npm:^1.1.14": + version: 1.1.14 + resolution: "@radix-ui/react-popover@npm:1.1.14" + dependencies: + "@radix-ui/primitive": 1.1.2 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-context": 1.1.2 + "@radix-ui/react-dismissable-layer": 1.1.10 + "@radix-ui/react-focus-guards": 1.1.2 + "@radix-ui/react-focus-scope": 1.1.7 + "@radix-ui/react-id": 1.1.1 + "@radix-ui/react-popper": 1.2.7 + "@radix-ui/react-portal": 1.1.9 + "@radix-ui/react-presence": 1.1.4 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-slot": 1.2.3 + "@radix-ui/react-use-controllable-state": 1.2.2 + aria-hidden: ^1.2.4 + react-remove-scroll: ^2.6.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 50f146117ebf675944181ef2df4fbc2d7ca017c71a2ab78eaa67159eb8a0101c682fa02bafa2b132ea7744592b7f103d02935ace2c1f430ab9040a0ece9246c8 + languageName: node + linkType: hard + +"@radix-ui/react-popper@npm:1.2.7": + version: 1.2.7 + resolution: "@radix-ui/react-popper@npm:1.2.7" + dependencies: + "@floating-ui/react-dom": ^2.0.0 + "@radix-ui/react-arrow": 1.1.7 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-context": 1.1.2 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-use-callback-ref": 1.1.1 + "@radix-ui/react-use-layout-effect": 1.1.1 + "@radix-ui/react-use-rect": 1.1.1 + "@radix-ui/react-use-size": 1.1.1 + "@radix-ui/rect": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 1d672b8b635846501212eb0cd15273c8acdd31e76e78d2b9ba29ce29730d5a2d3a61a8ed49bb689c94f67f45d1dffe0d49449e0810f08c4e112d8aef8430e76d + languageName: node + linkType: hard + +"@radix-ui/react-portal@npm:1.1.9": + version: 1.1.9 + resolution: "@radix-ui/react-portal@npm:1.1.9" + dependencies: + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: bd6be39bf021d5c917e2474ecba411e2625171f7ef96862b9af04bbd68833bb3662a7f1fbdeb5a7a237111b10e811e76d2cd03e957dadd6e668ef16541bfbd68 + languageName: node + linkType: hard + +"@radix-ui/react-presence@npm:1.1.4": + version: 1.1.4 + resolution: "@radix-ui/react-presence@npm:1.1.4" + dependencies: + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: d3b0976368fccdfa07100c1f07ca434d0092d4132d1ed4a5c213802f7318d77fc1fd61d1b7038b87e82912688fafa97d8af000a6cca4027b09d92c5477f79dd0 + languageName: node + linkType: hard + +"@radix-ui/react-primitive@npm:2.1.3, @radix-ui/react-primitive@npm:^2.1.3": + version: 2.1.3 + resolution: "@radix-ui/react-primitive@npm:2.1.3" + dependencies: + "@radix-ui/react-slot": 1.2.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 01f82e4bad76b57767198762c905e5bcea04f4f52129749791e31adfcb1b36f6fdc89c73c40017d812b6e25e4ac925d837214bb280cfeaa5dc383457ce6940b0 + languageName: node + linkType: hard + +"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-slot@npm:1.2.3" + dependencies: + "@radix-ui/react-compose-refs": 1.1.2 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 2731089e15477dd5eef98a5757c36113dd932d0c52ff05123cd89f05f0412e95e5b205229185d1cd705cda4a674a838479cce2b3b46ed903f82f5d23d9e3f3c2 + languageName: node + linkType: hard + +"@radix-ui/react-tooltip@npm:^1.2.7": + version: 1.2.7 + resolution: "@radix-ui/react-tooltip@npm:1.2.7" + dependencies: + "@radix-ui/primitive": 1.1.2 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-context": 1.1.2 + "@radix-ui/react-dismissable-layer": 1.1.10 + "@radix-ui/react-id": 1.1.1 + "@radix-ui/react-popper": 1.2.7 + "@radix-ui/react-portal": 1.1.9 + "@radix-ui/react-presence": 1.1.4 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-slot": 1.2.3 + "@radix-ui/react-use-controllable-state": 1.2.2 + "@radix-ui/react-visually-hidden": 1.2.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: aebb5c124c73dc236e9362899cb81bb0b1d4103d29205122f55dd64a9b9bdf4be7cc335964e1885098be3c570416a35899a317e0e6f373b6f9d39334699e4694 + languageName: node + linkType: hard + +"@radix-ui/react-use-callback-ref@npm:1.1.1, @radix-ui/react-use-callback-ref@npm:^1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: cde8c40f1d4e79e6e71470218163a746858304bad03758ac84dc1f94247a046478e8e397518350c8d6609c84b7e78565441d7505bb3ed573afce82cfdcd19faf + languageName: node + linkType: hard + +"@radix-ui/react-use-controllable-state@npm:1.2.2": + version: 1.2.2 + resolution: "@radix-ui/react-use-controllable-state@npm:1.2.2" + dependencies: + "@radix-ui/react-use-effect-event": 0.0.2 + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: b438ee199d0630bf95eaafe8bf4bce219e73b371cfc8465f47548bfa4ee231f1134b5c6696b242890a01a0fd25fa34a7b172346bbfc5ee25cfb28b3881b1dc92 + languageName: node + linkType: hard + +"@radix-ui/react-use-effect-event@npm:0.0.2": + version: 0.0.2 + resolution: "@radix-ui/react-use-effect-event@npm:0.0.2" + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 5a1950a30a399ea7e4b98154da9f536737a610de80189b7aacd4f064a89a3cd0d2a48571d527435227252e72e872bdb544ff6ffcfbdd02de2efd011be4aaa902 + languageName: node + linkType: hard + +"@radix-ui/react-use-escape-keydown@npm:1.1.1, @radix-ui/react-use-escape-keydown@npm:^1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.1" + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 0eb0756c2c55ddcde9ff01446ab01c085ab2bf799173e97db7ef5f85126f9e8600225570801a1f64740e6d14c39ffe8eed7c14d29737345a5797f4622ac96f6f + languageName: node + linkType: hard + +"@radix-ui/react-use-layout-effect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: bad2ba4f206e6255263582bedfb7868773c400836f9a1b423c0b464ffe4a17e13d3f306d1ce19cf7a19a492e9d0e49747464f2656451bb7c6a99f5a57bd34de2 + languageName: node + linkType: hard + +"@radix-ui/react-use-rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-rect@npm:1.1.1" + dependencies: + "@radix-ui/rect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 116461bebc49472f7497e66a9bd413541181b3d00c5e0aaeef45d790dc1fbd7c8dcea80b169ea273306228b9a3c2b70067e902d1fd5004b3057e3bbe35b9d55d + languageName: node + linkType: hard + +"@radix-ui/react-use-size@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-size@npm:1.1.1" + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 64e61f65feb67ffc80e1fc4a8d5e32480fb6d68475e2640377e021178dead101568cba5f936c9c33e6c142c7cf2fb5d76ad7b23ef80e556ba142d56cf306147b + languageName: node + linkType: hard + +"@radix-ui/react-visually-hidden@npm:1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-visually-hidden@npm:1.2.3" + dependencies: + "@radix-ui/react-primitive": 2.1.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 42296bde1ddf4af4e7445e914c35d6bc8406d6ede49f0a959a553e75b3ed21da09fda80a81c48d8ec058ed8129ce7137499d02ee26f90f0d3eaa2417922d6509 + languageName: node + linkType: hard + +"@radix-ui/rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/rect@npm:1.1.1" + checksum: c1c111edeab70b14a735bca43601de6468c792482864b766ac8940b43321492e5c0ae62f92b156cecdc9265ec3c680c32b3fa0c8a90b5e796923a9af13c5dc20 + languageName: node + linkType: hard + "@rc-component/async-validator@npm:^5.0.3": version: 5.0.4 resolution: "@rc-component/async-validator@npm:5.0.4" @@ -3927,6 +4561,13 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0": + version: 1.0.0 + resolution: "@standard-schema/spec@npm:1.0.0" + checksum: 2d7d73a1c9706622750ab06fc40ef7c1d320b52d5e795f8a1c7a77d0d6a9f978705092bc4149327b3cff4c9a14e5b3800d3b00dc945489175a2d3031ded8332a + languageName: node + linkType: hard + "@supabase/auth-js@npm:2.69.1": version: 2.69.1 resolution: "@supabase/auth-js@npm:2.69.1" @@ -4516,6 +5157,13 @@ __metadata: languageName: node linkType: hard +"@types/diff-match-patch@npm:^1.0.36": + version: 1.0.36 + resolution: "@types/diff-match-patch@npm:1.0.36" + checksum: 7d7ce03422fcc3e79d0cda26e4748aeb176b75ca4b4e5f38459b112bf24660d628424bdb08d330faefa69039d19a5316e7a102a8ab68b8e294c8346790e55113 + languageName: node + linkType: hard + "@types/eslint-scope@npm:^3.7.7": version: 3.7.7 resolution: "@types/eslint-scope@npm:3.7.7" @@ -4648,7 +5296,7 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^3.0.0": +"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4": version: 3.0.4 resolution: "@types/hast@npm:3.0.4" dependencies: @@ -5844,6 +6492,26 @@ __metadata: languageName: node linkType: hard +"ai@npm:^4.3.16": + version: 4.3.16 + resolution: "ai@npm:4.3.16" + dependencies: + "@ai-sdk/provider": 1.1.3 + "@ai-sdk/provider-utils": 2.2.8 + "@ai-sdk/react": 1.2.12 + "@ai-sdk/ui-utils": 1.2.11 + "@opentelemetry/api": 1.9.0 + jsondiffpatch: 0.6.0 + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + react: + optional: true + checksum: 1c13e641f04440d5ea550435defda01508473f4c5e65b248df4d39911c5accb77dd17009563c16163097642a33fe9eb421c6bedbcf824835ace9dfd96c785061 + languageName: node + linkType: hard + "ajv-formats@npm:^2.1.0, ajv-formats@npm:^2.1.1": version: 2.1.1 resolution: "ajv-formats@npm:2.1.1" @@ -6130,6 +6798,15 @@ __metadata: languageName: node linkType: hard +"aria-hidden@npm:^1.2.4": + version: 1.2.6 + resolution: "aria-hidden@npm:1.2.6" + dependencies: + tslib: ^2.0.0 + checksum: 56409c55c43ad917607f3f3aa67748dcf30a27e8bb5cb3c5d86b43e38babadd63cd77731a27bc8a8c4332c2291741ed92333bf7ca45f8b99ebc87b94a8070a6e + languageName: node + linkType: hard + "aria-query@npm:5.1.3": version: 5.1.3 resolution: "aria-query@npm:5.1.3" @@ -6319,6 +6996,26 @@ __metadata: languageName: node linkType: hard +"assistant-cloud@npm:0.0.2": + version: 0.0.2 + resolution: "assistant-cloud@npm:0.0.2" + dependencies: + assistant-stream: ^0.2.17 + checksum: 5a4df3ca6c40dad15eb38c3f974bffd321bf52859f41bc3459d75f912e37579211a07876a5d28ce462396407f87a863dc849034f9bc24e04c010ab31827c6f82 + languageName: node + linkType: hard + +"assistant-stream@npm:^0.2.17": + version: 0.2.17 + resolution: "assistant-stream@npm:0.2.17" + dependencies: + "@types/json-schema": ^7.0.15 + nanoid: 5.1.5 + secure-json-parse: ^4.0.0 + checksum: 1b317f2e5c19ce013cb8673ff73461b5ba39fe80ff05c3d4b6e67704d89a70ab76337dac6974901962caedcffc45429e990856c3e551122de6a3b37649a2d8cb + languageName: node + linkType: hard + "ast-types-flow@npm:^0.0.8": version: 0.0.8 resolution: "ast-types-flow@npm:0.0.8" @@ -7166,6 +7863,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 0c656f30b782fed4d99198825c0860158901f449a6b12b818b0aabad27ec970389e7e8767d0e00762175b23620c812e70c4fd92c0210e55fc2d993638b74e86e + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -8811,7 +9515,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard -"dequal@npm:^2.0.0": +"dequal@npm:^2.0.0, dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 @@ -8842,6 +9546,13 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"detect-node-es@npm:^1.1.0": + version: 1.1.0 + resolution: "detect-node-es@npm:1.1.0" + checksum: e46307d7264644975b71c104b9f028ed1d3d34b83a15b8a22373640ce5ea630e5640b1078b8ea15f202b54641da71e4aa7597093bd4b91f113db520a26a37449 + languageName: node + linkType: hard + "detect-node@npm:^2.0.4": version: 2.1.0 resolution: "detect-node@npm:2.1.0" @@ -8858,6 +9569,13 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"diff-match-patch@npm:^1.0.5": + version: 1.0.5 + resolution: "diff-match-patch@npm:1.0.5" + checksum: 841522d01b09cccbc4e4402cf61514a81b906349a7d97b67222390f2d35cf5df277cb23959eeed212d5e46afb5629cebab41b87918672c5a05c11c73688630e3 + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -10763,6 +11481,13 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"get-nonce@npm:^1.0.0": + version: 1.0.1 + resolution: "get-nonce@npm:1.0.1" + checksum: e2614e43b4694c78277bb61b0f04583d45786881289285c73770b07ded246a98be7e1f78b940c80cbe6f2b07f55f0b724e6db6fd6f1bcbd1e8bdac16521074ed + languageName: node + linkType: hard + "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -13227,7 +13952,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard -"json-schema@npm:0.4.0": +"json-schema@npm:0.4.0, json-schema@npm:^0.4.0": version: 0.4.0 resolution: "json-schema@npm:0.4.0" checksum: 66389434c3469e698da0df2e7ac5a3281bcff75e797a5c127db7c5b56270e01ae13d9afa3c03344f76e32e81678337a8c912bdbb75101c62e487dc3778461d72 @@ -13277,6 +14002,19 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"jsondiffpatch@npm:0.6.0": + version: 0.6.0 + resolution: "jsondiffpatch@npm:0.6.0" + dependencies: + "@types/diff-match-patch": ^1.0.36 + chalk: ^5.3.0 + diff-match-patch: ^1.0.5 + bin: + jsondiffpatch: bin/jsondiffpatch.js + checksum: 27d7aa42c3b9f9359fd179bb49c621ed848f6095615014cd0acbd29c37e364c11cb5a19c3ef2c873631e7b5f7ba6d1465978a489efa28cb1dc9fba98b0498712 + languageName: node + linkType: hard + "jsonfile@npm:^6.0.1": version: 6.1.0 resolution: "jsonfile@npm:6.1.0" @@ -14103,7 +14841,12 @@ coolshapes-react@lowcoder-org/coolshapes-react: version: 0.0.0-use.local resolution: "lowcoder@workspace:packages/lowcoder" dependencies: + "@ai-sdk/openai": ^1.3.22 "@ant-design/icons": ^5.3.0 + "@assistant-ui/react": ^0.10.24 + "@assistant-ui/react-ai-sdk": ^0.10.14 + "@assistant-ui/react-markdown": ^0.10.5 + "@assistant-ui/styles": ^0.1.13 "@bany/curl-to-json": ^1.2.8 "@codemirror/autocomplete": ^6.11.1 "@codemirror/commands": ^6.3.2 @@ -14125,6 +14868,8 @@ coolshapes-react@lowcoder-org/coolshapes-react: "@jsonforms/core": ^3.5.1 "@lottiefiles/dotlottie-react": ^0.13.0 "@manaflair/redux-batch": ^1.0.0 + "@radix-ui/react-slot": ^1.2.3 + "@radix-ui/react-tooltip": ^1.2.7 "@rjsf/antd": ^5.24.9 "@rjsf/core": ^5.24.9 "@rjsf/utils": ^5.24.9 @@ -14143,6 +14888,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: "@types/supercluster": ^7.1.3 "@types/uuid": ^8.3.4 "@vitejs/plugin-react": ^2.2.0 + ai: ^4.3.16 alasql: ^4.6.6 animate.css: ^4.1.1 antd: ^5.25.2 @@ -14172,6 +14918,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: loglevel: ^1.8.0 lowcoder-core: "workspace:^" lowcoder-design: "workspace:^" + lucide-react: ^0.525.0 mime: ^3.0.0 moment: ^2.29.4 numbro: ^2.3.6 @@ -14209,7 +14956,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: regenerator-runtime: ^0.13.9 rehype-raw: ^6.1.1 rehype-sanitize: ^5.0.1 - remark-gfm: ^4.0.0 + remark-gfm: ^4.0.1 resize-observer-polyfill: ^1.5.1 rollup-plugin-terser: ^7.0.2 rollup-plugin-visualizer: ^5.9.2 @@ -14274,6 +15021,15 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"lucide-react@npm:^0.525.0": + version: 0.525.0 + resolution: "lucide-react@npm:0.525.0" + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: eef781bfcfd211dac1ca93d0e7c5830aec1bacef72f4af3994807ac04d2a0fe327aca1a77234daf7f13e06ea47e5585766c927c8e1f2e7dcb982e4dc55aeda5e + languageName: node + linkType: hard + "lz-string@npm:^1.5.0": version: 1.5.0 resolution: "lz-string@npm:1.5.0" @@ -15685,6 +16441,15 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"nanoid@npm:5.1.5": + version: 5.1.5 + resolution: "nanoid@npm:5.1.5" + bin: + nanoid: bin/nanoid.js + checksum: 6de2d006b51c983be385ef7ee285f7f2a57bd96f8c0ca881c4111461644bd81fafc2544f8e07cb834ca0f3e0f3f676c1fe78052183f008b0809efe6e273119f5 + languageName: node + linkType: hard + "nanoid@npm:^3.3.7, nanoid@npm:^3.3.8": version: 3.3.11 resolution: "nanoid@npm:3.3.11" @@ -17933,6 +18698,28 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"react-markdown@npm:^10.1.0": + version: 10.1.0 + resolution: "react-markdown@npm:10.1.0" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + hast-util-to-jsx-runtime: ^2.0.0 + html-url-attributes: ^3.0.0 + mdast-util-to-hast: ^13.0.0 + remark-parse: ^11.0.0 + remark-rehype: ^11.0.0 + unified: ^11.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + peerDependencies: + "@types/react": ">=18" + react: ">=18" + checksum: fa7ef860e32a18206c5b301de8672be609b108f46f0f091e9779d50ff8145bd63d0f6e82ffb18fc1b7aee2264cbdac1100205596ff10d2c3d2de6627abb3868f + languageName: node + linkType: hard + "react-markdown@npm:^9.0.1": version: 9.1.0 resolution: "react-markdown@npm:9.1.0" @@ -18026,6 +18813,41 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"react-remove-scroll-bar@npm:^2.3.7": + version: 2.3.8 + resolution: "react-remove-scroll-bar@npm:2.3.8" + dependencies: + react-style-singleton: ^2.2.2 + tslib: ^2.0.0 + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: c4663247f689dbe51c370836edf735487f6d8796acb7f15b09e8a1c14e84c7997360e8e3d54de2bc9c0e782fed2b2c4127d15b4053e4d2cf26839e809e57605f + languageName: node + linkType: hard + +"react-remove-scroll@npm:^2.6.3": + version: 2.7.1 + resolution: "react-remove-scroll@npm:2.7.1" + dependencies: + react-remove-scroll-bar: ^2.3.7 + react-style-singleton: ^2.2.3 + tslib: ^2.1.0 + use-callback-ref: ^1.3.3 + use-sidecar: ^1.1.3 + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: c8b1988d473ca0b4911a0a42f09dc7806d5db998c3ec938ae2791a5f82d807c2cdebb78a1c58a0bab62a83112528dda2f20d509d0e048fe281b9dfc027c39763 + languageName: node + linkType: hard + "react-resizable@npm:^3.0.4, react-resizable@npm:^3.0.5": version: 3.0.5 resolution: "react-resizable@npm:3.0.5" @@ -18132,6 +18954,22 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"react-style-singleton@npm:^2.2.2, react-style-singleton@npm:^2.2.3": + version: 2.2.3 + resolution: "react-style-singleton@npm:2.2.3" + dependencies: + get-nonce: ^1.0.0 + tslib: ^2.0.0 + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: a7b0bf493c9231065ebafa84c4237aed997c746c561196121b7de82fe155a5355b372db5070a3ac9fe980cf7f60dc0f1e8cf6402a2aa5b2957392932ccf76e76 + languageName: node + linkType: hard + "react-test-renderer@npm:^18.1.0": version: 18.3.1 resolution: "react-test-renderer@npm:18.3.1" @@ -18145,7 +18983,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard -"react-textarea-autosize@npm:^8.3.2": +"react-textarea-autosize@npm:^8.3.2, react-textarea-autosize@npm:^8.5.9": version: 8.5.9 resolution: "react-textarea-autosize@npm:8.5.9" dependencies: @@ -18534,7 +19372,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard -"remark-gfm@npm:^4.0.0": +"remark-gfm@npm:^4.0.0, remark-gfm@npm:^4.0.1": version: 4.0.1 resolution: "remark-gfm@npm:4.0.1" dependencies: @@ -19291,6 +20129,20 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"secure-json-parse@npm:^2.7.0": + version: 2.7.0 + resolution: "secure-json-parse@npm:2.7.0" + checksum: d9d7d5a01fc6db6115744ba23cf9e67ecfe8c524d771537c062ee05ad5c11b64c730bc58c7f33f60bd6877f96b86f0ceb9ea29644e4040cb757f6912d4dd6737 + languageName: node + linkType: hard + +"secure-json-parse@npm:^4.0.0": + version: 4.0.0 + resolution: "secure-json-parse@npm:4.0.0" + checksum: 5092d1385f242ae1a189a193eeb2f5ed1f00350270edefe209fbd35898a93745da58e7d8d4833a6da2235a89df7140d2959817ca4f2b1a4bfd135a812fab4f01 + languageName: node + linkType: hard + "select-hose@npm:^2.0.0": version: 2.0.0 resolution: "select-hose@npm:2.0.0" @@ -20402,6 +21254,18 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"swr@npm:^2.2.5": + version: 2.3.3 + resolution: "swr@npm:2.3.3" + dependencies: + dequal: ^2.0.3 + use-sync-external-store: ^1.4.0 + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 25b2ab03d0149951e4612fae8458d333d6fbe912298684495969250553176b5b5b731be119f406ebadf6e9c5dc39726dd2492cca9684f448aa1734274e2a4919 + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.2, symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" @@ -20558,6 +21422,13 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"throttleit@npm:2.1.0": + version: 2.1.0 + resolution: "throttleit@npm:2.1.0" + checksum: a2003947aafc721c4a17e6f07db72dc88a64fa9bba0f9c659f7997d30f9590b3af22dadd6a41851e0e8497d539c33b2935c2c7919cf4255922509af6913c619b + languageName: node + linkType: hard + "thunky@npm:^1.0.2": version: 1.1.0 resolution: "thunky@npm:1.1.0" @@ -21478,6 +22349,21 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"use-callback-ref@npm:^1.3.3": + version: 1.3.3 + resolution: "use-callback-ref@npm:1.3.3" + dependencies: + tslib: ^2.0.0 + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 4da1c82d7a2409cee6c882748a40f4a083decf238308bf12c3d0166f0e338f8d512f37b8d11987eb5a421f14b9b5b991edf3e11ed25c3bb7a6559081f8359b44 + languageName: node + linkType: hard + "use-composed-ref@npm:^1.3.0": version: 1.4.0 resolution: "use-composed-ref@npm:1.4.0" @@ -21516,7 +22402,23 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.0": +"use-sidecar@npm:^1.1.3": + version: 1.1.3 + resolution: "use-sidecar@npm:1.1.3" + dependencies: + detect-node-es: ^1.1.0 + tslib: ^2.0.0 + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 88664c6b2c5b6e53e4d5d987694c9053cea806da43130248c74ca058945c8caa6ccb7b1787205a9eb5b9d124633e42153848904002828acabccdc48cda026622 + languageName: node + linkType: hard + +"use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:^1.4.0": version: 1.5.0 resolution: "use-sync-external-store@npm:1.5.0" peerDependencies: @@ -22741,6 +23643,22 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.24.1, zod-to-json-schema@npm:^3.24.5": + version: 3.24.6 + resolution: "zod-to-json-schema@npm:3.24.6" + peerDependencies: + zod: ^3.24.1 + checksum: 5f4d29597cfd88d8fb8a539f0169affb8705d67ee9cbe478aa01bb1d2554e0540ca713fa4ddeb2fd834e87e7cdff61fa396f6d1925a9006de70afe6cd68bf7d2 + languageName: node + linkType: hard + +"zod@npm:^3.25.64": + version: 3.25.67 + resolution: "zod@npm:3.25.67" + checksum: 56ab904d33b1cd00041ce64ae05b0628fcbfeb7e707fa31cd498a97b540135e4dfe685200c9c62aea307695ee132870b4bc34f035228ea728aa75cc96a4954cb + languageName: node + linkType: hard + "zrender@npm:5.6.1, zrender@npm:^5.1.1": version: 5.6.1 resolution: "zrender@npm:5.6.1" @@ -22750,6 +23668,27 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"zustand@npm:^5.0.5": + version: 5.0.6 + resolution: "zustand@npm:5.0.6" + peerDependencies: + "@types/react": ">=18.0.0" + immer: ">=9.0.6" + react: ">=18.0.0" + use-sync-external-store: ">=1.2.0" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + checksum: 1808cd7d49e8ba6777e0d8d524a858a918a4fd0bcbde88eb19ea3f4be9c8066276ecc236a8ed071623d3f13373424c56a5ddb0013be590b641ac99bf8f9b4e19 + languageName: node + linkType: hard + "zwitch@npm:^2.0.0": version: 2.0.4 resolution: "zwitch@npm:2.0.4" From 4e7e5409564aeaf4d437900e34cf10e711f90dcd Mon Sep 17 00:00:00 2001 From: FARAN Date: Tue, 1 Jul 2025 12:57:27 +0500 Subject: [PATCH 04/18] add assistant-ui components --- client/packages/lowcoder/package.json | 1 + .../components/assistant-ui/markdown-text.tsx | 130 ++++++++ .../components/assistant-ui/thread-list.tsx | 66 ++++ .../components/assistant-ui/thread.tsx | 283 ++++++++++++++++++ .../assistant-ui/tooltip-icon-button.tsx | 42 +++ .../comps/chatComp/components/ui/button.tsx | 45 +++ .../comps/chatComp/components/ui/tooltip.tsx | 29 ++ .../src/comps/comps/chatComp/utils/cn.ts | 5 + client/yarn.lock | 12 +- 9 files changed, 612 insertions(+), 1 deletion(-) create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/markdown-text.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/tooltip-icon-button.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/ui/button.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/ui/tooltip.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/utils/cn.ts diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index 3460b19ca3..04a4c30535 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -50,6 +50,7 @@ "antd": "^5.25.2", "axios": "^1.7.7", "buffer": "^6.0.3", + "class-variance-authority": "^0.7.1", "clsx": "^2.0.0", "cnchar": "^3.2.4", "coolshapes-react": "lowcoder-org/coolshapes-react", diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/markdown-text.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/markdown-text.tsx new file mode 100644 index 0000000000..bbf2e5648a --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/markdown-text.tsx @@ -0,0 +1,130 @@ +import "@assistant-ui/react-markdown/styles/dot.css"; + +import { + CodeHeaderProps, + MarkdownTextPrimitive, + unstable_memoizeMarkdownComponents as memoizeMarkdownComponents, + useIsMarkdownCodeBlock, +} from "@assistant-ui/react-markdown"; +import remarkGfm from "remark-gfm"; +import { FC, memo, useState } from "react"; +import { CheckIcon, CopyIcon } from "lucide-react"; + +import { TooltipIconButton } from "./tooltip-icon-button"; +import { cn } from "../../utils/cn"; + +const MarkdownTextImpl = () => { + return ( + + ); +}; + +export const MarkdownText = memo(MarkdownTextImpl); + +const CodeHeader: FC = ({ language, code }) => { + const { isCopied, copyToClipboard } = useCopyToClipboard(); + const onCopy = () => { + if (!code || isCopied) return; + copyToClipboard(code); + }; + + return ( +
+ {language} + + {!isCopied && } + {isCopied && } + +
+ ); +}; + +const useCopyToClipboard = ({ + copiedDuration = 3000, +}: { + copiedDuration?: number; +} = {}) => { + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = (value: string) => { + if (!value) return; + + navigator.clipboard.writeText(value).then(() => { + setIsCopied(true); + setTimeout(() => setIsCopied(false), copiedDuration); + }); + }; + + return { isCopied, copyToClipboard }; +}; + +const defaultComponents = memoizeMarkdownComponents({ + h1: ({ className, ...props }) => ( +

+ ), + h2: ({ className, ...props }) => ( +

+ ), + h3: ({ className, ...props }) => ( +

+ ), + h4: ({ className, ...props }) => ( +

+ ), + h5: ({ className, ...props }) => ( +

+ ), + h6: ({ className, ...props }) => ( +
+ ), + p: ({ className, ...props }) => ( +

+ ), + a: ({ className, ...props }) => ( + + ), + blockquote: ({ className, ...props }) => ( +

+ ), + ul: ({ className, ...props }) => ( +
    + ), + ol: ({ className, ...props }) => ( +
      + ), + hr: ({ className, ...props }) => ( +
      + ), + table: ({ className, ...props }) => ( + + ), + th: ({ className, ...props }) => ( + + ), + sup: ({ className, ...props }) => ( + + ), + pre: ({ className, ...props }) => ( +
      +  ),
      +  code: function Code({ className, ...props }) {
      +    const isCodeBlock = useIsMarkdownCodeBlock();
      +    return (
      +      
      +    );
      +  },
      +  CodeHeader,
      +});
      \ No newline at end of file
      diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx
      new file mode 100644
      index 0000000000..b44abaf0b7
      --- /dev/null
      +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx
      @@ -0,0 +1,66 @@
      +import type { FC } from "react";
      +import {
      +  ThreadListItemPrimitive,
      +  ThreadListPrimitive,
      +} from "@assistant-ui/react";
      +import { ArchiveIcon, PlusIcon } from "lucide-react";
      +
      +import { Button } from "../ui/button";
      +import { TooltipIconButton } from "./tooltip-icon-button";
      +
      +export const ThreadList: FC = () => {
      +  return (
      +    
      +      
      +      
      +    
      +  );
      +};
      +
      +const ThreadListNew: FC = () => {
      +  return (
      +    
      +      
      +    
      +  );
      +};
      +
      +const ThreadListItems: FC = () => {
      +  return ;
      +};
      +
      +const ThreadListItem: FC = () => {
      +  return (
      +    
      +      
      +        
      +      
      +      
      +    
      +  );
      +};
      +
      +const ThreadListItemTitle: FC = () => {
      +  return (
      +    

      + +

      + ); +}; + +const ThreadListItemArchive: FC = () => { + return ( + + + + + + ); +}; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx new file mode 100644 index 0000000000..95b0d0a2b8 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx @@ -0,0 +1,283 @@ +import { + ActionBarPrimitive, + BranchPickerPrimitive, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, + } from "@assistant-ui/react"; + import type { FC } from "react"; + import { + ArrowDownIcon, + CheckIcon, + ChevronLeftIcon, + ChevronRightIcon, + CopyIcon, + PencilIcon, + RefreshCwIcon, + SendHorizontalIcon, + } from "lucide-react"; + import { cn } from "../../utils/cn"; + + import { Button } from "../ui/button"; + import { MarkdownText } from "./markdown-text"; + import { TooltipIconButton } from "./tooltip-icon-button"; + + export const Thread: FC = () => { + return ( + + + + + + + +
      + + +
      + + +
      + + + ); + }; + + const ThreadScrollToBottom: FC = () => { + return ( + + + + + + ); + }; + + const ThreadWelcome: FC = () => { + return ( + +
      +
      +

      + How can I help you today? +

      +
      + +
      +
      + ); + }; + + const ThreadWelcomeSuggestions: FC = () => { + return ( +
      + + + What is the weather in Tokyo? + + + + + What is assistant-ui? + + +
      + ); + }; + + const Composer: FC = () => { + return ( + + + + + ); + }; + + const ComposerAction: FC = () => { + return ( + <> + + + + + + + + + + + + + + + + ); + }; + + const UserMessage: FC = () => { + return ( + + + +
      + +
      + + +
      + ); + }; + + const UserActionBar: FC = () => { + return ( + + + + + + + + ); + }; + + const EditComposer: FC = () => { + return ( + + + +
      + + + + + + +
      +
      + ); + }; + + const AssistantMessage: FC = () => { + return ( + +
      + +
      + + + + +
      + ); + }; + + const AssistantActionBar: FC = () => { + return ( + + + + + + + + + + + + + + + + + + ); + }; + + const BranchPicker: FC = ({ + className, + ...rest + }) => { + return ( + + + + + + + + / + + + + + + + + ); + }; + + const CircleStopIcon = () => { + return ( + + + + ); + }; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/tooltip-icon-button.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/tooltip-icon-button.tsx new file mode 100644 index 0000000000..d2434babff --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/tooltip-icon-button.tsx @@ -0,0 +1,42 @@ +import { ComponentPropsWithoutRef, forwardRef } from "react"; + +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "../ui/tooltip"; +import { Button } from "../ui/button"; +import { cn } from "../../utils/cn"; + +export type TooltipIconButtonProps = ComponentPropsWithoutRef & { + tooltip: string; + side?: "top" | "bottom" | "left" | "right"; +}; + +export const TooltipIconButton = forwardRef< + HTMLButtonElement, + TooltipIconButtonProps +>(({ children, tooltip, side = "bottom", className, ...rest }, ref) => { + return ( + + + + + + {tooltip} + + + ); +}); + +TooltipIconButton.displayName = "TooltipIconButton"; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/button.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/button.tsx new file mode 100644 index 0000000000..4406b74e67 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/button.tsx @@ -0,0 +1,45 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "../../utils/cn"; + +const buttonVariants = cva("aui-button", { + variants: { + variant: { + default: "aui-button-primary", + outline: "aui-button-outline", + ghost: "aui-button-ghost", + }, + size: { + default: "aui-button-medium", + icon: "aui-button-icon", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, +}); + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : "button"; + + return ( + + ); +} + +export { Button, buttonVariants }; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/tooltip.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/tooltip.tsx new file mode 100644 index 0000000000..ede610e327 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/tooltip.tsx @@ -0,0 +1,29 @@ +"use client"; + +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "../../utils/cn"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/utils/cn.ts b/client/packages/lowcoder/src/comps/comps/chatComp/utils/cn.ts new file mode 100644 index 0000000000..5ba370c74d --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/utils/cn.ts @@ -0,0 +1,5 @@ +import { type ClassValue, clsx } from "clsx"; + +export function cn(...inputs: ClassValue[]) { + return clsx(inputs); +} \ No newline at end of file diff --git a/client/yarn.lock b/client/yarn.lock index de7a2c227b..e8357b3c08 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -7962,6 +7962,15 @@ __metadata: languageName: node linkType: hard +"class-variance-authority@npm:^0.7.1": + version: 0.7.1 + resolution: "class-variance-authority@npm:0.7.1" + dependencies: + clsx: ^2.1.1 + checksum: e05ba26ef9ec38f7c675047ce366b067d60af6c954dba08f7802af19a9460a534ae752d8fe1294fff99d0fa94a669b16ccebd87e8a20f637c0736cf2751dd2c5 + languageName: node + linkType: hard + "classnames@npm:2.x, classnames@npm:^2.2.1, classnames@npm:^2.2.3, classnames@npm:^2.2.5, classnames@npm:^2.2.6, classnames@npm:^2.3.1, classnames@npm:^2.3.2, classnames@npm:^2.5.1": version: 2.5.1 resolution: "classnames@npm:2.5.1" @@ -8051,7 +8060,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^2.0.0": +"clsx@npm:^2.0.0, clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" checksum: acd3e1ab9d8a433ecb3cc2f6a05ab95fe50b4a3cfc5ba47abb6cbf3754585fcb87b84e90c822a1f256c4198e3b41c7f6c391577ffc8678ad587fc0976b24fd57 @@ -14894,6 +14903,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: antd: ^5.25.2 axios: ^1.7.7 buffer: ^6.0.3 + class-variance-authority: ^0.7.1 clsx: ^2.0.0 cnchar: ^3.2.4 coolshapes-react: lowcoder-org/coolshapes-react From c54139368e647295474fd42538691cdb40dcd583 Mon Sep 17 00:00:00 2001 From: FARAN Date: Tue, 1 Jul 2025 13:09:26 +0500 Subject: [PATCH 05/18] fix linter error --- .../comps/comps/chatComp/components/assistant-ui/thread.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx index 95b0d0a2b8..ae3749fb77 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx @@ -159,7 +159,7 @@ import {
      - +
      @@ -204,7 +204,7 @@ import { return (
      - +
      From bf3810f918938d9e42c3f7e10773761a43d12b5e Mon Sep 17 00:00:00 2001 From: FARAN Date: Tue, 1 Jul 2025 14:17:33 +0500 Subject: [PATCH 06/18] add thread --- .../src/comps/comps/chatComp/chatView.tsx | 63 +------------------ 1 file changed, 2 insertions(+), 61 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx index 6ca388777f..28285b7827 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx @@ -1,12 +1,11 @@ // client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx import React from "react"; import { ChatCompProps } from "./chatCompTypes"; +import { Thread } from "./components/assistant-ui/thread"; // Import assistant-ui components and proper runtime import { AssistantRuntimeProvider, - ThreadPrimitive, - ComposerPrimitive } from "@assistant-ui/react"; import { useChatRuntime } from "@assistant-ui/react-ai-sdk"; import "@assistant-ui/styles/index.css"; @@ -20,65 +19,7 @@ export const ChatView = React.memo((props: ChatCompProps) => { return ( -
      -
      -

      🚀 Assistant-UI with Vercel AI SDK!

      - - {/* Test Thread with real runtime */} -
      - -
      -
      -

      - {props.text} - Runtime Working! 🎉 -

      -
      -
      -
      -
      - - {/* Test Composer with real runtime */} -
      - -
      - - {/* Property status */} -
      - ✅ Test Status:
      - Text: {props.text}
      - Runtime: Vercel AI SDK ✅ -
      -
      -
      +
      ); }); From 11c98fd1119c3fc76432be5d559a197a79717116 Mon Sep 17 00:00:00 2001 From: FARAN Date: Tue, 1 Jul 2025 15:36:12 +0500 Subject: [PATCH 07/18] add properties for chat component --- .../src/comps/comps/chatComp/chatCompTypes.ts | 22 ++++++++++++++++++- .../comps/comps/chatComp/chatPropertyView.tsx | 12 +++++++--- .../src/comps/comps/chatComp/chatView.tsx | 9 ++++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts b/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts index a58a0eda55..147f4dee1b 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts @@ -1,11 +1,31 @@ // client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts -import { StringControl } from "comps/controls/codeControl"; +import { StringControl, NumberControl } from "comps/controls/codeControl"; import { withDefault } from "comps/generators"; +import { BoolControl } from "comps/controls/boolControl"; +import { dropdownControl } from "comps/controls/dropdownControl"; + +// Model type dropdown options +const ModelTypeOptions = [ + { label: "Direct LLM", value: "direct-llm" }, + { label: "n8n Workflow", value: "n8n" }, +] as const; export const chatChildrenMap = { text: withDefault(StringControl, "Chat Component Placeholder"), + modelHost: withDefault(StringControl, "http://localhost:11434"), + modelType: dropdownControl(ModelTypeOptions, "direct-llm"), + streaming: BoolControl.DEFAULT_TRUE, + systemPrompt: withDefault(StringControl, "You are a helpful assistant."), + agent: BoolControl, + maxInteractions: withDefault(NumberControl, 10), }; export type ChatCompProps = { text: string; + modelHost: string; + modelType: "direct-llm" | "n8n"; + streaming: boolean; + systemPrompt: string; + agent: boolean; + maxInteractions: number; }; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx index e31f6268de..510e87ac2e 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx @@ -3,11 +3,17 @@ import React from "react"; import { Section, sectionNames } from "lowcoder-design"; export const ChatPropertyView = React.memo((props: any) => { + const { children } = props; + return (
      - {props.children.text.propertyView({ - label: "Text" - })} + {children.text.propertyView({ label: "Text" })} + {children.modelHost.propertyView({ label: "Model Host URL" })} + {children.modelType.propertyView({ label: "Model Type" })} + {children.streaming.propertyView({ label: "Streaming Responses" })} + {children.systemPrompt.propertyView({ label: "System Prompt" })} + {children.agent.propertyView({ label: "Agent Mode" })} + {children.maxInteractions.propertyView({ label: "Max Interactions" })}
      ); }); diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx index 28285b7827..0b74094b40 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx @@ -14,8 +14,13 @@ import "@assistant-ui/styles/markdown.css"; export const ChatView = React.memo((props: ChatCompProps) => { // Create proper runtime using useChatRuntime const runtime = useChatRuntime({ - api: "/api/chat", // We'll create this endpoint later - }); + api: props.modelHost, + stream: props.streaming, + modelType: props.modelType as any, + systemPrompt: props.systemPrompt, + agent: props.agent, + maxTurns: props.maxInteractions, + } as any); return ( From aec9485818d9c70e98b5d0b2b9e8b8a7c83fa249 Mon Sep 17 00:00:00 2001 From: FARAN Date: Wed, 2 Jul 2025 12:14:30 +0500 Subject: [PATCH 08/18] add query / new hook from assisstant ui --- .../src/comps/comps/chatComp/chatComp.tsx | 2 +- .../src/comps/comps/chatComp/chatCompTypes.ts | 7 +- .../comps/comps/chatComp/chatPropertyView.tsx | 17 ++- .../src/comps/comps/chatComp/chatView.tsx | 121 ++++++++++++++++-- 4 files changed, 126 insertions(+), 21 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx index e9c395dd2d..75de96494a 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx @@ -8,7 +8,7 @@ import { ChatPropertyView } from "./chatPropertyView"; // Build the component const ChatTmpComp = new UICompBuilder( chatChildrenMap, - (props) => + (props) => ) .setPropertyViewFn((children) => ) .build(); diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts b/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts index 147f4dee1b..79ba4c80dc 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts @@ -3,6 +3,7 @@ import { StringControl, NumberControl } from "comps/controls/codeControl"; import { withDefault } from "comps/generators"; import { BoolControl } from "comps/controls/boolControl"; import { dropdownControl } from "comps/controls/dropdownControl"; +import QuerySelectControl from "comps/controls/querySelectControl"; // Model type dropdown options const ModelTypeOptions = [ @@ -12,7 +13,7 @@ const ModelTypeOptions = [ export const chatChildrenMap = { text: withDefault(StringControl, "Chat Component Placeholder"), - modelHost: withDefault(StringControl, "http://localhost:11434"), + chatQuery: QuerySelectControl, modelType: dropdownControl(ModelTypeOptions, "direct-llm"), streaming: BoolControl.DEFAULT_TRUE, systemPrompt: withDefault(StringControl, "You are a helpful assistant."), @@ -22,8 +23,8 @@ export const chatChildrenMap = { export type ChatCompProps = { text: string; - modelHost: string; - modelType: "direct-llm" | "n8n"; + chatQuery: string; + modelType: string; streaming: boolean; systemPrompt: string; agent: boolean; diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx index 510e87ac2e..b4f42c8e17 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatPropertyView.tsx @@ -8,12 +8,19 @@ export const ChatPropertyView = React.memo((props: any) => { return (
      {children.text.propertyView({ label: "Text" })} - {children.modelHost.propertyView({ label: "Model Host URL" })} + {children.chatQuery.propertyView({ label: "Chat Query" })} {children.modelType.propertyView({ label: "Model Type" })} - {children.streaming.propertyView({ label: "Streaming Responses" })} - {children.systemPrompt.propertyView({ label: "System Prompt" })} - {children.agent.propertyView({ label: "Agent Mode" })} - {children.maxInteractions.propertyView({ label: "Max Interactions" })} + {children.streaming.propertyView({ label: "Enable Streaming" })} + {children.systemPrompt.propertyView({ + label: "System Prompt", + placeholder: "Enter system prompt...", + enableSpellCheck: false, + })} + {children.agent.propertyView({ label: "Enable Agent Mode" })} + {children.maxInteractions.propertyView({ + label: "Max Interactions", + placeholder: "10", + })}
      ); }); diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx index 0b74094b40..c6db6caa2e 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx @@ -1,26 +1,123 @@ // client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx -import React from "react"; +import React, { useState, useContext } from "react"; import { ChatCompProps } from "./chatCompTypes"; import { Thread } from "./components/assistant-ui/thread"; -// Import assistant-ui components and proper runtime +// Import assistant-ui components for external store runtime import { AssistantRuntimeProvider, + useExternalStoreRuntime, + ThreadMessageLike, + AppendMessage } from "@assistant-ui/react"; -import { useChatRuntime } from "@assistant-ui/react-ai-sdk"; import "@assistant-ui/styles/index.css"; import "@assistant-ui/styles/markdown.css"; +// Import Lowcoder query execution +import { EditorContext } from "comps/editorState"; +import { executeQueryAction, routeByNameAction } from "lowcoder-core"; + +const convertMessage = (message: ThreadMessageLike) => { + return message; +}; + export const ChatView = React.memo((props: ChatCompProps) => { - // Create proper runtime using useChatRuntime - const runtime = useChatRuntime({ - api: props.modelHost, - stream: props.streaming, - modelType: props.modelType as any, - systemPrompt: props.systemPrompt, - agent: props.agent, - maxTurns: props.maxInteractions, - } as any); + const [messages, setMessages] = useState([]); + const editorState = useContext(EditorContext); + + const onNew = async (message: AppendMessage) => { + if (message.content.length !== 1 || message.content[0]?.type !== "text") { + throw new Error("Only text content is supported"); + } + + // Add user message immediately + const userMessage: ThreadMessageLike = { + role: "user", + content: [{ type: "text", text: message.content[0].text }], + }; + setMessages((currentMessages) => [...currentMessages, userMessage]); + + try { + // Execute the selected Lowcoder query + if (props.chatQuery) { + // Prepare query arguments with chat context + const queryArgs = { + message: message.content[0].text, + systemPrompt: props.systemPrompt, + streaming: props.streaming, + agent: props.agent, + maxInteractions: props.maxInteractions, + modelType: props.modelType, + // Pass entire conversation history for context + messages: messages.concat([userMessage]).map(msg => ({ + role: msg.role, + content: Array.isArray(msg.content) && msg.content[0] && typeof msg.content[0] === "object" && "text" in msg.content[0] + ? msg.content[0].text + : typeof msg.content === "string" ? msg.content : "" + })) + }; + + // Execute the query through Lowcoder's query system + const result = await new Promise((resolve, reject) => { + const queryComp = editorState?.getQueriesComp() + .getView() + .find(q => q.children.name.getView() === props.chatQuery); + + if (!queryComp) { + reject(new Error(`Query "${props.chatQuery}" not found`)); + return; + } + + queryComp.dispatch( + executeQueryAction({ + args: queryArgs, + afterExecFunc: () => { + const queryResult = queryComp.children.data.getView(); + resolve(queryResult); + } + }) + ); + }); + + // Add assistant response + const assistantMessage: ThreadMessageLike = { + role: "assistant", + content: [{ + type: "text", + text: typeof result === "string" + ? result + : (result as any)?.message || (result as any)?.response || "No response" + }], + }; + setMessages((currentMessages) => [...currentMessages, assistantMessage]); + + } else { + // Fallback response when no query is selected + const assistantMessage: ThreadMessageLike = { + role: "assistant", + content: [{ type: "text", text: "Please select a chat query in the component properties." }], + }; + setMessages((currentMessages) => [...currentMessages, assistantMessage]); + } + } catch (error) { + // Error handling + const errorMessage: ThreadMessageLike = { + role: "assistant", + content: [{ + type: "text", + text: `Error: ${error instanceof Error ? error.message : "Unknown error occurred"}` + }], + }; + setMessages((currentMessages) => [...currentMessages, errorMessage]); + } + }; + + const runtime = useExternalStoreRuntime({ + messages, + setMessages, + onNew, + convertMessage, + }); return ( From b820f8e28865c8212ac8031ed82697dc86f3818e Mon Sep 17 00:00:00 2001 From: FARAN Date: Wed, 2 Jul 2025 15:52:11 +0500 Subject: [PATCH 09/18] use mock data --- .../src/comps/comps/chatComp/chatView.tsx | 117 +----------------- .../components/context/MyRuntimeProvider.tsx | 70 +++++++++++ 2 files changed, 75 insertions(+), 112 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx index c6db6caa2e..3e9f1ced2c 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx @@ -1,128 +1,21 @@ // client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx -import React, { useState, useContext } from "react"; +import React from "react"; import { ChatCompProps } from "./chatCompTypes"; import { Thread } from "./components/assistant-ui/thread"; -// Import assistant-ui components for external store runtime -import { - AssistantRuntimeProvider, - useExternalStoreRuntime, - ThreadMessageLike, - AppendMessage -} from "@assistant-ui/react"; import "@assistant-ui/styles/index.css"; import "@assistant-ui/styles/markdown.css"; -// Import Lowcoder query execution -import { EditorContext } from "comps/editorState"; -import { executeQueryAction, routeByNameAction } from "lowcoder-core"; -const convertMessage = (message: ThreadMessageLike) => { - return message; -}; +import { MyRuntimeProvider } from "./components/context/MyRuntimeProvider"; -export const ChatView = React.memo((props: ChatCompProps) => { - const [messages, setMessages] = useState([]); - const editorState = useContext(EditorContext); - - const onNew = async (message: AppendMessage) => { - if (message.content.length !== 1 || message.content[0]?.type !== "text") { - throw new Error("Only text content is supported"); - } - - // Add user message immediately - const userMessage: ThreadMessageLike = { - role: "user", - content: [{ type: "text", text: message.content[0].text }], - }; - setMessages((currentMessages) => [...currentMessages, userMessage]); - - try { - // Execute the selected Lowcoder query - if (props.chatQuery) { - // Prepare query arguments with chat context - const queryArgs = { - message: message.content[0].text, - systemPrompt: props.systemPrompt, - streaming: props.streaming, - agent: props.agent, - maxInteractions: props.maxInteractions, - modelType: props.modelType, - // Pass entire conversation history for context - messages: messages.concat([userMessage]).map(msg => ({ - role: msg.role, - content: Array.isArray(msg.content) && msg.content[0] && typeof msg.content[0] === "object" && "text" in msg.content[0] - ? msg.content[0].text - : typeof msg.content === "string" ? msg.content : "" - })) - }; - - // Execute the query through Lowcoder's query system - const result = await new Promise((resolve, reject) => { - const queryComp = editorState?.getQueriesComp() - .getView() - .find(q => q.children.name.getView() === props.chatQuery); - if (!queryComp) { - reject(new Error(`Query "${props.chatQuery}" not found`)); - return; - } - - queryComp.dispatch( - executeQueryAction({ - args: queryArgs, - afterExecFunc: () => { - const queryResult = queryComp.children.data.getView(); - resolve(queryResult); - } - }) - ); - }); - - // Add assistant response - const assistantMessage: ThreadMessageLike = { - role: "assistant", - content: [{ - type: "text", - text: typeof result === "string" - ? result - : (result as any)?.message || (result as any)?.response || "No response" - }], - }; - setMessages((currentMessages) => [...currentMessages, assistantMessage]); - - } else { - // Fallback response when no query is selected - const assistantMessage: ThreadMessageLike = { - role: "assistant", - content: [{ type: "text", text: "Please select a chat query in the component properties." }], - }; - setMessages((currentMessages) => [...currentMessages, assistantMessage]); - } - } catch (error) { - // Error handling - const errorMessage: ThreadMessageLike = { - role: "assistant", - content: [{ - type: "text", - text: `Error: ${error instanceof Error ? error.message : "Unknown error occurred"}` - }], - }; - setMessages((currentMessages) => [...currentMessages, errorMessage]); - } - }; - - const runtime = useExternalStoreRuntime({ - messages, - setMessages, - onNew, - convertMessage, - }); +export const ChatView = React.memo((props: ChatCompProps) => { return ( - + - + ); }); diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx new file mode 100644 index 0000000000..b8fbbc2740 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx @@ -0,0 +1,70 @@ +import { useState } from "react"; +import { + useExternalStoreRuntime, + ThreadMessageLike, + AppendMessage, + AssistantRuntimeProvider, +} from "@assistant-ui/react"; + +const callYourAPI = async (message: AppendMessage) => { + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 1500)); + + // Simple responses + return { + content: "This is a mock response from your backend. You typed: " + + (typeof message.content === 'string' ? message.content : 'something') + }; + }; + +export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { + const [messages, setMessages] = useState([]); + const [isRunning, setIsRunning] = useState(false); + + const onNew = async (message: AppendMessage) => { + // Add user message + const userMessage: ThreadMessageLike = { + role: "user", + content: message.content, + }; + + setMessages(prev => [...prev, userMessage]); + setIsRunning(true); + + try { + // Call mock API + const response = await callYourAPI(message); + + const assistantMessage: ThreadMessageLike = { + role: "assistant", + content: response.content, + }; + + setMessages(prev => [...prev, assistantMessage]); + } catch (error) { + // Handle errors gracefully + const errorMessage: ThreadMessageLike = { + role: "assistant", + content: `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}. This is expected in mock mode for testing error handling.`, + }; + + setMessages(prev => [...prev, errorMessage]); + } finally { + setIsRunning(false); + } + }; + + const runtime = useExternalStoreRuntime({ + messages, + setMessages, + isRunning, + onNew, + convertMessage: (message) => message, + }); + + return ( + + {children} + + ); + } \ No newline at end of file From 0dc85d4579796d631889fb8572a09fa791cc9acc Mon Sep 17 00:00:00 2001 From: FARAN Date: Wed, 2 Jul 2025 16:45:00 +0500 Subject: [PATCH 10/18] add edit functionality --- .../components/context/MyRuntimeProvider.tsx | 108 +++++++++++++++--- 1 file changed, 91 insertions(+), 17 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx index b8fbbc2740..cb50e25016 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx @@ -6,6 +6,16 @@ import { AssistantRuntimeProvider, } from "@assistant-ui/react"; +// Define your custom message type +interface MyMessage { + id: string; + role: "user" | "assistant"; + text: string; + timestamp: number; +} + +const generateId = () => Math.random().toString(36).substr(2, 9); + const callYourAPI = async (message: AppendMessage) => { // Simulate API delay await new Promise(resolve => setTimeout(resolve, 1500)); @@ -15,51 +25,115 @@ const callYourAPI = async (message: AppendMessage) => { content: "This is a mock response from your backend. You typed: " + (typeof message.content === 'string' ? message.content : 'something') }; - }; +}; export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { - const [messages, setMessages] = useState([]); + // Use your custom message type in state + const [myMessages, setMyMessages] = useState([]); const [isRunning, setIsRunning] = useState(false); + + // Convert your custom format to ThreadMessageLike + const convertMessage = (message: MyMessage): ThreadMessageLike => ({ + role: message.role, + content: [{ type: "text", text: message.text }], + id: message.id, + createdAt: new Date(message.timestamp), + }); const onNew = async (message: AppendMessage) => { - // Add user message - const userMessage: ThreadMessageLike = { + // Add user message in your custom format + const userMessage: MyMessage = { + id: generateId(), role: "user", - content: message.content, + text: typeof message.content === 'string' ? message.content : JSON.stringify(message.content), + timestamp: Date.now(), }; - setMessages(prev => [...prev, userMessage]); + setMyMessages(prev => [...prev, userMessage]); setIsRunning(true); try { // Call mock API const response = await callYourAPI(message); - const assistantMessage: ThreadMessageLike = { + const assistantMessage: MyMessage = { + id: generateId(), + role: "assistant", + text: response.content, + timestamp: Date.now(), + }; + + setMyMessages(prev => [...prev, assistantMessage]); + } catch (error) { + // Handle errors gracefully + const errorMessage: MyMessage = { + id: generateId(), + role: "assistant", + text: `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}. This is expected in mock mode for testing error handling.`, + timestamp: Date.now(), + }; + + setMyMessages(prev => [...prev, errorMessage]); + } finally { + setIsRunning(false); + } + }; + + // Add onEdit functionality + const onEdit = async (message: AppendMessage) => { + // Find the index where to insert the edited message + const index = myMessages.findIndex((m) => m.id === message.parentId) + 1; + + // Keep messages up to the parent + const newMessages = [...myMessages.slice(0, index)]; + + // Add the edited message in your custom format + const editedMessage: MyMessage = { + id: generateId(), // Always generate new ID for edited messages + role: "user", + text: typeof message.content === 'string' ? message.content : JSON.stringify(message.content), + timestamp: Date.now(), + }; + newMessages.push(editedMessage); + + setMyMessages(newMessages); + setIsRunning(true); + + try { + // Generate new response + const response = await callYourAPI(message); + + const assistantMessage: MyMessage = { + id: generateId(), role: "assistant", - content: response.content, + text: response.content, + timestamp: Date.now(), }; - setMessages(prev => [...prev, assistantMessage]); + newMessages.push(assistantMessage); + setMyMessages(newMessages); } catch (error) { // Handle errors gracefully - const errorMessage: ThreadMessageLike = { + const errorMessage: MyMessage = { + id: generateId(), role: "assistant", - content: `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}. This is expected in mock mode for testing error handling.`, + text: `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}`, + timestamp: Date.now(), }; - setMessages(prev => [...prev, errorMessage]); + newMessages.push(errorMessage); + setMyMessages(newMessages); } finally { setIsRunning(false); } }; - const runtime = useExternalStoreRuntime({ - messages, - setMessages, + const runtime = useExternalStoreRuntime({ + messages: myMessages, // Your custom message array + convertMessage, // Conversion function isRunning, onNew, - convertMessage: (message) => message, + onEdit, // Enable message editing }); return ( @@ -67,4 +141,4 @@ export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { {children} ); - } \ No newline at end of file +} \ No newline at end of file From fd9dc7777d73c8cbbd2f0bbbf86a2aa3da99e857 Mon Sep 17 00:00:00 2001 From: FARAN Date: Wed, 2 Jul 2025 16:50:11 +0500 Subject: [PATCH 11/18] fix message json issue --- .../components/context/MyRuntimeProvider.tsx | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx index cb50e25016..84070c5caf 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx @@ -16,14 +16,13 @@ interface MyMessage { const generateId = () => Math.random().toString(36).substr(2, 9); -const callYourAPI = async (message: AppendMessage) => { +const callYourAPI = async (text: string) => { // Simulate API delay await new Promise(resolve => setTimeout(resolve, 1500)); // Simple responses return { - content: "This is a mock response from your backend. You typed: " + - (typeof message.content === 'string' ? message.content : 'something') + content: "This is a mock response from your backend. You typed: " + text }; }; @@ -41,11 +40,16 @@ export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { }); const onNew = async (message: AppendMessage) => { + // Extract text from AppendMessage content array + if (message.content.length !== 1 || message.content[0]?.type !== "text") { + throw new Error("Only text content is supported"); + } + // Add user message in your custom format const userMessage: MyMessage = { id: generateId(), role: "user", - text: typeof message.content === 'string' ? message.content : JSON.stringify(message.content), + text: message.content[0].text, timestamp: Date.now(), }; @@ -54,7 +58,7 @@ export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { try { // Call mock API - const response = await callYourAPI(message); + const response = await callYourAPI(userMessage.text); const assistantMessage: MyMessage = { id: generateId(), @@ -81,6 +85,11 @@ export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { // Add onEdit functionality const onEdit = async (message: AppendMessage) => { + // Extract text from AppendMessage content array + if (message.content.length !== 1 || message.content[0]?.type !== "text") { + throw new Error("Only text content is supported"); + } + // Find the index where to insert the edited message const index = myMessages.findIndex((m) => m.id === message.parentId) + 1; @@ -91,7 +100,7 @@ export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { const editedMessage: MyMessage = { id: generateId(), // Always generate new ID for edited messages role: "user", - text: typeof message.content === 'string' ? message.content : JSON.stringify(message.content), + text: message.content[0].text, timestamp: Date.now(), }; newMessages.push(editedMessage); @@ -101,7 +110,7 @@ export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { try { // Generate new response - const response = await callYourAPI(message); + const response = await callYourAPI(editedMessage.text); const assistantMessage: MyMessage = { id: generateId(), From 52cdcf2060f59db8306c7936423639e5bf15001d Mon Sep 17 00:00:00 2001 From: FARAN Date: Wed, 2 Jul 2025 17:59:02 +0500 Subject: [PATCH 12/18] add threads logic --- .../src/comps/comps/chatComp/chatView.tsx | 13 +- .../chatComp/components/ChatWithThreads.tsx | 246 ++++++++++++++++++ .../components/context/MyRuntimeProvider.tsx | 2 + .../components/context/ThreadContext.tsx | 53 ++++ 4 files changed, 303 insertions(+), 11 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/context/ThreadContext.tsx diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx index 3e9f1ced2c..0af26ffe25 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx @@ -1,22 +1,13 @@ // client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx import React from "react"; import { ChatCompProps } from "./chatCompTypes"; -import { Thread } from "./components/assistant-ui/thread"; +import { ChatApp } from "./components/ChatWithThreads"; import "@assistant-ui/styles/index.css"; import "@assistant-ui/styles/markdown.css"; - -import { MyRuntimeProvider } from "./components/context/MyRuntimeProvider"; - - - export const ChatView = React.memo((props: ChatCompProps) => { - return ( - - - - ); + return ; }); ChatView.displayName = 'ChatView'; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx new file mode 100644 index 0000000000..01913e3102 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx @@ -0,0 +1,246 @@ +import React, { useState } from "react"; +import { + useExternalStoreRuntime, + ThreadMessageLike, + AppendMessage, + AssistantRuntimeProvider, + ExternalStoreThreadListAdapter, +} from "@assistant-ui/react"; +import { useThreadContext, MyMessage, ThreadProvider } from "./context/ThreadContext"; +import { Thread } from "./assistant-ui/thread"; +import { ThreadList } from "./assistant-ui/thread-list"; + +// Define thread data interfaces to match ExternalStoreThreadData requirements +interface RegularThreadData { + threadId: string; + status: "regular"; + title: string; +} + +interface ArchivedThreadData { + threadId: string; + status: "archived"; + title: string; +} + +type ThreadData = RegularThreadData | ArchivedThreadData; + +const generateId = () => Math.random().toString(36).substr(2, 9); + +const callYourAPI = async (text: string) => { + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 1500)); + + // Simple responses + return { + content: "This is a mock response from your backend. You typed: " + text + }; +}; + +function ChatWithThreads() { + const { currentThreadId, setCurrentThreadId, threads, setThreads } = + useThreadContext(); + const [isRunning, setIsRunning] = useState(false); + const [threadList, setThreadList] = useState([ + { threadId: "default", status: "regular", title: "New Chat" } as RegularThreadData, + ]); + + // Get messages for current thread + const currentMessages = threads.get(currentThreadId) || []; + + // Convert custom format to ThreadMessageLike + const convertMessage = (message: MyMessage): ThreadMessageLike => ({ + role: message.role, + content: [{ type: "text", text: message.text }], + id: message.id, + createdAt: new Date(message.timestamp), + }); + + const onNew = async (message: AppendMessage) => { + // Extract text from AppendMessage content array + if (message.content.length !== 1 || message.content[0]?.type !== "text") { + throw new Error("Only text content is supported"); + } + + // Add user message in custom format + const userMessage: MyMessage = { + id: generateId(), + role: "user", + text: message.content[0].text, + timestamp: Date.now(), + }; + + // Update current thread with new user message + const updatedMessages = [...currentMessages, userMessage]; + setThreads(prev => new Map(prev).set(currentThreadId, updatedMessages)); + setIsRunning(true); + + try { + // Call mock API + const response = await callYourAPI(userMessage.text); + + const assistantMessage: MyMessage = { + id: generateId(), + role: "assistant", + text: response.content, + timestamp: Date.now(), + }; + + // Update current thread with assistant response + const finalMessages = [...updatedMessages, assistantMessage]; + setThreads(prev => new Map(prev).set(currentThreadId, finalMessages)); + } catch (error) { + // Handle errors gracefully + const errorMessage: MyMessage = { + id: generateId(), + role: "assistant", + text: `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}`, + timestamp: Date.now(), + }; + + const finalMessages = [...updatedMessages, errorMessage]; + setThreads(prev => new Map(prev).set(currentThreadId, finalMessages)); + } finally { + setIsRunning(false); + } + }; + + // Add onEdit functionality + const onEdit = async (message: AppendMessage) => { + // Extract text from AppendMessage content array + if (message.content.length !== 1 || message.content[0]?.type !== "text") { + throw new Error("Only text content is supported"); + } + + // Find the index where to insert the edited message + const index = currentMessages.findIndex((m) => m.id === message.parentId) + 1; + + // Keep messages up to the parent + const newMessages = [...currentMessages.slice(0, index)]; + + // Add the edited message in custom format + const editedMessage: MyMessage = { + id: generateId(), + role: "user", + text: message.content[0].text, + timestamp: Date.now(), + }; + newMessages.push(editedMessage); + + setThreads(prev => new Map(prev).set(currentThreadId, newMessages)); + setIsRunning(true); + + try { + // Generate new response + const response = await callYourAPI(editedMessage.text); + + const assistantMessage: MyMessage = { + id: generateId(), + role: "assistant", + text: response.content, + timestamp: Date.now(), + }; + + newMessages.push(assistantMessage); + setThreads(prev => new Map(prev).set(currentThreadId, newMessages)); + } catch (error) { + // Handle errors gracefully + const errorMessage: MyMessage = { + id: generateId(), + role: "assistant", + text: `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}`, + timestamp: Date.now(), + }; + + newMessages.push(errorMessage); + setThreads(prev => new Map(prev).set(currentThreadId, newMessages)); + } finally { + setIsRunning(false); + } + }; + + // Thread list adapter for managing multiple threads + const threadListAdapter: ExternalStoreThreadListAdapter = { + threadId: currentThreadId, + threads: threadList.filter((t): t is RegularThreadData => t.status === "regular"), + archivedThreads: threadList.filter((t): t is ArchivedThreadData => t.status === "archived"), + + onSwitchToNewThread: () => { + const newId = `thread-${Date.now()}`; + setThreadList((prev) => [ + ...prev, + { + threadId: newId, + status: "regular", + title: "New Chat", + } as RegularThreadData, + ]); + setThreads((prev) => new Map(prev).set(newId, [])); + setCurrentThreadId(newId); + }, + + onSwitchToThread: (threadId) => { + setCurrentThreadId(threadId); + }, + + onRename: (threadId, newTitle) => { + setThreadList((prev) => + prev.map((t) => + t.threadId === threadId ? { ...t, title: newTitle } : t, + ), + ); + }, + + onArchive: (threadId) => { + setThreadList((prev) => + prev.map((t) => + t.threadId === threadId ? { ...t, status: "archived" } : t, + ), + ); + }, + + onDelete: (threadId) => { + setThreadList((prev) => prev.filter((t) => t.threadId !== threadId)); + setThreads((prev) => { + const next = new Map(prev); + next.delete(threadId); + return next; + }); + if (currentThreadId === threadId) { + setCurrentThreadId("default"); + } + }, + }; + + const runtime = useExternalStoreRuntime({ + messages: currentMessages, + setMessages: (messages) => { + setThreads((prev) => new Map(prev).set(currentThreadId, messages)); + }, + convertMessage, + isRunning, + onNew, + onEdit, + adapters: { + threadList: threadListAdapter, + }, + }); + + return ( + + + + + ); +} + +// Main App component with proper context wrapping +export function ChatApp() { + return ( + + + + ); +} + +export { ChatWithThreads }; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx index 84070c5caf..81143047ef 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx @@ -4,6 +4,8 @@ import { ThreadMessageLike, AppendMessage, AssistantRuntimeProvider, + ExternalStoreThreadData, + ExternalStoreThreadListAdapter, } from "@assistant-ui/react"; // Define your custom message type diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ThreadContext.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ThreadContext.tsx new file mode 100644 index 0000000000..313833105c --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ThreadContext.tsx @@ -0,0 +1,53 @@ +import React, { createContext, useContext, useState, ReactNode } from "react"; + +// Define thread-specific message type +interface MyMessage { + id: string; + role: "user" | "assistant"; + text: string; + timestamp: number; +} + +// Thread context type +interface ThreadContextType { + currentThreadId: string; + setCurrentThreadId: (id: string) => void; + threads: Map; + setThreads: React.Dispatch>>; +} + +// Create the context +const ThreadContext = createContext({ + currentThreadId: "default", + setCurrentThreadId: () => {}, + threads: new Map(), + setThreads: () => {}, +}); + +// Thread provider component +export function ThreadProvider({ children }: { children: ReactNode }) { + const [threads, setThreads] = useState>( + new Map([["default", []]]), + ); + const [currentThreadId, setCurrentThreadId] = useState("default"); + + return ( + + {children} + + ); +} + +// Hook for accessing thread context +export function useThreadContext() { + const context = useContext(ThreadContext); + if (!context) { + throw new Error("useThreadContext must be used within ThreadProvider"); + } + return context; +} + +// Export the MyMessage type for use in other files +export type { MyMessage }; \ No newline at end of file From c619f89791a0aa50fda1d068a04e1dc05f0c0556 Mon Sep 17 00:00:00 2001 From: FARAN Date: Wed, 2 Jul 2025 20:59:07 +0500 Subject: [PATCH 13/18] add alaSql to chat component --- .../chatComp/components/ChatWithThreads.tsx | 143 ++++++++- .../comps/comps/chatComp/utils/chatStorage.ts | 281 ++++++++++++++++++ 2 files changed, 414 insertions(+), 10 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/utils/chatStorage.ts diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx index 01913e3102..a5e43afc2f 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { useExternalStoreRuntime, ThreadMessageLike, @@ -9,6 +9,7 @@ import { import { useThreadContext, MyMessage, ThreadProvider } from "./context/ThreadContext"; import { Thread } from "./assistant-ui/thread"; import { ThreadList } from "./assistant-ui/thread-list"; +import { chatStorage, ThreadData as StoredThreadData } from "../utils/chatStorage"; // Define thread data interfaces to match ExternalStoreThreadData requirements interface RegularThreadData { @@ -44,6 +45,108 @@ function ChatWithThreads() { const [threadList, setThreadList] = useState([ { threadId: "default", status: "regular", title: "New Chat" } as RegularThreadData, ]); + const [isInitialized, setIsInitialized] = useState(false); + + // Load data from persistent storage on component mount + useEffect(() => { + const loadData = async () => { + try { + await chatStorage.initialize(); + + // Load all threads from storage + const storedThreads = await chatStorage.getAllThreads(); + if (storedThreads.length > 0) { + // Convert stored threads to UI format + const uiThreads: ThreadData[] = storedThreads.map(stored => ({ + threadId: stored.threadId, + status: stored.status as "regular" | "archived", + title: stored.title, + })); + setThreadList(uiThreads); + + // Load messages for each thread + const threadMessages = new Map(); + for (const thread of storedThreads) { + const messages = await chatStorage.getMessages(thread.threadId); + threadMessages.set(thread.threadId, messages); + } + + // Ensure default thread exists + if (!threadMessages.has("default")) { + threadMessages.set("default", []); + } + + setThreads(threadMessages); + + // Set current thread to the most recently updated one + const latestThread = storedThreads.sort((a, b) => b.updatedAt - a.updatedAt)[0]; + if (latestThread) { + setCurrentThreadId(latestThread.threadId); + } + } else { + // Initialize with default thread + const defaultThread: StoredThreadData = { + threadId: "default", + status: "regular", + title: "New Chat", + createdAt: Date.now(), + updatedAt: Date.now(), + }; + await chatStorage.saveThread(defaultThread); + } + + setIsInitialized(true); + } catch (error) { + console.error("Failed to load chat data:", error); + setIsInitialized(true); // Continue with default state + } + }; + + loadData(); + }, [setCurrentThreadId, setThreads]); + + // Save thread data whenever threadList changes + useEffect(() => { + if (!isInitialized) return; + + const saveThreads = async () => { + try { + for (const thread of threadList) { + const storedThread: StoredThreadData = { + threadId: thread.threadId, + status: thread.status, + title: thread.title, + createdAt: Date.now(), // In real app, preserve original createdAt + updatedAt: Date.now(), + }; + await chatStorage.saveThread(storedThread); + } + } catch (error) { + console.error("Failed to save threads:", error); + } + }; + + saveThreads(); + }, [threadList, isInitialized]); + + // Save messages whenever threads change + useEffect(() => { + if (!isInitialized) return; + + const saveMessages = async () => { + try { + for (const [threadId, messages] of threads.entries()) { + await chatStorage.saveMessages(messages, threadId); + } + } catch (error) { + console.error("Failed to save messages:", error); + } + }; + + saveMessages(); + }, [threads, isInitialized]); + + // Get messages for current thread const currentMessages = threads.get(currentThreadId) || []; @@ -165,18 +268,31 @@ function ChatWithThreads() { threads: threadList.filter((t): t is RegularThreadData => t.status === "regular"), archivedThreads: threadList.filter((t): t is ArchivedThreadData => t.status === "archived"), - onSwitchToNewThread: () => { + onSwitchToNewThread: async () => { const newId = `thread-${Date.now()}`; - setThreadList((prev) => [ - ...prev, - { + const newThread: RegularThreadData = { + threadId: newId, + status: "regular", + title: "New Chat", + }; + + setThreadList((prev) => [...prev, newThread]); + setThreads((prev) => new Map(prev).set(newId, [])); + setCurrentThreadId(newId); + + // Save new thread to storage + try { + const storedThread: StoredThreadData = { threadId: newId, status: "regular", title: "New Chat", - } as RegularThreadData, - ]); - setThreads((prev) => new Map(prev).set(newId, [])); - setCurrentThreadId(newId); + createdAt: Date.now(), + updatedAt: Date.now(), + }; + await chatStorage.saveThread(storedThread); + } catch (error) { + console.error("Failed to save new thread:", error); + } }, onSwitchToThread: (threadId) => { @@ -199,7 +315,7 @@ function ChatWithThreads() { ); }, - onDelete: (threadId) => { + onDelete: async (threadId) => { setThreadList((prev) => prev.filter((t) => t.threadId !== threadId)); setThreads((prev) => { const next = new Map(prev); @@ -209,6 +325,13 @@ function ChatWithThreads() { if (currentThreadId === threadId) { setCurrentThreadId("default"); } + + // Delete thread from storage + try { + await chatStorage.deleteThread(threadId); + } catch (error) { + console.error("Failed to delete thread from storage:", error); + } }, }; diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/utils/chatStorage.ts b/client/packages/lowcoder/src/comps/comps/chatComp/utils/chatStorage.ts new file mode 100644 index 0000000000..7e85087e02 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/utils/chatStorage.ts @@ -0,0 +1,281 @@ +import alasql from "alasql"; +import { MyMessage } from "../components/context/ThreadContext"; + +// Database configuration +const DB_NAME = "ChatDB"; +const THREADS_TABLE = "threads"; +const MESSAGES_TABLE = "messages"; + +// Thread data interface +export interface ThreadData { + threadId: string; + status: "regular" | "archived"; + title: string; + createdAt: number; + updatedAt: number; +} + +// Initialize the database +class ChatStorage { + private initialized = false; + + async initialize() { + if (this.initialized) return; + + try { + // Create database with localStorage backend + await alasql.promise(`CREATE LOCALSTORAGE DATABASE IF NOT EXISTS ${DB_NAME}`); + await alasql.promise(`ATTACH LOCALSTORAGE DATABASE ${DB_NAME}`); + await alasql.promise(`USE ${DB_NAME}`); + + // Create threads table + await alasql.promise(` + CREATE TABLE IF NOT EXISTS ${THREADS_TABLE} ( + threadId STRING PRIMARY KEY, + status STRING, + title STRING, + createdAt NUMBER, + updatedAt NUMBER + ) + `); + + // Create messages table + await alasql.promise(` + CREATE TABLE IF NOT EXISTS ${MESSAGES_TABLE} ( + id STRING PRIMARY KEY, + threadId STRING, + role STRING, + text STRING, + timestamp NUMBER + ) + `); + + this.initialized = true; + console.log("Chat database initialized successfully"); + } catch (error) { + console.error("Failed to initialize chat database:", error); + throw error; + } + } + + // Thread operations + async saveThread(thread: ThreadData): Promise { + await this.initialize(); + + try { + // Insert or replace thread + await alasql.promise(` + DELETE FROM ${THREADS_TABLE} WHERE threadId = ? + `, [thread.threadId]); + + await alasql.promise(` + INSERT INTO ${THREADS_TABLE} VALUES (?, ?, ?, ?, ?) + `, [thread.threadId, thread.status, thread.title, thread.createdAt, thread.updatedAt]); + } catch (error) { + console.error("Failed to save thread:", error); + throw error; + } + } + + async getThread(threadId: string): Promise { + await this.initialize(); + + try { + const result = await alasql.promise(` + SELECT * FROM ${THREADS_TABLE} WHERE threadId = ? + `, [threadId]) as ThreadData[]; + + return result && result.length > 0 ? result[0] : null; + } catch (error) { + console.error("Failed to get thread:", error); + return null; + } + } + + async getAllThreads(): Promise { + await this.initialize(); + + try { + const result = await alasql.promise(` + SELECT * FROM ${THREADS_TABLE} ORDER BY updatedAt DESC + `) as ThreadData[]; + + return Array.isArray(result) ? result : []; + } catch (error) { + console.error("Failed to get threads:", error); + return []; + } + } + + async deleteThread(threadId: string): Promise { + await this.initialize(); + + try { + // Delete thread and all its messages + await alasql.promise(`DELETE FROM ${THREADS_TABLE} WHERE threadId = ?`, [threadId]); + await alasql.promise(`DELETE FROM ${MESSAGES_TABLE} WHERE threadId = ?`, [threadId]); + } catch (error) { + console.error("Failed to delete thread:", error); + throw error; + } + } + + // Message operations + async saveMessage(message: MyMessage, threadId: string): Promise { + await this.initialize(); + + try { + // Insert or replace message + await alasql.promise(` + DELETE FROM ${MESSAGES_TABLE} WHERE id = ? + `, [message.id]); + + await alasql.promise(` + INSERT INTO ${MESSAGES_TABLE} VALUES (?, ?, ?, ?, ?) + `, [message.id, threadId, message.role, message.text, message.timestamp]); + } catch (error) { + console.error("Failed to save message:", error); + throw error; + } + } + + async saveMessages(messages: MyMessage[], threadId: string): Promise { + await this.initialize(); + + try { + // Delete existing messages for this thread + await alasql.promise(`DELETE FROM ${MESSAGES_TABLE} WHERE threadId = ?`, [threadId]); + + // Insert all messages + for (const message of messages) { + await alasql.promise(` + INSERT INTO ${MESSAGES_TABLE} VALUES (?, ?, ?, ?, ?) + `, [message.id, threadId, message.role, message.text, message.timestamp]); + } + } catch (error) { + console.error("Failed to save messages:", error); + throw error; + } + } + + async getMessages(threadId: string): Promise { + await this.initialize(); + + try { + const result = await alasql.promise(` + SELECT id, role, text, timestamp FROM ${MESSAGES_TABLE} + WHERE threadId = ? ORDER BY timestamp ASC + `, [threadId]) as MyMessage[]; + + return Array.isArray(result) ? result : []; + } catch (error) { + console.error("Failed to get messages:", error); + return []; + } + } + + async deleteMessages(threadId: string): Promise { + await this.initialize(); + + try { + await alasql.promise(`DELETE FROM ${MESSAGES_TABLE} WHERE threadId = ?`, [threadId]); + } catch (error) { + console.error("Failed to delete messages:", error); + throw error; + } + } + + // Utility methods + async clearAllData(): Promise { + await this.initialize(); + + try { + await alasql.promise(`DELETE FROM ${THREADS_TABLE}`); + await alasql.promise(`DELETE FROM ${MESSAGES_TABLE}`); + } catch (error) { + console.error("Failed to clear all data:", error); + throw error; + } + } + + async resetDatabase(): Promise { + try { + // Drop the entire database + await alasql.promise(`DROP LOCALSTORAGE DATABASE IF EXISTS ${DB_NAME}`); + this.initialized = false; + + // Reinitialize fresh + await this.initialize(); + console.log("✅ Database reset and reinitialized"); + } catch (error) { + console.error("Failed to reset database:", error); + throw error; + } + } + + async clearOnlyMessages(): Promise { + await this.initialize(); + + try { + await alasql.promise(`DELETE FROM ${MESSAGES_TABLE}`); + console.log("✅ All messages cleared, threads preserved"); + } catch (error) { + console.error("Failed to clear messages:", error); + throw error; + } + } + + async clearOnlyThreads(): Promise { + await this.initialize(); + + try { + await alasql.promise(`DELETE FROM ${THREADS_TABLE}`); + await alasql.promise(`DELETE FROM ${MESSAGES_TABLE}`); // Clear orphaned messages + console.log("✅ All threads and messages cleared"); + } catch (error) { + console.error("Failed to clear threads:", error); + throw error; + } + } + + async exportData(): Promise<{ threads: ThreadData[]; messages: any[] }> { + await this.initialize(); + + try { + const threads = await this.getAllThreads(); + const messages = await alasql.promise(`SELECT * FROM ${MESSAGES_TABLE}`) as any[]; + + return { threads, messages: Array.isArray(messages) ? messages : [] }; + } catch (error) { + console.error("Failed to export data:", error); + throw error; + } + } + + async importData(data: { threads: ThreadData[]; messages: any[] }): Promise { + await this.initialize(); + + try { + // Clear existing data + await this.clearAllData(); + + // Import threads + for (const thread of data.threads) { + await this.saveThread(thread); + } + + // Import messages + for (const message of data.messages) { + await alasql.promise(` + INSERT INTO ${MESSAGES_TABLE} VALUES (?, ?, ?, ?, ?) + `, [message.id, message.threadId, message.role, message.text, message.timestamp]); + } + } catch (error) { + console.error("Failed to import data:", error); + throw error; + } + } +} + +// Export singleton instance +export const chatStorage = new ChatStorage(); \ No newline at end of file From f379cac8e62f5f696cf84a9d342bb0beefa2d1af Mon Sep 17 00:00:00 2001 From: FARAN Date: Wed, 2 Jul 2025 21:58:29 +0500 Subject: [PATCH 14/18] delete myruntime provider --- .../components/context/MyRuntimeProvider.tsx | 155 ------------------ 1 file changed, 155 deletions(-) delete mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx deleted file mode 100644 index 81143047ef..0000000000 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/MyRuntimeProvider.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { useState } from "react"; -import { - useExternalStoreRuntime, - ThreadMessageLike, - AppendMessage, - AssistantRuntimeProvider, - ExternalStoreThreadData, - ExternalStoreThreadListAdapter, -} from "@assistant-ui/react"; - -// Define your custom message type -interface MyMessage { - id: string; - role: "user" | "assistant"; - text: string; - timestamp: number; -} - -const generateId = () => Math.random().toString(36).substr(2, 9); - -const callYourAPI = async (text: string) => { - // Simulate API delay - await new Promise(resolve => setTimeout(resolve, 1500)); - - // Simple responses - return { - content: "This is a mock response from your backend. You typed: " + text - }; -}; - -export function MyRuntimeProvider({ children }: { children: React.ReactNode }) { - // Use your custom message type in state - const [myMessages, setMyMessages] = useState([]); - const [isRunning, setIsRunning] = useState(false); - - // Convert your custom format to ThreadMessageLike - const convertMessage = (message: MyMessage): ThreadMessageLike => ({ - role: message.role, - content: [{ type: "text", text: message.text }], - id: message.id, - createdAt: new Date(message.timestamp), - }); - - const onNew = async (message: AppendMessage) => { - // Extract text from AppendMessage content array - if (message.content.length !== 1 || message.content[0]?.type !== "text") { - throw new Error("Only text content is supported"); - } - - // Add user message in your custom format - const userMessage: MyMessage = { - id: generateId(), - role: "user", - text: message.content[0].text, - timestamp: Date.now(), - }; - - setMyMessages(prev => [...prev, userMessage]); - setIsRunning(true); - - try { - // Call mock API - const response = await callYourAPI(userMessage.text); - - const assistantMessage: MyMessage = { - id: generateId(), - role: "assistant", - text: response.content, - timestamp: Date.now(), - }; - - setMyMessages(prev => [...prev, assistantMessage]); - } catch (error) { - // Handle errors gracefully - const errorMessage: MyMessage = { - id: generateId(), - role: "assistant", - text: `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}. This is expected in mock mode for testing error handling.`, - timestamp: Date.now(), - }; - - setMyMessages(prev => [...prev, errorMessage]); - } finally { - setIsRunning(false); - } - }; - - // Add onEdit functionality - const onEdit = async (message: AppendMessage) => { - // Extract text from AppendMessage content array - if (message.content.length !== 1 || message.content[0]?.type !== "text") { - throw new Error("Only text content is supported"); - } - - // Find the index where to insert the edited message - const index = myMessages.findIndex((m) => m.id === message.parentId) + 1; - - // Keep messages up to the parent - const newMessages = [...myMessages.slice(0, index)]; - - // Add the edited message in your custom format - const editedMessage: MyMessage = { - id: generateId(), // Always generate new ID for edited messages - role: "user", - text: message.content[0].text, - timestamp: Date.now(), - }; - newMessages.push(editedMessage); - - setMyMessages(newMessages); - setIsRunning(true); - - try { - // Generate new response - const response = await callYourAPI(editedMessage.text); - - const assistantMessage: MyMessage = { - id: generateId(), - role: "assistant", - text: response.content, - timestamp: Date.now(), - }; - - newMessages.push(assistantMessage); - setMyMessages(newMessages); - } catch (error) { - // Handle errors gracefully - const errorMessage: MyMessage = { - id: generateId(), - role: "assistant", - text: `Sorry, I encountered an error: ${error instanceof Error ? error.message : 'Unknown error'}`, - timestamp: Date.now(), - }; - - newMessages.push(errorMessage); - setMyMessages(newMessages); - } finally { - setIsRunning(false); - } - }; - - const runtime = useExternalStoreRuntime({ - messages: myMessages, // Your custom message array - convertMessage, // Conversion function - isRunning, - onNew, - onEdit, // Enable message editing - }); - - return ( - - {children} - - ); -} \ No newline at end of file From e553d52c769811ec20f5e7909872c7ac56b06572 Mon Sep 17 00:00:00 2001 From: FARAN Date: Thu, 3 Jul 2025 15:54:40 +0500 Subject: [PATCH 15/18] add storage support --- .../chatComp/components/ChatWithThreads.tsx | 141 +++++------------- .../comps/chatComp/hooks/useChatStorage.ts | 139 +++++++++++++++++ 2 files changed, 175 insertions(+), 105 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/hooks/useChatStorage.ts diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx index a5e43afc2f..7a9101a838 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx @@ -10,6 +10,27 @@ import { useThreadContext, MyMessage, ThreadProvider } from "./context/ThreadCon import { Thread } from "./assistant-ui/thread"; import { ThreadList } from "./assistant-ui/thread-list"; import { chatStorage, ThreadData as StoredThreadData } from "../utils/chatStorage"; +import { useChatStorage } from "../hooks/useChatStorage"; +import styled from "styled-components"; + + + + +const ChatContainer = styled.div` + display: flex; + height: 500px; + + .aui-thread-list-root { + width: 250px; + background-color: #333; + } + + .aui-thread-root { + flex: 1; + background-color: #f0f0f0; + } + +`; // Define thread data interfaces to match ExternalStoreThreadData requirements interface RegularThreadData { @@ -45,109 +66,13 @@ function ChatWithThreads() { const [threadList, setThreadList] = useState([ { threadId: "default", status: "regular", title: "New Chat" } as RegularThreadData, ]); - const [isInitialized, setIsInitialized] = useState(false); - - // Load data from persistent storage on component mount - useEffect(() => { - const loadData = async () => { - try { - await chatStorage.initialize(); - - // Load all threads from storage - const storedThreads = await chatStorage.getAllThreads(); - if (storedThreads.length > 0) { - // Convert stored threads to UI format - const uiThreads: ThreadData[] = storedThreads.map(stored => ({ - threadId: stored.threadId, - status: stored.status as "regular" | "archived", - title: stored.title, - })); - setThreadList(uiThreads); - - // Load messages for each thread - const threadMessages = new Map(); - for (const thread of storedThreads) { - const messages = await chatStorage.getMessages(thread.threadId); - threadMessages.set(thread.threadId, messages); - } - - // Ensure default thread exists - if (!threadMessages.has("default")) { - threadMessages.set("default", []); - } - - setThreads(threadMessages); - - // Set current thread to the most recently updated one - const latestThread = storedThreads.sort((a, b) => b.updatedAt - a.updatedAt)[0]; - if (latestThread) { - setCurrentThreadId(latestThread.threadId); - } - } else { - // Initialize with default thread - const defaultThread: StoredThreadData = { - threadId: "default", - status: "regular", - title: "New Chat", - createdAt: Date.now(), - updatedAt: Date.now(), - }; - await chatStorage.saveThread(defaultThread); - } - - setIsInitialized(true); - } catch (error) { - console.error("Failed to load chat data:", error); - setIsInitialized(true); // Continue with default state - } - }; - - loadData(); - }, [setCurrentThreadId, setThreads]); - - // Save thread data whenever threadList changes - useEffect(() => { - if (!isInitialized) return; - - const saveThreads = async () => { - try { - for (const thread of threadList) { - const storedThread: StoredThreadData = { - threadId: thread.threadId, - status: thread.status, - title: thread.title, - createdAt: Date.now(), // In real app, preserve original createdAt - updatedAt: Date.now(), - }; - await chatStorage.saveThread(storedThread); - } - } catch (error) { - console.error("Failed to save threads:", error); - } - }; - - saveThreads(); - }, [threadList, isInitialized]); - - // Save messages whenever threads change - useEffect(() => { - if (!isInitialized) return; - - const saveMessages = async () => { - try { - for (const [threadId, messages] of threads.entries()) { - await chatStorage.saveMessages(messages, threadId); - } - } catch (error) { - console.error("Failed to save messages:", error); - } - }; - - saveMessages(); - }, [threads, isInitialized]); - - - + const { isInitialized } = useChatStorage({ + threadList, + threads, + setThreadList, + setThreads, + setCurrentThreadId, + }); // Get messages for current thread const currentMessages = threads.get(currentThreadId) || []; @@ -349,10 +274,16 @@ function ChatWithThreads() { }, }); + if (!isInitialized) { + return
      Loading...
      ; + } + return ( - - + + + + ); } diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/hooks/useChatStorage.ts b/client/packages/lowcoder/src/comps/comps/chatComp/hooks/useChatStorage.ts new file mode 100644 index 0000000000..2f24b3a827 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/hooks/useChatStorage.ts @@ -0,0 +1,139 @@ +import { useEffect, useState } from "react"; +import { chatStorage, ThreadData as StoredThreadData } from "../utils/chatStorage"; +import { MyMessage } from "../components/context/ThreadContext"; + +// Thread data interfaces (matching ChatWithThreads) +interface RegularThreadData { + threadId: string; + status: "regular"; + title: string; +} + +interface ArchivedThreadData { + threadId: string; + status: "archived"; + title: string; +} + +type ThreadData = RegularThreadData | ArchivedThreadData; + +interface UseChatStorageParams { + threadList: ThreadData[]; + threads: Map; + setThreadList: React.Dispatch>; + setThreads: React.Dispatch>>; + setCurrentThreadId: (id: string) => void; +} + +export function useChatStorage({ + threadList, + threads, + setThreadList, + setThreads, + setCurrentThreadId, +}: UseChatStorageParams) { + const [isInitialized, setIsInitialized] = useState(false); + + // Load data from persistent storage on component mount + useEffect(() => { + const loadData = async () => { + try { + await chatStorage.initialize(); + + // Load all threads from storage + const storedThreads = await chatStorage.getAllThreads(); + if (storedThreads.length > 0) { + // Convert stored threads to UI format + const uiThreads: ThreadData[] = storedThreads.map(stored => ({ + threadId: stored.threadId, + status: stored.status as "regular" | "archived", + title: stored.title, + })); + setThreadList(uiThreads); + + // Load messages for each thread + const threadMessages = new Map(); + for (const thread of storedThreads) { + const messages = await chatStorage.getMessages(thread.threadId); + threadMessages.set(thread.threadId, messages); + } + + // Ensure default thread exists + if (!threadMessages.has("default")) { + threadMessages.set("default", []); + } + + setThreads(threadMessages); + + // Set current thread to the most recently updated one + const latestThread = storedThreads.sort((a, b) => b.updatedAt - a.updatedAt)[0]; + if (latestThread) { + setCurrentThreadId(latestThread.threadId); + } + } else { + // Initialize with default thread + const defaultThread: StoredThreadData = { + threadId: "default", + status: "regular", + title: "New Chat", + createdAt: Date.now(), + updatedAt: Date.now(), + }; + await chatStorage.saveThread(defaultThread); + } + + setIsInitialized(true); + } catch (error) { + console.error("Failed to load chat data:", error); + setIsInitialized(true); // Continue with default state + } + }; + + loadData(); + }, [setCurrentThreadId, setThreads, setThreadList]); + + // Save thread data whenever threadList changes + useEffect(() => { + if (!isInitialized) return; + + const saveThreads = async () => { + try { + for (const thread of threadList) { + const storedThread: StoredThreadData = { + threadId: thread.threadId, + status: thread.status, + title: thread.title, + createdAt: Date.now(), // In real app, preserve original createdAt + updatedAt: Date.now(), + }; + await chatStorage.saveThread(storedThread); + } + } catch (error) { + console.error("Failed to save threads:", error); + } + }; + + saveThreads(); + }, [threadList, isInitialized]); + + // Save messages whenever threads change + useEffect(() => { + if (!isInitialized) return; + + const saveMessages = async () => { + try { + for (const [threadId, messages] of threads.entries()) { + await chatStorage.saveMessages(messages, threadId); + } + } catch (error) { + console.error("Failed to save messages:", error); + } + }; + + saveMessages(); + }, [threads, isInitialized]); + + return { + isInitialized, + }; +} \ No newline at end of file From dbd901cc2e9159c9e3ac8e2d553c47c8f5090d4f Mon Sep 17 00:00:00 2001 From: FARAN Date: Thu, 3 Jul 2025 21:08:14 +0500 Subject: [PATCH 16/18] add delete thread functionality --- .../components/assistant-ui/thread-list.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx index b44abaf0b7..565464fc76 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx @@ -3,7 +3,7 @@ import { ThreadListItemPrimitive, ThreadListPrimitive, } from "@assistant-ui/react"; -import { ArchiveIcon, PlusIcon } from "lucide-react"; +import { PlusIcon, Trash2Icon } from "lucide-react"; import { Button } from "../ui/button"; import { TooltipIconButton } from "./tooltip-icon-button"; @@ -38,7 +38,7 @@ const ThreadListItem: FC = () => { - + ); }; @@ -51,16 +51,16 @@ const ThreadListItemTitle: FC = () => { ); }; -const ThreadListItemArchive: FC = () => { +const ThreadListItemDelete: FC = () => { return ( - + - + - + ); }; \ No newline at end of file From bf9f26942779f95655faa5b2f2ca7bc92d2843c5 Mon Sep 17 00:00:00 2001 From: FARAN Date: Thu, 3 Jul 2025 21:48:42 +0500 Subject: [PATCH 17/18] add rename thread ability --- .../components/assistant-ui/thread-list.tsx | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx index 565464fc76..ef4eca84d0 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx @@ -3,10 +3,11 @@ import { ThreadListItemPrimitive, ThreadListPrimitive, } from "@assistant-ui/react"; -import { PlusIcon, Trash2Icon } from "lucide-react"; +import { PencilIcon, PlusIcon, Trash2Icon } from "lucide-react"; import { Button } from "../ui/button"; import { TooltipIconButton } from "./tooltip-icon-button"; +import { useThreadListItemRuntime } from "@assistant-ui/react"; export const ThreadList: FC = () => { return ( @@ -38,6 +39,7 @@ const ThreadListItem: FC = () => { + ); @@ -63,4 +65,40 @@ const ThreadListItemDelete: FC = () => { ); +}; + + +const ThreadListItemRename: FC = () => { + const runtime = useThreadListItemRuntime(); + + const handleClick = async () => { + // runtime doesn't expose a direct `title` prop; read it from its state + let current = ""; + try { + // getState is part of the public runtime surface + current = (runtime.getState?.() as any)?.title ?? ""; + } catch { + // fallback – generate a title if the runtime provides a helper + if (typeof (runtime as any).generateTitle === "function") { + // generateTitle(threadId) in older builds, generateTitle() in newer ones + current = (runtime as any).generateTitle((runtime as any).threadId ?? undefined); + } + } + + const next = prompt("Rename thread", current)?.trim(); + if (next && next !== current) { + await runtime.rename(next); + } + }; + + return ( + + + + ); }; \ No newline at end of file From 0205c725438d82ecf85d3c63ef5466bf551aef13 Mon Sep 17 00:00:00 2001 From: FARAN Date: Fri, 4 Jul 2025 23:55:21 +0500 Subject: [PATCH 18/18] [Feat]: Add chat component --- .../src/comps/comps/chatComp/chatView.tsx | 2 +- .../comps/chatComp/components/ChatApp.tsx | 10 + .../{ChatWithThreads.tsx => ChatMain.tsx} | 161 +++----- .../components/assistant-ui/thread-list.tsx | 17 +- .../components/context/ChatContext.tsx | 378 ++++++++++++++++++ .../components/context/ThreadContext.tsx | 53 --- .../comps/chatComp/hooks/useChatStorage.ts | 139 ------- .../comps/comps/chatComp/utils/chatStorage.ts | 2 +- 8 files changed, 449 insertions(+), 313 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/ChatApp.tsx rename client/packages/lowcoder/src/comps/comps/chatComp/components/{ChatWithThreads.tsx => ChatMain.tsx} (54%) create mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/context/ChatContext.tsx delete mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/components/context/ThreadContext.tsx delete mode 100644 client/packages/lowcoder/src/comps/comps/chatComp/hooks/useChatStorage.ts diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx index 0af26ffe25..07383b48a7 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx @@ -1,7 +1,7 @@ // client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx import React from "react"; import { ChatCompProps } from "./chatCompTypes"; -import { ChatApp } from "./components/ChatWithThreads"; +import { ChatApp } from "./components/ChatApp"; import "@assistant-ui/styles/index.css"; import "@assistant-ui/styles/markdown.css"; diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatApp.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatApp.tsx new file mode 100644 index 0000000000..e87ed8585f --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatApp.tsx @@ -0,0 +1,10 @@ +import { ChatProvider } from "./context/ChatContext"; +import { ChatMain } from "./ChatMain"; + +export function ChatApp() { + return ( + + + + ); +} diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatMain.tsx similarity index 54% rename from client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx rename to client/packages/lowcoder/src/comps/comps/chatComp/components/ChatMain.tsx index 7a9101a838..d0e151d88e 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatWithThreads.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatMain.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { useExternalStoreRuntime, ThreadMessageLike, @@ -6,46 +6,42 @@ import { AssistantRuntimeProvider, ExternalStoreThreadListAdapter, } from "@assistant-ui/react"; -import { useThreadContext, MyMessage, ThreadProvider } from "./context/ThreadContext"; import { Thread } from "./assistant-ui/thread"; import { ThreadList } from "./assistant-ui/thread-list"; -import { chatStorage, ThreadData as StoredThreadData } from "../utils/chatStorage"; -import { useChatStorage } from "../hooks/useChatStorage"; +import { + useChatContext, + MyMessage, + ThreadData, + RegularThreadData, + ArchivedThreadData +} from "./context/ChatContext"; import styled from "styled-components"; - - - const ChatContainer = styled.div` display: flex; height: 500px; .aui-thread-list-root { width: 250px; - background-color: #333; + background-color: #fff; + padding: 10px; } .aui-thread-root { flex: 1; - background-color: #f0f0f0; + background-color: #f9fafb; } -`; - -// Define thread data interfaces to match ExternalStoreThreadData requirements -interface RegularThreadData { - threadId: string; - status: "regular"; - title: string; -} + .aui-thread-list-item { + cursor: pointer; + transition: background-color 0.2s ease; -interface ArchivedThreadData { - threadId: string; - status: "archived"; - title: string; -} - -type ThreadData = RegularThreadData | ArchivedThreadData; + &[data-active="true"] { + background-color: #dbeafe; + border: 1px solid #bfdbfe; + } + } +`; const generateId = () => Math.random().toString(36).substr(2, 9); @@ -59,22 +55,14 @@ const callYourAPI = async (text: string) => { }; }; -function ChatWithThreads() { - const { currentThreadId, setCurrentThreadId, threads, setThreads } = - useThreadContext(); +export function ChatMain() { + const { state, actions } = useChatContext(); const [isRunning, setIsRunning] = useState(false); - const [threadList, setThreadList] = useState([ - { threadId: "default", status: "regular", title: "New Chat" } as RegularThreadData, - ]); - const { isInitialized } = useChatStorage({ - threadList, - threads, - setThreadList, - setThreads, - setCurrentThreadId, - }); + + console.log("STATE", state); + // Get messages for current thread - const currentMessages = threads.get(currentThreadId) || []; + const currentMessages = actions.getCurrentMessages(); // Convert custom format to ThreadMessageLike const convertMessage = (message: MyMessage): ThreadMessageLike => ({ @@ -99,8 +87,7 @@ function ChatWithThreads() { }; // Update current thread with new user message - const updatedMessages = [...currentMessages, userMessage]; - setThreads(prev => new Map(prev).set(currentThreadId, updatedMessages)); + await actions.addMessage(state.currentThreadId, userMessage); setIsRunning(true); try { @@ -115,8 +102,7 @@ function ChatWithThreads() { }; // Update current thread with assistant response - const finalMessages = [...updatedMessages, assistantMessage]; - setThreads(prev => new Map(prev).set(currentThreadId, finalMessages)); + await actions.addMessage(state.currentThreadId, assistantMessage); } catch (error) { // Handle errors gracefully const errorMessage: MyMessage = { @@ -126,8 +112,7 @@ function ChatWithThreads() { timestamp: Date.now(), }; - const finalMessages = [...updatedMessages, errorMessage]; - setThreads(prev => new Map(prev).set(currentThreadId, finalMessages)); + await actions.addMessage(state.currentThreadId, errorMessage); } finally { setIsRunning(false); } @@ -155,7 +140,8 @@ function ChatWithThreads() { }; newMessages.push(editedMessage); - setThreads(prev => new Map(prev).set(currentThreadId, newMessages)); + // Update messages using the new context action + await actions.updateMessages(state.currentThreadId, newMessages); setIsRunning(true); try { @@ -170,7 +156,7 @@ function ChatWithThreads() { }; newMessages.push(assistantMessage); - setThreads(prev => new Map(prev).set(currentThreadId, newMessages)); + await actions.updateMessages(state.currentThreadId, newMessages); } catch (error) { // Handle errors gracefully const errorMessage: MyMessage = { @@ -181,7 +167,7 @@ function ChatWithThreads() { }; newMessages.push(errorMessage); - setThreads(prev => new Map(prev).set(currentThreadId, newMessages)); + await actions.updateMessages(state.currentThreadId, newMessages); } finally { setIsRunning(false); } @@ -189,81 +175,36 @@ function ChatWithThreads() { // Thread list adapter for managing multiple threads const threadListAdapter: ExternalStoreThreadListAdapter = { - threadId: currentThreadId, - threads: threadList.filter((t): t is RegularThreadData => t.status === "regular"), - archivedThreads: threadList.filter((t): t is ArchivedThreadData => t.status === "archived"), + threadId: state.currentThreadId, + threads: state.threadList.filter((t): t is RegularThreadData => t.status === "regular"), + archivedThreads: state.threadList.filter((t): t is ArchivedThreadData => t.status === "archived"), onSwitchToNewThread: async () => { - const newId = `thread-${Date.now()}`; - const newThread: RegularThreadData = { - threadId: newId, - status: "regular", - title: "New Chat", - }; - - setThreadList((prev) => [...prev, newThread]); - setThreads((prev) => new Map(prev).set(newId, [])); - setCurrentThreadId(newId); - - // Save new thread to storage - try { - const storedThread: StoredThreadData = { - threadId: newId, - status: "regular", - title: "New Chat", - createdAt: Date.now(), - updatedAt: Date.now(), - }; - await chatStorage.saveThread(storedThread); - } catch (error) { - console.error("Failed to save new thread:", error); - } + const threadId = await actions.createThread("New Chat"); + actions.setCurrentThread(threadId); }, onSwitchToThread: (threadId) => { - setCurrentThreadId(threadId); + actions.setCurrentThread(threadId); }, - onRename: (threadId, newTitle) => { - setThreadList((prev) => - prev.map((t) => - t.threadId === threadId ? { ...t, title: newTitle } : t, - ), - ); + onRename: async (threadId, newTitle) => { + await actions.updateThread(threadId, { title: newTitle }); }, - onArchive: (threadId) => { - setThreadList((prev) => - prev.map((t) => - t.threadId === threadId ? { ...t, status: "archived" } : t, - ), - ); + onArchive: async (threadId) => { + await actions.updateThread(threadId, { status: "archived" }); }, onDelete: async (threadId) => { - setThreadList((prev) => prev.filter((t) => t.threadId !== threadId)); - setThreads((prev) => { - const next = new Map(prev); - next.delete(threadId); - return next; - }); - if (currentThreadId === threadId) { - setCurrentThreadId("default"); - } - - // Delete thread from storage - try { - await chatStorage.deleteThread(threadId); - } catch (error) { - console.error("Failed to delete thread from storage:", error); - } + await actions.deleteThread(threadId); }, }; const runtime = useExternalStoreRuntime({ messages: currentMessages, setMessages: (messages) => { - setThreads((prev) => new Map(prev).set(currentThreadId, messages)); + actions.updateMessages(state.currentThreadId, messages); }, convertMessage, isRunning, @@ -274,7 +215,7 @@ function ChatWithThreads() { }, }); - if (!isInitialized) { + if (!state.isInitialized) { return
      Loading...
      ; } @@ -288,13 +229,3 @@ function ChatWithThreads() { ); } -// Main App component with proper context wrapping -export function ChatApp() { - return ( - - - - ); -} - -export { ChatWithThreads }; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx index ef4eca84d0..bb01b7d5ee 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx @@ -5,9 +5,18 @@ import { } from "@assistant-ui/react"; import { PencilIcon, PlusIcon, Trash2Icon } from "lucide-react"; -import { Button } from "../ui/button"; import { TooltipIconButton } from "./tooltip-icon-button"; import { useThreadListItemRuntime } from "@assistant-ui/react"; +import { Button } from "antd"; + +import styled from "styled-components"; +import { useChatContext } from "../context/ChatContext"; + +const StyledPrimaryButton = styled(Button)` + padding: 20px; + margin-bottom: 20px; +`; + export const ThreadList: FC = () => { return ( @@ -21,10 +30,9 @@ export const ThreadList: FC = () => { const ThreadListNew: FC = () => { return ( - + ); }; @@ -46,6 +54,7 @@ const ThreadListItem: FC = () => { }; const ThreadListItemTitle: FC = () => { + return (

      diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ChatContext.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ChatContext.tsx new file mode 100644 index 0000000000..41ef892af4 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ChatContext.tsx @@ -0,0 +1,378 @@ +import React, { createContext, useContext, useReducer, useEffect, ReactNode } from "react"; +import { chatStorage, ThreadData as StoredThreadData } from "../../utils/chatStorage"; + +// Define thread-specific message type +export interface MyMessage { + id: string; + role: "user" | "assistant"; + text: string; + timestamp: number; +} + +// Thread data interfaces +export interface RegularThreadData { + threadId: string; + status: "regular"; + title: string; +} + +export interface ArchivedThreadData { + threadId: string; + status: "archived"; + title: string; +} + +export type ThreadData = RegularThreadData | ArchivedThreadData; + +// Chat state interface +interface ChatState { + isInitialized: boolean; + isLoading: boolean; + currentThreadId: string; + threadList: ThreadData[]; + threads: Map; + lastSaved: number; // Timestamp for tracking when data was last saved +} + +// Action types +type ChatAction = + | { type: "INITIALIZE_START" } + | { type: "INITIALIZE_SUCCESS"; threadList: ThreadData[]; threads: Map; currentThreadId: string } + | { type: "INITIALIZE_ERROR" } + | { type: "SET_CURRENT_THREAD"; threadId: string } + | { type: "ADD_THREAD"; thread: ThreadData } + | { type: "UPDATE_THREAD"; threadId: string; updates: Partial } + | { type: "DELETE_THREAD"; threadId: string } + | { type: "SET_MESSAGES"; threadId: string; messages: MyMessage[] } + | { type: "ADD_MESSAGE"; threadId: string; message: MyMessage } + | { type: "UPDATE_MESSAGES"; threadId: string; messages: MyMessage[] } + | { type: "MARK_SAVED" }; + +// Initial state +const initialState: ChatState = { + isInitialized: false, + isLoading: false, + currentThreadId: "default", + threadList: [{ threadId: "default", status: "regular", title: "New Chat" }], + threads: new Map([["default", []]]), + lastSaved: 0, +}; + +// Reducer function +function chatReducer(state: ChatState, action: ChatAction): ChatState { + switch (action.type) { + case "INITIALIZE_START": + return { + ...state, + isLoading: true, + }; + + case "INITIALIZE_SUCCESS": + return { + ...state, + isInitialized: true, + isLoading: false, + threadList: action.threadList, + threads: action.threads, + currentThreadId: action.currentThreadId, + lastSaved: Date.now(), + }; + + case "INITIALIZE_ERROR": + return { + ...state, + isInitialized: true, + isLoading: false, + }; + + case "SET_CURRENT_THREAD": + return { + ...state, + currentThreadId: action.threadId, + }; + + case "ADD_THREAD": + return { + ...state, + threadList: [...state.threadList, action.thread], + threads: new Map(state.threads).set(action.thread.threadId, []), + }; + + case "UPDATE_THREAD": + return { + ...state, + threadList: state.threadList.map(thread => + thread.threadId === action.threadId + ? { ...thread, ...action.updates } + : thread + ), + }; + + case "DELETE_THREAD": + const newThreads = new Map(state.threads); + newThreads.delete(action.threadId); + return { + ...state, + threadList: state.threadList.filter(t => t.threadId !== action.threadId), + threads: newThreads, + currentThreadId: state.currentThreadId === action.threadId + ? "default" + : state.currentThreadId, + }; + + case "SET_MESSAGES": + return { + ...state, + threads: new Map(state.threads).set(action.threadId, action.messages), + }; + + case "ADD_MESSAGE": + const currentMessages = state.threads.get(action.threadId) || []; + return { + ...state, + threads: new Map(state.threads).set(action.threadId, [...currentMessages, action.message]), + }; + + case "UPDATE_MESSAGES": + return { + ...state, + threads: new Map(state.threads).set(action.threadId, action.messages), + }; + + case "MARK_SAVED": + return { + ...state, + lastSaved: Date.now(), + }; + + default: + return state; + } +} + +// Context type +interface ChatContextType { + state: ChatState; + actions: { + // Initialization + initialize: () => Promise; + + // Thread management + setCurrentThread: (threadId: string) => void; + createThread: (title?: string) => Promise; + updateThread: (threadId: string, updates: Partial) => Promise; + deleteThread: (threadId: string) => Promise; + + // Message management + addMessage: (threadId: string, message: MyMessage) => Promise; + updateMessages: (threadId: string, messages: MyMessage[]) => Promise; + + // Utility + getCurrentMessages: () => MyMessage[]; + }; +} + +// Create the context +const ChatContext = createContext(null); + +// Chat provider component +export function ChatProvider({ children }: { children: ReactNode }) { + const [state, dispatch] = useReducer(chatReducer, initialState); + + // Initialize data from storage + const initialize = async () => { + dispatch({ type: "INITIALIZE_START" }); + + try { + await chatStorage.initialize(); + + // Load all threads from storage + const storedThreads = await chatStorage.getAllThreads(); + + if (storedThreads.length > 0) { + // Convert stored threads to UI format + const uiThreads: ThreadData[] = storedThreads.map(stored => ({ + threadId: stored.threadId, + status: stored.status as "regular" | "archived", + title: stored.title, + })); + + // Load messages for each thread + const threadMessages = new Map(); + for (const thread of storedThreads) { + const messages = await chatStorage.getMessages(thread.threadId); + threadMessages.set(thread.threadId, messages); + } + + // Ensure default thread exists + if (!threadMessages.has("default")) { + threadMessages.set("default", []); + } + + // Find the most recently updated thread + const latestThread = storedThreads.sort((a, b) => b.updatedAt - a.updatedAt)[0]; + const currentThreadId = latestThread ? latestThread.threadId : "default"; + + dispatch({ + type: "INITIALIZE_SUCCESS", + threadList: uiThreads, + threads: threadMessages, + currentThreadId + }); + } else { + // Initialize with default thread + const defaultThread: StoredThreadData = { + threadId: "default", + status: "regular", + title: "New Chat", + createdAt: Date.now(), + updatedAt: Date.now(), + }; + await chatStorage.saveThread(defaultThread); + + dispatch({ + type: "INITIALIZE_SUCCESS", + threadList: initialState.threadList, + threads: initialState.threads, + currentThreadId: "default" + }); + } + } catch (error) { + console.error("Failed to initialize chat data:", error); + dispatch({ type: "INITIALIZE_ERROR" }); + } + }; + + // Thread management actions + const setCurrentThread = (threadId: string) => { + dispatch({ type: "SET_CURRENT_THREAD", threadId }); + }; + + const createThread = async (title: string = "New Chat"): Promise => { + const threadId = `thread-${Date.now()}`; + const newThread: ThreadData = { + threadId, + status: "regular", + title, + }; + + // Update local state first + dispatch({ type: "ADD_THREAD", thread: newThread }); + + // Save to storage + try { + const storedThread: StoredThreadData = { + threadId, + status: "regular", + title, + createdAt: Date.now(), + updatedAt: Date.now(), + }; + await chatStorage.saveThread(storedThread); + dispatch({ type: "MARK_SAVED" }); + } catch (error) { + console.error("Failed to save new thread:", error); + } + + return threadId; + }; + + const updateThread = async (threadId: string, updates: Partial) => { + // Update local state first + dispatch({ type: "UPDATE_THREAD", threadId, updates }); + + // Save to storage + try { + const existingThread = await chatStorage.getThread(threadId); + if (existingThread) { + const updatedThread: StoredThreadData = { + ...existingThread, + ...updates, + updatedAt: Date.now(), + }; + await chatStorage.saveThread(updatedThread); + dispatch({ type: "MARK_SAVED" }); + } + } catch (error) { + console.error("Failed to update thread:", error); + } + }; + + const deleteThread = async (threadId: string) => { + // Update local state first + dispatch({ type: "DELETE_THREAD", threadId }); + + // Delete from storage + try { + await chatStorage.deleteThread(threadId); + dispatch({ type: "MARK_SAVED" }); + } catch (error) { + console.error("Failed to delete thread:", error); + } + }; + + // Message management actions + const addMessage = async (threadId: string, message: MyMessage) => { + // Update local state first + dispatch({ type: "ADD_MESSAGE", threadId, message }); + + // Save to storage + try { + await chatStorage.saveMessage(message, threadId); + dispatch({ type: "MARK_SAVED" }); + } catch (error) { + console.error("Failed to save message:", error); + } + }; + + const updateMessages = async (threadId: string, messages: MyMessage[]) => { + // Update local state first + dispatch({ type: "UPDATE_MESSAGES", threadId, messages }); + + // Save to storage + try { + await chatStorage.saveMessages(messages, threadId); + dispatch({ type: "MARK_SAVED" }); + } catch (error) { + console.error("Failed to save messages:", error); + } + }; + + // Utility functions + const getCurrentMessages = (): MyMessage[] => { + return state.threads.get(state.currentThreadId) || []; + }; + + // Auto-initialize on mount + useEffect(() => { + if (!state.isInitialized && !state.isLoading) { + initialize(); + } + }, [state.isInitialized, state.isLoading]); + + const actions = { + initialize, + setCurrentThread, + createThread, + updateThread, + deleteThread, + addMessage, + updateMessages, + getCurrentMessages, + }; + + return ( + + {children} + + ); +} + +// Hook for accessing chat context +export function useChatContext() { + const context = useContext(ChatContext); + if (!context) { + throw new Error("useChatContext must be used within ChatProvider"); + } + return context; +} \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ThreadContext.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ThreadContext.tsx deleted file mode 100644 index 313833105c..0000000000 --- a/client/packages/lowcoder/src/comps/comps/chatComp/components/context/ThreadContext.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { createContext, useContext, useState, ReactNode } from "react"; - -// Define thread-specific message type -interface MyMessage { - id: string; - role: "user" | "assistant"; - text: string; - timestamp: number; -} - -// Thread context type -interface ThreadContextType { - currentThreadId: string; - setCurrentThreadId: (id: string) => void; - threads: Map; - setThreads: React.Dispatch>>; -} - -// Create the context -const ThreadContext = createContext({ - currentThreadId: "default", - setCurrentThreadId: () => {}, - threads: new Map(), - setThreads: () => {}, -}); - -// Thread provider component -export function ThreadProvider({ children }: { children: ReactNode }) { - const [threads, setThreads] = useState>( - new Map([["default", []]]), - ); - const [currentThreadId, setCurrentThreadId] = useState("default"); - - return ( - - {children} - - ); -} - -// Hook for accessing thread context -export function useThreadContext() { - const context = useContext(ThreadContext); - if (!context) { - throw new Error("useThreadContext must be used within ThreadProvider"); - } - return context; -} - -// Export the MyMessage type for use in other files -export type { MyMessage }; \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/hooks/useChatStorage.ts b/client/packages/lowcoder/src/comps/comps/chatComp/hooks/useChatStorage.ts deleted file mode 100644 index 2f24b3a827..0000000000 --- a/client/packages/lowcoder/src/comps/comps/chatComp/hooks/useChatStorage.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { useEffect, useState } from "react"; -import { chatStorage, ThreadData as StoredThreadData } from "../utils/chatStorage"; -import { MyMessage } from "../components/context/ThreadContext"; - -// Thread data interfaces (matching ChatWithThreads) -interface RegularThreadData { - threadId: string; - status: "regular"; - title: string; -} - -interface ArchivedThreadData { - threadId: string; - status: "archived"; - title: string; -} - -type ThreadData = RegularThreadData | ArchivedThreadData; - -interface UseChatStorageParams { - threadList: ThreadData[]; - threads: Map; - setThreadList: React.Dispatch>; - setThreads: React.Dispatch>>; - setCurrentThreadId: (id: string) => void; -} - -export function useChatStorage({ - threadList, - threads, - setThreadList, - setThreads, - setCurrentThreadId, -}: UseChatStorageParams) { - const [isInitialized, setIsInitialized] = useState(false); - - // Load data from persistent storage on component mount - useEffect(() => { - const loadData = async () => { - try { - await chatStorage.initialize(); - - // Load all threads from storage - const storedThreads = await chatStorage.getAllThreads(); - if (storedThreads.length > 0) { - // Convert stored threads to UI format - const uiThreads: ThreadData[] = storedThreads.map(stored => ({ - threadId: stored.threadId, - status: stored.status as "regular" | "archived", - title: stored.title, - })); - setThreadList(uiThreads); - - // Load messages for each thread - const threadMessages = new Map(); - for (const thread of storedThreads) { - const messages = await chatStorage.getMessages(thread.threadId); - threadMessages.set(thread.threadId, messages); - } - - // Ensure default thread exists - if (!threadMessages.has("default")) { - threadMessages.set("default", []); - } - - setThreads(threadMessages); - - // Set current thread to the most recently updated one - const latestThread = storedThreads.sort((a, b) => b.updatedAt - a.updatedAt)[0]; - if (latestThread) { - setCurrentThreadId(latestThread.threadId); - } - } else { - // Initialize with default thread - const defaultThread: StoredThreadData = { - threadId: "default", - status: "regular", - title: "New Chat", - createdAt: Date.now(), - updatedAt: Date.now(), - }; - await chatStorage.saveThread(defaultThread); - } - - setIsInitialized(true); - } catch (error) { - console.error("Failed to load chat data:", error); - setIsInitialized(true); // Continue with default state - } - }; - - loadData(); - }, [setCurrentThreadId, setThreads, setThreadList]); - - // Save thread data whenever threadList changes - useEffect(() => { - if (!isInitialized) return; - - const saveThreads = async () => { - try { - for (const thread of threadList) { - const storedThread: StoredThreadData = { - threadId: thread.threadId, - status: thread.status, - title: thread.title, - createdAt: Date.now(), // In real app, preserve original createdAt - updatedAt: Date.now(), - }; - await chatStorage.saveThread(storedThread); - } - } catch (error) { - console.error("Failed to save threads:", error); - } - }; - - saveThreads(); - }, [threadList, isInitialized]); - - // Save messages whenever threads change - useEffect(() => { - if (!isInitialized) return; - - const saveMessages = async () => { - try { - for (const [threadId, messages] of threads.entries()) { - await chatStorage.saveMessages(messages, threadId); - } - } catch (error) { - console.error("Failed to save messages:", error); - } - }; - - saveMessages(); - }, [threads, isInitialized]); - - return { - isInitialized, - }; -} \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/utils/chatStorage.ts b/client/packages/lowcoder/src/comps/comps/chatComp/utils/chatStorage.ts index 7e85087e02..edc68a0d93 100644 --- a/client/packages/lowcoder/src/comps/comps/chatComp/utils/chatStorage.ts +++ b/client/packages/lowcoder/src/comps/comps/chatComp/utils/chatStorage.ts @@ -1,5 +1,5 @@ import alasql from "alasql"; -import { MyMessage } from "../components/context/ThreadContext"; +import { MyMessage } from "../components/context/ChatContext"; // Database configuration const DB_NAME = "ChatDB";

      + ), + td: ({ className, ...props }) => ( + + ), + tr: ({ className, ...props }) => ( +