Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[In Progress] [Feat]: Chat Component#1841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
20 commits
Select commitHold shift + click to select a range
d5d9873
init chat component
iamfaranJun 30, 2025
d1d9b92
refactor component structure
iamfaranJun 30, 2025
0c132c1
install dependencies
iamfaranJun 30, 2025
4e7e540
add assistant-ui components
iamfaranJul 1, 2025
c541393
fix linter error
iamfaranJul 1, 2025
bf3810f
add thread
iamfaranJul 1, 2025
11c98fd
add properties for chat component
iamfaranJul 1, 2025
aec9485
add query / new hook from assisstant ui
iamfaranJul 2, 2025
b820f8e
use mock data
iamfaranJul 2, 2025
0dc85d4
add edit functionality
iamfaranJul 2, 2025
fd9dc77
fix message json issue
iamfaranJul 2, 2025
22934c6
Merge branch 'dev' of github.com:lowcoder-org/lowcoder into feat/chat…
iamfaranJul 2, 2025
52cdcf2
add threads logic
iamfaranJul 2, 2025
c619f89
add alaSql to chat component
iamfaranJul 2, 2025
f379cac
delete myruntime provider
iamfaranJul 2, 2025
e553d52
add storage support
iamfaranJul 3, 2025
dbd901c
add delete thread functionality
iamfaranJul 3, 2025
bf9f269
add rename thread ability
iamfaranJul 3, 2025
0205c72
[Feat]: Add chat component
iamfaranJul 4, 2025
be3b3ab
Merge branch 'dev' of github.com:lowcoder-org/lowcoder into feat/chat…
iamfaranJul 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletionclient/packages/lowcoder/package.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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",
Expand All@@ -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",
Expand All@@ -37,11 +44,13 @@
"@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",
"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",
Expand All@@ -61,6 +70,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",
Expand DownExpand Up@@ -98,7 +108,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",
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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) => <ChatView {...props} chatQuery={props.chatQuery.value} />
)
.setPropertyViewFn((children) => <ChatPropertyView children={children} />)
.build();

// Export the component
export const ChatComp = withExposingConfigs(ChatTmpComp, [
new NameConfig("text", "Chat component text"),
]);
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
// client/packages/lowcoder/src/comps/comps/chatComp/chatCompTypes.ts
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 = [
{ label: "Direct LLM", value: "direct-llm" },
{ label: "n8n Workflow", value: "n8n" },
] as const;

export const chatChildrenMap = {
text: withDefault(StringControl, "Chat Component Placeholder"),
chatQuery: QuerySelectControl,
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;
chatQuery: string;
modelType: string;
streaming: boolean;
systemPrompt: string;
agent: boolean;
maxInteractions: number;
};
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
// 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) => {
const { children } = props;

return (
<Section name={sectionNames.basic}>
{children.text.propertyView({ label: "Text" })}
{children.chatQuery.propertyView({ label: "Chat Query" })}
{children.modelType.propertyView({ label: "Model Type" })}
{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",
})}
</Section>
);
});

ChatPropertyView.displayName = 'ChatPropertyView';
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
// client/packages/lowcoder/src/comps/comps/chatComp/chatView.tsx
import React from "react";
import { ChatCompProps } from "./chatCompTypes";
import { ChatApp } from "./components/ChatApp";

import "@assistant-ui/styles/index.css";
import "@assistant-ui/styles/markdown.css";

export const ChatView = React.memo((props: ChatCompProps) => {
return <ChatApp />;
});

ChatView.displayName = 'ChatView';
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
import { ChatProvider } from "./context/ChatContext";
import { ChatMain } from "./ChatMain";

export function ChatApp() {
return (
<ChatProvider>
<ChatMain />
</ChatProvider>
);
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
import React, { useState } from "react";
import {
useExternalStoreRuntime,
ThreadMessageLike,
AppendMessage,
AssistantRuntimeProvider,
ExternalStoreThreadListAdapter,
} from "@assistant-ui/react";
import { Thread } from "./assistant-ui/thread";
import { ThreadList } from "./assistant-ui/thread-list";
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: #fff;
padding: 10px;
}

.aui-thread-root {
flex: 1;
background-color: #f9fafb;
}

.aui-thread-list-item {
cursor: pointer;
transition: background-color 0.2s ease;

&[data-active="true"] {
background-color: #dbeafe;
border: 1px solid #bfdbfe;
}
}
`;

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 ChatMain() {
const { state, actions } = useChatContext();
const [isRunning, setIsRunning] = useState(false);

console.log("STATE", state);

// Get messages for current thread
const currentMessages = actions.getCurrentMessages();

// 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
await actions.addMessage(state.currentThreadId, 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(),
};

// Update current thread with assistant response
await actions.addMessage(state.currentThreadId, 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'}`,
timestamp: Date.now(),
};

await actions.addMessage(state.currentThreadId, 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 = 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);

// Update messages using the new context action
await actions.updateMessages(state.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);
await actions.updateMessages(state.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);
await actions.updateMessages(state.currentThreadId, newMessages);
} finally {
setIsRunning(false);
}
};

// Thread list adapter for managing multiple threads
const threadListAdapter: ExternalStoreThreadListAdapter = {
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 threadId = await actions.createThread("New Chat");
actions.setCurrentThread(threadId);
},

onSwitchToThread: (threadId) => {
actions.setCurrentThread(threadId);
},

onRename: async (threadId, newTitle) => {
await actions.updateThread(threadId, { title: newTitle });
},

onArchive: async (threadId) => {
await actions.updateThread(threadId, { status: "archived" });
},

onDelete: async (threadId) => {
await actions.deleteThread(threadId);
},
};

const runtime = useExternalStoreRuntime({
messages: currentMessages,
setMessages: (messages) => {
actions.updateMessages(state.currentThreadId, messages);
},
convertMessage,
isRunning,
onNew,
onEdit,
adapters: {
threadList: threadListAdapter,
},
});

if (!state.isInitialized) {
return <div>Loading...</div>;
}

return (
<AssistantRuntimeProvider runtime={runtime}>
<ChatContainer>
<ThreadList />
<Thread />
</ChatContainer>
</AssistantRuntimeProvider>
);
}

Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp