diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json
index 7c2899bfcc..542327bee7 100644
--- a/client/packages/lowcoder/package.json
+++ b/client/packages/lowcoder/package.json
@@ -8,10 +8,8 @@
"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",
+ "@assistant-ui/react": "^0.14.5",
+ "@assistant-ui/react-markdown": "^0.14.0",
"@bany/curl-to-json": "^1.2.8",
"@codemirror/autocomplete": "^6.11.1",
"@codemirror/commands": "^6.3.2",
diff --git a/client/packages/lowcoder/src/base/codeEditor/codeEditor.tsx b/client/packages/lowcoder/src/base/codeEditor/codeEditor.tsx
index 02630eefef..44fedbc32a 100644
--- a/client/packages/lowcoder/src/base/codeEditor/codeEditor.tsx
+++ b/client/packages/lowcoder/src/base/codeEditor/codeEditor.tsx
@@ -15,6 +15,7 @@ import type { CodeEditorProps, StyleName } from "./codeEditorTypes";
import { useClickCompNameEffect } from "./clickCompName";
import { Layers } from "../../constants/Layers";
import { debounce } from "lodash";
+import { CodeEditorAIHelpButton } from "components/ai-helper";
type StyleConfig = {
minHeight: string;
@@ -214,6 +215,7 @@ function useCodeMirror(
) {
const { value, onChange } = props;
const viewRef = useRef();
+ const [viewVersion, setViewVersion] = useState(0);
// will not trigger view.setState when typing inputs, to avoid focus chaos
const isTypingRef = useRef(0);
@@ -250,6 +252,7 @@ function useCodeMirror(
view.setState(state);
} else {
viewRef.current = new EditorView({ state, parent: container.current });
+ setViewVersion((version) => version + 1);
}
}
}, [container, value, extensions]);
@@ -262,7 +265,7 @@ function useCodeMirror(
};
}, []);
- return { view: viewRef.current, isFocus };
+ return { view: viewRef.current, isFocus, viewVersion };
}
function clickCompNameCss(enableClickCompName?: boolean) {
@@ -338,6 +341,20 @@ const CodeEditorPanelContainer = styled.div<{
const CodeEditorWrapper = styled.div`
height: 100%;
+ position: relative;
+
+ .code-editor-ai-help-button {
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 120ms ease;
+ }
+
+ &:hover {
+ .code-editor-ai-help-button {
+ opacity: 1;
+ pointer-events: auto;
+ }
+ }
`;
function canShowCard(props: CodeEditorProps) {
@@ -358,6 +375,21 @@ function CodeEditorCommon(
view && onClick(e, view) : undefined}>
{!disabled && view && props.widgetPopup?.(view)}
{children}
+ {!disabled && props.enableAIHelp && view && (
+
+ )}
ReactNode;
cardTips?: ReactNode;
enableMetaCompletion?: boolean;
+ enableAIHelp?: boolean;
+ aiHelp?: CodeEditorAIHelp;
}
export interface CodeEditorProps extends CodeEditorControlParams {
diff --git a/client/packages/lowcoder/src/components/ai-helper/AIHelperModal.tsx b/client/packages/lowcoder/src/components/ai-helper/AIHelperModal.tsx
new file mode 100644
index 0000000000..64bbee42f9
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/AIHelperModal.tsx
@@ -0,0 +1,225 @@
+import { useContext, useEffect, useRef } from "react";
+import Button from "antd/es/button";
+import Empty from "antd/es/empty";
+import Select from "antd/es/select";
+import { AssistantModalPrimitive } from "@assistant-ui/react";
+import { SparklesIcon, XIcon } from "lucide-react";
+import { useSelector } from "react-redux";
+import styled from "styled-components";
+
+import { EditorContext } from "comps/editorState";
+import { getDataSourceStructures } from "redux/selectors/datasourceSelectors";
+import { getSelectedAIQueryName } from "util/localStorageUtil";
+
+import { AIHelperRuntime } from "./AIHelperRuntime";
+import { useAIHelper } from "./context/AIHelperController";
+
+const Anchor = styled.div`
+ position: fixed;
+ right: 16px;
+ bottom: 16px;
+ width: 1px;
+ height: 1px;
+`;
+
+const Content = styled(AssistantModalPrimitive.Content)`
+ width: 430px;
+ height: min(640px, calc(100vh - 128px));
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ border: 1px solid #e1e3eb;
+ border-radius: 8px;
+ background: #ffffff;
+ box-shadow: 0 12px 40px rgba(15, 23, 42, 0.18);
+ z-index: 2147483000;
+
+ .aui-thread-root {
+ min-height: 0;
+ flex: 1 1 auto;
+ background: #fafbfc;
+ }
+
+ .aui-thread-viewport {
+ padding: 12px 12px 0;
+ }
+
+ .aui-thread-welcome-root {
+ padding: 16px 8px;
+ }
+
+ .aui-thread-welcome-suggestions {
+ display: none;
+ }
+`;
+
+const Header = styled.div`
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 10px 12px;
+ border-bottom: 1px solid #e1e3eb;
+`;
+
+const Title = styled.div`
+ min-width: 0;
+`;
+
+const TitleLine = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: #111827;
+ font-size: 13px;
+ font-weight: 600;
+`;
+
+const TargetLabel = styled.div`
+ max-width: 300px;
+ margin-top: 2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: #6b7280;
+ font-size: 11px;
+`;
+
+const IconButton = styled.button`
+ width: 28px;
+ height: 28px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 0;
+ border-radius: 6px;
+ background: transparent;
+ color: #6b7280;
+ cursor: pointer;
+
+ &:hover {
+ background: #f3f4f6;
+ color: #111827;
+ }
+`;
+
+const QueryBar = styled.div`
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ border-bottom: 1px solid #f1f5f9;
+ background: #fcfcfd;
+ color: #6b7280;
+ font-size: 12px;
+`;
+
+const EmptyState = styled.div`
+ flex: 1 1 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24px;
+`;
+
+export function AIHelperModal() {
+ const helper = useAIHelper();
+ const editorState = useContext(EditorContext);
+ const datasourceStructures = useSelector(getDataSourceStructures);
+ const datasourceStructuresRef = useRef(datasourceStructures);
+
+ useEffect(() => {
+ datasourceStructuresRef.current = datasourceStructures;
+ }, [datasourceStructures]);
+
+ useEffect(() => {
+ if (!helper?.open) {
+ return;
+ }
+ const selectedQueryName = getSelectedAIQueryName();
+ if (selectedQueryName !== helper.helperQueryName) {
+ helper.setHelperQueryName(selectedQueryName);
+ }
+ }, [helper?.open]);
+
+ if (!helper) return null;
+
+ const queryOptions = (() => {
+ if (!editorState) return [];
+ try {
+ return editorState.getQueriesComp().getView().map((query: any) => {
+ const name = query.children.name.getView();
+ const type = query.children.compType.getView();
+ return {
+ label: type ? `${name} (${type})` : name,
+ value: name,
+ };
+ });
+ } catch {
+ return [];
+ }
+ })();
+
+ const target = helper.target;
+
+ return (
+
+
+
+
+
+
+
+
+
+ AI Helper
+
+ {target?.label && (
+ {target.label}
+ )}
+
+
+
+
+
+
+
+ AI query:
+ helper.setHelperQueryName(value || "")}
+ options={queryOptions}
+ notFoundContent={queryOptions.length ? undefined : "No queries found"}
+ getPopupContainer={(triggerNode) => triggerNode.parentElement ?? document.body}
+ style={{ flex: 1, minWidth: 0 }}
+ />
+
+
+ {!helper.helperQueryName || !target ? (
+
+
+ Close
+
+
+ ) : (
+ datasourceStructuresRef.current}
+ target={target}
+ />
+ )}
+
+
+ );
+}
diff --git a/client/packages/lowcoder/src/components/ai-helper/AIHelperRuntime.tsx b/client/packages/lowcoder/src/components/ai-helper/AIHelperRuntime.tsx
new file mode 100644
index 0000000000..86845ebcde
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/AIHelperRuntime.tsx
@@ -0,0 +1,140 @@
+import {
+ AssistantRuntimeProvider,
+ type AppendMessage,
+ type ThreadMessageLike,
+ useAssistantToolUI,
+ useExternalStoreRuntime,
+} from "@assistant-ui/react";
+import { useCallback, useMemo, useState } from "react";
+
+import { Thread } from "components/assistant-ui/thread";
+import type { ChatMessage } from "comps/comps/chatComp/types/chatTypes";
+import {
+ createAssistantErrorMessage,
+ createUserMessage,
+ getTextFromAppendMessage,
+ toChatMessage,
+} from "comps/comps/chatComp/utils/assistantMessages";
+
+import { ApplyActions } from "./components/ApplyActions";
+import { AIHelperQueryHandler } from "./handlers/AIHelperQueryHandler";
+import { useAIHelper } from "./context/AIHelperController";
+import {
+ AI_HELPER_APPLY_TOOL,
+ type AIHelperApplyAction,
+ type AIHelperTarget,
+} from "./types";
+
+interface ApplyToolArgs {
+ value: string;
+ mode: AIHelperApplyAction["mode"];
+ label?: string;
+ language?: string;
+}
+
+function AIHelperApplyToolUI() {
+ const helper = useAIHelper();
+
+ const render = useCallback(
+ ({ args }: { args: ApplyToolArgs }) => {
+ const action: AIHelperApplyAction = {
+ id: AI_HELPER_APPLY_TOOL,
+ label: args.label ?? "Apply",
+ value: args.value,
+ mode: args.mode,
+ language: args.language,
+ };
+
+ return (
+ helper?.applyResult(action)}
+ />
+ );
+ },
+ [helper]
+ );
+
+ const tool = useMemo(
+ () => ({
+ toolName: AI_HELPER_APPLY_TOOL,
+ render,
+ }),
+ [render]
+ );
+
+ useAssistantToolUI(tool);
+
+ return null;
+}
+
+export function AIHelperRuntime({
+ helperQueryName,
+ dispatch,
+ getDatasourceStructures,
+ target,
+}: {
+ helperQueryName: string;
+ dispatch: any;
+ getDatasourceStructures: () => Record | undefined;
+ target: AIHelperTarget;
+}) {
+ const [messages, setMessages] = useState([]);
+ const [isRunning, setIsRunning] = useState(false);
+
+ const handler = useMemo(
+ () =>
+ new AIHelperQueryHandler({
+ helperQueryName,
+ dispatch,
+ getDatasourceStructures,
+ target,
+ }),
+ [
+ helperQueryName,
+ dispatch,
+ getDatasourceStructures,
+ target,
+ ]
+ );
+
+ const onNew = useCallback(
+ async (message: AppendMessage) => {
+ const text = getTextFromAppendMessage(message);
+ if (!text) throw new Error("Cannot send an empty message");
+
+ const userMessage = createUserMessage(text);
+ const conversationHistory = [...messages, userMessage];
+ setMessages(conversationHistory);
+ setIsRunning(true);
+
+ try {
+ const assistantMessage = await handler.sendMessage(conversationHistory);
+ setMessages((prev) => [...prev, assistantMessage]);
+ } catch (error: any) {
+ setMessages((prev) => [
+ ...prev,
+ createAssistantErrorMessage(error?.message || "AI Helper failed"),
+ ]);
+ } finally {
+ setIsRunning(false);
+ }
+ },
+ [handler, messages]
+ );
+
+ const runtime = useExternalStoreRuntime({
+ messages,
+ setMessages: (next) => setMessages(next.map(toChatMessage)),
+ convertMessage: (message: ChatMessage): ThreadMessageLike => message,
+ isRunning,
+ onNew,
+ });
+
+ return (
+
+
+
+
+ );
+}
diff --git a/client/packages/lowcoder/src/components/ai-helper/CodeEditorAIHelpButton.tsx b/client/packages/lowcoder/src/components/ai-helper/CodeEditorAIHelpButton.tsx
new file mode 100644
index 0000000000..b36327090e
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/CodeEditorAIHelpButton.tsx
@@ -0,0 +1,163 @@
+import { useMemo, type MouseEvent } from "react";
+import { SparklesIcon } from "lucide-react";
+import styled from "styled-components";
+import type { EditorView } from "@codemirror/view";
+import Tooltip from "antd/es/tooltip";
+
+import { useAIHelper } from "./context/AIHelperController";
+import type {
+ AIHelperApplyAction,
+ AIHelperTarget,
+ AIHelperTargetKind,
+} from "./types";
+
+export interface CodeEditorAIHelpButtonProps {
+ view?: EditorView;
+ label?: string;
+ language?: AIHelperTarget["language"];
+ targetKind?: AIHelperTargetKind;
+ datasourceId?: string;
+ queryType?: string;
+ queryName?: string;
+ componentName?: string;
+ fieldName?: string;
+ fieldDescription?: string;
+ targetId?: string;
+}
+
+const Button = styled.button.attrs({ className: "code-editor-ai-help-button" })`
+ position: absolute;
+ top: 4px;
+ right: 24px;
+ z-index: 5;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ height: 22px;
+ padding: 0 8px;
+ border: 1px solid #dbe4ff;
+ border-radius: 6px;
+ background: rgba(255, 255, 255, 0.94);
+ color: #4965f2;
+ cursor: pointer;
+ font-size: 11px;
+ font-weight: 500;
+
+ &:hover {
+ border-color: #b7c4ff;
+ background: #f2f5ff;
+ }
+`;
+
+function applyToView(view: EditorView, action: AIHelperApplyAction) {
+ const doc = view.state.doc;
+ const docLen = doc.length;
+
+ if (action.mode === "append") {
+ view.dispatch({
+ changes: { from: docLen, to: docLen, insert: action.value },
+ selection: { anchor: docLen + action.value.length },
+ });
+ view.focus();
+ return;
+ }
+
+ if (action.mode === "insertAtCursor") {
+ const cursor = view.state.selection.main.head;
+ view.dispatch({
+ changes: { from: cursor, to: cursor, insert: action.value },
+ selection: { anchor: cursor + action.value.length },
+ });
+ view.focus();
+ return;
+ }
+
+ const selection = view.state.selection.main;
+ const from = selection.empty ? 0 : selection.from;
+ const to = selection.empty ? docLen : selection.to;
+ view.dispatch({
+ changes: { from, to, insert: action.value },
+ selection: { anchor: from + action.value.length },
+ });
+ view.focus();
+}
+
+function defaultTargetKind(
+ language: AIHelperTarget["language"] | undefined
+): AIHelperTargetKind {
+ if (language === "sql") return "sql";
+ if (language === "javascript") return "javascript";
+ if (language === "json") return "json";
+ return "component-field";
+}
+
+export function CodeEditorAIHelpButton({
+ view,
+ label,
+ language,
+ targetKind,
+ datasourceId,
+ queryType,
+ queryName,
+ componentName,
+ fieldName,
+ fieldDescription,
+ targetId,
+}: CodeEditorAIHelpButtonProps) {
+ const helper = useAIHelper();
+ const id = useMemo(
+ () =>
+ targetId ??
+ [
+ queryName,
+ componentName,
+ fieldName,
+ label,
+ language,
+ ].filter(Boolean).join("|"),
+ [targetId, queryName, componentName, fieldName, label, language]
+ );
+
+ if (!helper || !view) return null;
+
+ const onClick = (event: MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const doc = view.state.doc.toString();
+ const selection = view.state.selection.main;
+ const target: AIHelperTarget = {
+ id: id || "field",
+ kind: targetKind ?? defaultTargetKind(language),
+ label,
+ language,
+ currentValue: doc,
+ selection: doc.slice(selection.from, selection.to),
+ cursor: selection.head,
+ datasourceId,
+ queryType,
+ queryName,
+ componentName,
+ fieldName,
+ fieldDescription,
+ };
+
+ helper.openHelper({
+ target,
+ onApply: (action) => applyToView(view, action),
+ });
+ };
+
+ return (
+
+ event.stopPropagation()}
+ >
+
+
+
+ );
+}
diff --git a/client/packages/lowcoder/src/components/ai-helper/components/ApplyActions.tsx b/client/packages/lowcoder/src/components/ai-helper/components/ApplyActions.tsx
new file mode 100644
index 0000000000..20f111040a
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/components/ApplyActions.tsx
@@ -0,0 +1,66 @@
+import Button from "antd/es/button";
+import { CodeIcon } from "lucide-react";
+import styled from "styled-components";
+
+import type { AIHelperApplyAction } from "../types";
+
+const Wrapper = styled.div`
+ flex: 0 0 auto;
+ border-top: 1px solid #e1e3eb;
+ background: #ffffff;
+ padding: 10px 12px;
+`;
+
+const Title = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 8px;
+ color: #4b5563;
+ font-size: 12px;
+ font-weight: 600;
+`;
+
+const Row = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+`;
+
+const Label = styled.div`
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: #111827;
+ font-size: 13px;
+`;
+
+export function ApplyActions({
+ actions,
+ onApply,
+}: {
+ actions: AIHelperApplyAction[];
+ onApply: (action: AIHelperApplyAction) => void;
+}) {
+ if (!actions.length) return null;
+
+ return (
+
+
+
+ Generated value
+
+ {actions.map((action) => (
+
+ {action.label}
+ onApply(action)}>
+ Apply
+
+
+ ))}
+
+ );
+}
+
diff --git a/client/packages/lowcoder/src/components/ai-helper/context/AIHelperController.tsx b/client/packages/lowcoder/src/components/ai-helper/context/AIHelperController.tsx
new file mode 100644
index 0000000000..c83231915e
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/context/AIHelperController.tsx
@@ -0,0 +1,102 @@
+import {
+ createContext,
+ useCallback,
+ useContext,
+ useMemo,
+ useRef,
+ useState,
+ type ReactNode,
+} from "react";
+
+import type { AIHelperApplyAction, AIHelperTarget } from "../types";
+import {
+ getSelectedAIQueryName,
+ saveSelectedAIQueryName,
+} from "util/localStorageUtil";
+
+export type AIHelperApplyCallback = (
+ action: AIHelperApplyAction,
+ target: AIHelperTarget
+) => void;
+
+export interface AIHelperOpenOptions {
+ target: AIHelperTarget;
+ onApply?: AIHelperApplyCallback;
+}
+
+interface AIHelperState {
+ open: boolean;
+ target?: AIHelperTarget;
+ helperQueryName: string;
+}
+
+interface AIHelperContextValue extends AIHelperState {
+ openHelper: (opts: AIHelperOpenOptions) => void;
+ closeHelper: () => void;
+ setOpen: (open: boolean) => void;
+ setHelperQueryName: (name: string) => void;
+ applyResult: (action: AIHelperApplyAction) => void;
+}
+
+const AIHelperContext = createContext(null);
+
+export function AIHelperProvider({ children }: { children: ReactNode }) {
+ const [state, setState] = useState(() => ({
+ open: false,
+ helperQueryName: getSelectedAIQueryName(),
+ }));
+ const applyRef = useRef(null);
+
+ const openHelper = useCallback((opts: AIHelperOpenOptions) => {
+ applyRef.current = opts.onApply ?? null;
+ setState((s) => ({
+ ...s,
+ open: true,
+ target: opts.target,
+ }));
+ }, []);
+
+ const closeHelper = useCallback(() => {
+ setState((s) => ({ ...s, open: false }));
+ }, []);
+
+ const setOpen = useCallback((open: boolean) => {
+ setState((s) => ({ ...s, open }));
+ if (!open) applyRef.current = null;
+ }, []);
+
+ const setHelperQueryName = useCallback((name: string) => {
+ saveSelectedAIQueryName(name);
+ setState((s) => ({ ...s, helperQueryName: name }));
+ }, []);
+
+ const applyResult = useCallback(
+ (action: AIHelperApplyAction) => {
+ if (!state.target) return;
+ applyRef.current?.(action, state.target);
+ },
+ [state.target]
+ );
+
+ const value = useMemo(
+ () => ({
+ ...state,
+ openHelper,
+ closeHelper,
+ setOpen,
+ setHelperQueryName,
+ applyResult,
+ }),
+ [state, openHelper, closeHelper, setOpen, setHelperQueryName, applyResult]
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAIHelper() {
+ return useContext(AIHelperContext);
+}
diff --git a/client/packages/lowcoder/src/components/ai-helper/context/buildAIHelperContext.ts b/client/packages/lowcoder/src/components/ai-helper/context/buildAIHelperContext.ts
new file mode 100644
index 0000000000..ea44375ef2
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/context/buildAIHelperContext.ts
@@ -0,0 +1,115 @@
+import type { DatasourceStructure } from "api/datasourceApi";
+
+import { AI_HELPER_APPLY_TOOL, type AIHelperTarget } from "../types";
+
+export type AIHelperTargetContext = AIHelperTarget;
+
+export function flattenDatasourceSchema(
+ structure: DatasourceStructure[] | undefined
+): Record {
+ const out: Record = {};
+ if (!structure) return out;
+
+ for (const table of structure) {
+ if (!table?.name) continue;
+ out[table.name] = "table";
+ for (const col of table.columns ?? []) {
+ if (!col?.name) continue;
+ out[`${table.name}.${col.name}`] = col.type ?? "unknown";
+ }
+ }
+
+ return out;
+}
+
+export function buildAIHelperTargetContext(args: {
+ datasourceStructures?: Record;
+ target: AIHelperTarget;
+}): AIHelperTargetContext {
+ const { datasourceStructures, target } = args;
+
+ const datasourceSchema =
+ target.datasourceId && datasourceStructures
+ ? flattenDatasourceSchema(datasourceStructures[target.datasourceId])
+ : undefined;
+
+ return {
+ ...target,
+ datasourceType: target.datasourceType ?? target.queryType,
+ datasourceSchema:
+ datasourceSchema && Object.keys(datasourceSchema).length > 0
+ ? datasourceSchema
+ : undefined,
+ };
+}
+
+function describeTarget(target: AIHelperTarget): string {
+ switch (target.kind) {
+ case "sql":
+ return `The target is a ${target.queryType || "SQL"} query editor. Help write, explain, or improve SQL for this datasource.`;
+ case "javascript":
+ return "The target is a JavaScript query/editor. Use modern JavaScript that can run in Lowcoder's query environment.";
+ case "echarts-option":
+ return "The target is an Apache ECharts option JSON field. Generate valid ECharts option JSON, not Automator actions.";
+ case "json":
+ return "The target is a JSON field. Generate valid JSON when the user asks for a value.";
+ default:
+ return target.fieldDescription || "The target is a complex Lowcoder component input.";
+ }
+}
+
+export function buildAIHelperSystemMessage(target: AIHelperTargetContext) {
+ return `You are Lowcoder AI Helper, an embedded field assistant.
+
+You help the builder understand or generate code/data for one focused Lowcoder input.
+You are NOT the Automator and must not create, move, or modify Lowcoder canvas components.
+
+Target:
+${describeTarget(target)}
+
+Target context:
+${JSON.stringify(target, null, 2)}
+
+Response rules:
+- Explain briefly when explanation is useful.
+- Prefer concrete code/data that can be pasted into the current target.
+- If you produce a replacement value for the target, call the ${AI_HELPER_APPLY_TOOL} tool with the exact value.
+- Use mode "replace" unless the user explicitly asks to insert or append.
+- Do not include markdown fences inside tool values.
+- Respect the current language/field kind and keep generated values syntactically valid.`;
+}
+
+export function buildAIHelperTools() {
+ return [
+ {
+ type: "function",
+ function: {
+ name: AI_HELPER_APPLY_TOOL,
+ description: "Offer a generated value that Lowcoder can apply to the focused editor field.",
+ parameters: {
+ type: "object",
+ properties: {
+ label: {
+ type: "string",
+ description: "Short button label, e.g. Replace SQL or Apply option JSON.",
+ },
+ value: {
+ type: "string",
+ description: "The exact code/data to place in the focused field.",
+ },
+ mode: {
+ type: "string",
+ enum: ["replace", "insertAtCursor", "append"],
+ description: "How to apply the value. Use replace unless the user asks otherwise.",
+ },
+ language: {
+ type: "string",
+ description: "Optional language hint such as sql, javascript, or json.",
+ },
+ },
+ required: ["value", "mode"],
+ },
+ },
+ },
+ ];
+}
diff --git a/client/packages/lowcoder/src/components/ai-helper/handlers/AIHelperQueryHandler.ts b/client/packages/lowcoder/src/components/ai-helper/handlers/AIHelperQueryHandler.ts
new file mode 100644
index 0000000000..cf9a048d5d
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/handlers/AIHelperQueryHandler.ts
@@ -0,0 +1,90 @@
+import { executeQueryAction, routeByNameAction } from "lowcoder-core";
+import { getPromiseAfterDispatch } from "util/promiseUtils";
+
+import type { ChatMessage } from "comps/comps/chatComp/types/chatTypes";
+import {
+ getTextFromThreadContent,
+ toAssistantMessage,
+} from "comps/comps/chatComp/utils/assistantMessages";
+
+import {
+ buildAIHelperTargetContext,
+ buildAIHelperSystemMessage,
+ buildAIHelperTools,
+ type AIHelperTargetContext,
+} from "../context/buildAIHelperContext";
+import type { AIHelperTarget } from "../types";
+
+interface AIHelperQueryHandlerConfig {
+ helperQueryName: string;
+ dispatch: any;
+ getDatasourceStructures: () => Record | undefined;
+ target: AIHelperTarget;
+}
+
+function buildHelperPayload(args: {
+ conversationHistory: ChatMessage[];
+ target: AIHelperTargetContext;
+}) {
+ const { conversationHistory, target } = args;
+ const messagesWithoutSystem = conversationHistory.map((msg) => ({
+ role: msg.role,
+ content: getTextFromThreadContent(msg.content),
+ }));
+ const system = buildAIHelperSystemMessage(target);
+ const tools = buildAIHelperTools();
+ const messages = [
+ { role: "system" as const, content: system },
+ ...messagesWithoutSystem,
+ ];
+
+ return {
+ mode: "helper" as const,
+ messages,
+ tools,
+ target,
+ };
+}
+
+export class AIHelperQueryHandler {
+ constructor(private readonly config: AIHelperQueryHandlerConfig) {}
+
+ async sendMessage(conversationHistory: ChatMessage[]): Promise {
+ const {
+ helperQueryName,
+ dispatch,
+ getDatasourceStructures,
+ target,
+ } = this.config;
+
+ if (!helperQueryName) {
+ throw new Error("Select an AI query before sending a message");
+ }
+ if (!dispatch) {
+ throw new Error("AI Helper dispatch is unavailable");
+ }
+
+ const targetContext = buildAIHelperTargetContext({
+ datasourceStructures: getDatasourceStructures(),
+ target,
+ });
+ const ai = buildHelperPayload({
+ conversationHistory,
+ target: targetContext,
+ });
+
+ const result: any = await getPromiseAfterDispatch(
+ dispatch,
+ routeByNameAction(
+ helperQueryName,
+ executeQueryAction({
+ args: {
+ ai: { value: ai },
+ },
+ })
+ )
+ );
+
+ return toAssistantMessage(result);
+ }
+}
diff --git a/client/packages/lowcoder/src/components/ai-helper/index.ts b/client/packages/lowcoder/src/components/ai-helper/index.ts
new file mode 100644
index 0000000000..6e42ab2535
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/index.ts
@@ -0,0 +1,11 @@
+export { AIHelperModal } from "./AIHelperModal";
+export { AIHelperProvider, useAIHelper } from "./context/AIHelperController";
+export { CodeEditorAIHelpButton } from "./CodeEditorAIHelpButton";
+export type { CodeEditorAIHelpButtonProps } from "./CodeEditorAIHelpButton";
+export type {
+ AIHelperApplyAction,
+ AIHelperApplyMode,
+ AIHelperTarget,
+ AIHelperTargetKind,
+} from "./types";
+
diff --git a/client/packages/lowcoder/src/components/ai-helper/types.ts b/client/packages/lowcoder/src/components/ai-helper/types.ts
new file mode 100644
index 0000000000..c5f0e7208c
--- /dev/null
+++ b/client/packages/lowcoder/src/components/ai-helper/types.ts
@@ -0,0 +1,36 @@
+export const AI_HELPER_APPLY_TOOL = "apply_ai_helper_result";
+
+export type AIHelperTargetKind =
+ | "sql"
+ | "javascript"
+ | "echarts-option"
+ | "json"
+ | "component-field";
+
+export type AIHelperApplyMode = "replace" | "insertAtCursor" | "append";
+
+export interface AIHelperTarget {
+ id: string;
+ kind: AIHelperTargetKind;
+ label?: string;
+ language?: "sql" | "javascript" | "css" | "html" | "json";
+ currentValue?: string;
+ selection?: string;
+ cursor?: number;
+ datasourceId?: string;
+ datasourceType?: string;
+ datasourceSchema?: Record;
+ queryType?: string;
+ queryName?: string;
+ componentName?: string;
+ fieldName?: string;
+ fieldDescription?: string;
+}
+
+export interface AIHelperApplyAction {
+ id: string;
+ label: string;
+ value: string;
+ mode: AIHelperApplyMode;
+ language?: string;
+}
diff --git a/client/packages/lowcoder/src/components/assistant-ui/assistant-message-loader.tsx b/client/packages/lowcoder/src/components/assistant-ui/assistant-message-loader.tsx
new file mode 100644
index 0000000000..3115237e7a
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/assistant-message-loader.tsx
@@ -0,0 +1,26 @@
+import { LoadingOutlined } from "@ant-design/icons";
+import { Spin } from "antd";
+import type { FC } from "react";
+import styled from "styled-components";
+
+const LoaderRoot = styled.div`
+ align-items: center;
+ color: #6b7280;
+ display: flex;
+ font-size: 14px;
+ gap: 12px;
+ line-height: 20px;
+ min-height: 28px;
+`;
+
+const LoaderIcon = styled(LoadingOutlined)`
+ color: #1677ff;
+ font-size: 18px;
+`;
+
+export const AssistantMessageLoader: FC = () => (
+
+ } size="small" />
+ Working on it...
+
+);
diff --git a/client/packages/lowcoder/src/components/assistant-ui/markdown-text.styles.ts b/client/packages/lowcoder/src/components/assistant-ui/markdown-text.styles.ts
new file mode 100644
index 0000000000..498c8561bf
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/markdown-text.styles.ts
@@ -0,0 +1,116 @@
+import { MarkdownTextPrimitive } from "@assistant-ui/react-markdown";
+import styled from "styled-components";
+
+export const StyledMarkdownTextPrimitive = styled(MarkdownTextPrimitive)`
+ color: inherit;
+ font-size: inherit;
+ line-height: inherit;
+
+ .aui-md-h1,
+ .aui-md-h2,
+ .aui-md-h3,
+ .aui-md-h4,
+ .aui-md-h5,
+ .aui-md-h6 {
+ color: #111827;
+ font-weight: 600;
+ line-height: 1.35;
+ margin: 14px 0 8px;
+ }
+
+ .aui-md-h1 {
+ font-size: 18px;
+ }
+
+ .aui-md-h2,
+ .aui-md-h3 {
+ font-size: 16px;
+ }
+
+ .aui-md-h4,
+ .aui-md-h5,
+ .aui-md-h6,
+ .aui-md-p {
+ font-size: 14px;
+ }
+
+ .aui-md-p {
+ margin: 0 0 10px;
+ }
+
+ .aui-md-a {
+ color: #1677ff;
+ }
+
+ .aui-md-blockquote {
+ border-left: 3px solid #d9d9d9;
+ color: #4b5563;
+ margin: 12px 0;
+ padding: 2px 0 2px 12px;
+ }
+
+ .aui-md-ul,
+ .aui-md-ol {
+ margin: 8px 0 10px;
+ padding-left: 22px;
+ }
+
+ .aui-md-hr {
+ border: 0;
+ border-top: 1px solid #e5e7eb;
+ margin: 16px 0;
+ }
+
+ .aui-md-table {
+ border-collapse: collapse;
+ margin: 12px 0;
+ width: 100%;
+ }
+
+ .aui-md-th,
+ .aui-md-td {
+ border: 1px solid #e5e7eb;
+ padding: 6px 8px;
+ text-align: left;
+ }
+
+ .aui-md-th {
+ background: #f3f4f6;
+ font-weight: 600;
+ }
+
+ .aui-md-pre {
+ background: #111827;
+ border-radius: 0 0 8px 8px;
+ color: #f9fafb;
+ margin: 0 0 12px;
+ overflow-x: auto;
+ padding: 12px;
+ }
+
+ .aui-md-inline-code {
+ background: #f3f4f6;
+ border-radius: 4px;
+ color: #111827;
+ padding: 1px 4px;
+ }
+
+ .aui-code-header-root {
+ align-items: center;
+ background: #f3f4f6;
+ border: 1px solid #e5e7eb;
+ border-bottom: 0;
+ border-radius: 8px 8px 0 0;
+ display: flex;
+ font-size: 12px;
+ justify-content: space-between;
+ margin-top: 10px;
+ padding: 6px 10px;
+ }
+
+ .aui-code-header-language {
+ color: #4b5563;
+ font-weight: 500;
+ text-transform: lowercase;
+ }
+`;
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/markdown-text.tsx b/client/packages/lowcoder/src/components/assistant-ui/markdown-text.tsx
similarity index 96%
rename from client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/markdown-text.tsx
rename to client/packages/lowcoder/src/components/assistant-ui/markdown-text.tsx
index bbf2e5648a..68c156e06c 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/markdown-text.tsx
+++ b/client/packages/lowcoder/src/components/assistant-ui/markdown-text.tsx
@@ -2,20 +2,20 @@ 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 { StyledMarkdownTextPrimitive } from "./markdown-text.styles";
import { TooltipIconButton } from "./tooltip-icon-button";
-import { cn } from "../../utils/cn";
+import { cn } from "./utils/cn";
const MarkdownTextImpl = () => {
return (
- `
+ border: ${({ $variant }) => ($variant === "outline" ? "1px solid #e5e7eb" : "0")};
+ border-radius: ${({ $variant }) => ($variant === "ghost" ? "0" : "8px")};
+ background: ${({ $variant }) => ($variant === "muted" ? "#f3f4f6" : "transparent")};
+ margin-bottom: 16px;
+ padding: ${({ $variant }) => ($variant === "ghost" ? "0" : "8px 12px")};
+ width: 100%;
+`;
+
+const StyledReasoningTrigger = styled(CollapsibleTrigger)`
+ align-items: center;
+ background: transparent;
+ border: 0;
+ color: #6b7280;
+ cursor: pointer;
+ display: flex;
+ font-size: 14px;
+ gap: 8px;
+ line-height: 20px;
+ max-width: 75%;
+ padding: 4px 0;
+ text-align: left;
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: #1f2937;
+ }
+
+ .aui-reasoning-trigger-icon,
+ .aui-reasoning-trigger-chevron {
+ flex: 0 0 auto;
+ height: 16px;
+ width: 16px;
+ }
+
+ .aui-reasoning-trigger-label-wrapper {
+ display: inline-block;
+ line-height: 1;
+ position: relative;
+ }
+
+ .aui-reasoning-trigger-shimmer {
+ background: linear-gradient(90deg, transparent, rgba(22, 119, 255, 0.35), transparent);
+ background-size: 200% 100%;
+ inset: 0;
+ pointer-events: none;
+ position: absolute;
+ -webkit-background-clip: text;
+ color: transparent;
+ animation: ${shimmer} 1.6s linear infinite;
+ }
+
+ .aui-reasoning-trigger-chevron {
+ margin-top: 2px;
+ transform: rotate(0deg);
+ transition: transform var(--animation-duration, 200ms) ease-out;
+ }
+
+ &[data-state="closed"] .aui-reasoning-trigger-chevron {
+ transform: rotate(-90deg);
+ }
+`;
+
+const StyledReasoningContent = styled(CollapsibleContent)`
+ color: #6b7280;
+ font-size: 14px;
+ line-height: 22px;
+ outline: none;
+ overflow: hidden;
+ position: relative;
+`;
+
+const StyledReasoningText = styled.div`
+ max-height: 256px;
+ overflow-y: auto;
+ padding: 8px 0 8px 24px;
+ position: relative;
+ z-index: 0;
+
+ > * + * {
+ margin-top: 16px;
+ }
+`;
+
+const StyledReasoningFade = styled.div`
+ background: linear-gradient(to top, #f9fafb, rgba(249, 250, 251, 0));
+ bottom: 0;
+ height: 32px;
+ left: 0;
+ pointer-events: none;
+ position: absolute;
+ right: 0;
+ z-index: 1;
+`;
+
+export type ReasoningRootProps = Omit<
+ React.ComponentPropsWithoutRef,
+ "open" | "onOpenChange"
+> &
+ {
+ variant?: ReasoningVariant;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ defaultOpen?: boolean;
+ };
+
+function ReasoningRoot({
+ className,
+ variant,
+ open: controlledOpen,
+ onOpenChange: controlledOnOpenChange,
+ defaultOpen = false,
+ children,
+ ...props
+}: ReasoningRootProps) {
+ const collapsibleRef = useRef(null);
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
+ const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);
+
+ const isControlled = controlledOpen !== undefined;
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
+
+ const handleOpenChange = useCallback(
+ (open: boolean) => {
+ if (!open) {
+ lockScroll();
+ }
+ if (!isControlled) {
+ setUncontrolledOpen(open);
+ }
+ controlledOnOpenChange?.(open);
+ },
+ [lockScroll, isControlled, controlledOnOpenChange],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+function ReasoningFade({ className, ...props }: React.ComponentPropsWithoutRef<"div">) {
+ return (
+
+ );
+}
+
+function ReasoningTrigger({
+ active,
+ duration,
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef & {
+ active?: boolean;
+ duration?: number;
+}) {
+ const durationText = duration ? ` (${duration}s)` : "";
+
+ return (
+
+
+
+ Reasoning{durationText}
+ {active ? (
+
+ Reasoning{durationText}
+
+ ) : null}
+
+
+
+ );
+}
+
+function ReasoningContent({
+ className,
+ children,
+ ...props
+}: React.ComponentPropsWithoutRef) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function ReasoningText({ className, ...props }: React.ComponentPropsWithoutRef<"div">) {
+ return (
+
+ );
+}
+
+const ReasoningImpl: ReasoningMessagePartComponent = () => ;
+
+const ReasoningGroupImpl: ReasoningGroupComponent = ({
+ children,
+ startIndex,
+ endIndex,
+}) => {
+ const isReasoningStreaming = useAuiState((s) => {
+ if (s.message.status?.type !== "running") return false;
+ const lastIndex = s.message.parts.length - 1;
+ if (lastIndex < 0) return false;
+ const lastType = s.message.parts[lastIndex]?.type;
+ if (lastType !== "reasoning") return false;
+ return lastIndex >= startIndex && lastIndex <= endIndex;
+ });
+
+ return (
+
+
+
+ {children}
+
+
+ );
+};
+
+const Reasoning = memo(
+ ReasoningImpl,
+) as unknown as ReasoningMessagePartComponent & {
+ Root: typeof ReasoningRoot;
+ Trigger: typeof ReasoningTrigger;
+ Content: typeof ReasoningContent;
+ Text: typeof ReasoningText;
+ Fade: typeof ReasoningFade;
+};
+
+Reasoning.displayName = "Reasoning";
+Reasoning.Root = ReasoningRoot;
+Reasoning.Trigger = ReasoningTrigger;
+Reasoning.Content = ReasoningContent;
+Reasoning.Text = ReasoningText;
+Reasoning.Fade = ReasoningFade;
+
+/**
+ * @deprecated This wrapper targets the legacy `components.ReasoningGroup`
+ * prop on ``. Use ``
+ * with a `groupBy` returning `"group-reasoning"` and compose `ReasoningRoot`
+ * / `ReasoningTrigger` / `ReasoningContent` / `ReasoningText` directly.
+ * See `thread.tsx` for an example.
+ */
+const ReasoningGroup = memo(ReasoningGroupImpl);
+ReasoningGroup.displayName = "ReasoningGroup";
+
+export {
+ Reasoning,
+ ReasoningGroup,
+ ReasoningRoot,
+ ReasoningTrigger,
+ ReasoningContent,
+ ReasoningText,
+ ReasoningFade,
+};
diff --git a/client/packages/lowcoder/src/components/assistant-ui/thread-composer.tsx b/client/packages/lowcoder/src/components/assistant-ui/thread-composer.tsx
new file mode 100644
index 0000000000..18405fdde6
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/thread-composer.tsx
@@ -0,0 +1,103 @@
+import {
+ AuiIf,
+ ComposerPrimitive,
+ MessagePrimitive,
+} from "@assistant-ui/react";
+import { ArrowUpIcon, SquareIcon } from "lucide-react";
+import type { FC } from "react";
+import { trans } from "i18n";
+
+import {
+ ComposerAddAttachment,
+ ComposerAttachments,
+} from "./ui/attachment";
+import { Button } from "./ui/button";
+import { TooltipIconButton } from "./tooltip-icon-button";
+
+export const Composer: FC<{
+ placeholder?: string;
+ showAttachments?: boolean;
+}> = ({
+ placeholder = trans("chat.composerPlaceholder"),
+ showAttachments = true,
+}) => {
+ return (
+
+
+
+ {showAttachments && }
+
+
+
+
+
+ );
+};
+
+export const EditComposer: FC = () => {
+ return (
+
+
+
+
+
+ Cancel
+
+
+ Update
+
+
+
+
+ );
+};
+
+const ComposerAction: FC<{ showAttachments?: boolean }> = ({
+ showAttachments = true,
+}) => {
+ return (
+
+ {showAttachments ?
:
}
+
!s.thread.isRunning}>
+
+
+
+
+
+
+
s.thread.isRunning}>
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/packages/lowcoder/src/components/assistant-ui/thread-list.styles.ts b/client/packages/lowcoder/src/components/assistant-ui/thread-list.styles.ts
new file mode 100644
index 0000000000..223b202a24
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/thread-list.styles.ts
@@ -0,0 +1,155 @@
+import {
+ ThreadListItemMorePrimitive,
+ ThreadListItemPrimitive,
+ ThreadListPrimitive,
+} from "@assistant-ui/react";
+import styled from "styled-components";
+
+import { Button } from "./ui/button";
+
+export const StyledThreadListRoot = styled(ThreadListPrimitive.Root)`
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-height: 0;
+ overflow: hidden;
+`;
+
+export const SkeletonStack = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+`;
+
+export const SkeletonRow = styled.div`
+ align-items: center;
+ display: flex;
+ height: 36px;
+ padding: 0 12px;
+`;
+
+export const SkeletonBar = styled.div`
+ background: #eef0f3;
+ border-radius: 4px;
+ height: 16px;
+ width: 100%;
+`;
+
+export const StyledNewThreadButton = styled(Button)`
+ justify-content: flex-start;
+ width: 100%;
+`;
+
+export const StyledThreadListItem = styled(ThreadListItemPrimitive.Root)`
+ align-items: center;
+ border-radius: 8px;
+ display: flex;
+ gap: 6px;
+ height: 36px;
+ min-width: 0;
+ transition: background-color 0.2s ease;
+
+ &:hover,
+ &:focus-within,
+ &[data-active],
+ &[data-active="true"] {
+ background: #f3f4f6;
+ }
+
+ .aui-thread-list-item-more {
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ }
+
+ &:hover .aui-thread-list-item-more,
+ &:focus-within .aui-thread-list-item-more,
+ &[data-active] .aui-thread-list-item-more,
+ &[data-active="true"] .aui-thread-list-item-more {
+ opacity: 1;
+ }
+`;
+
+export const StyledThreadListTrigger = styled(ThreadListItemPrimitive.Trigger)`
+ align-items: center;
+ background: transparent;
+ border: 0;
+ color: #1f2937;
+ cursor: pointer;
+ display: flex;
+ flex: 1;
+ font-size: 14px;
+ height: 100%;
+ min-width: 0;
+ overflow: hidden;
+ padding: 0 12px;
+ text-align: left;
+`;
+
+export const ThreadTitle = styled.span`
+ display: block;
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
+export const StyledThreadRenameForm = styled.form`
+ align-items: center;
+ display: flex;
+ flex: 1;
+ height: 100%;
+ min-width: 0;
+ padding: 0 6px;
+`;
+
+export const StyledThreadRenameInput = styled.input`
+ background: #ffffff;
+ border: 1px solid #1677ff;
+ border-radius: 6px;
+ color: #1f2937;
+ flex: 1;
+ font-size: 14px;
+ height: 28px;
+ min-width: 0;
+ outline: none;
+ padding: 0 8px;
+
+ &:focus {
+ box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.14);
+ }
+`;
+
+export const StyledMenuContent = styled(ThreadListItemMorePrimitive.Content)`
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
+ min-width: 144px;
+ padding: 4px;
+ z-index: 1000;
+`;
+
+export const StyledMenuItem = styled(ThreadListItemMorePrimitive.Item)<{
+ $danger?: boolean;
+}>`
+ align-items: center;
+ border-radius: 6px;
+ color: ${(props) => (props.$danger ? "#cf1322" : "#1f2937")};
+ cursor: pointer;
+ display: flex;
+ font-size: 14px;
+ gap: 8px;
+ outline: none;
+ padding: 7px 8px;
+
+ &:hover,
+ &:focus {
+ background: ${(props) => (props.$danger ? "#fff1f0" : "#f3f4f6")};
+ }
+
+ svg {
+ height: 16px;
+ width: 16px;
+ }
+`;
diff --git a/client/packages/lowcoder/src/components/assistant-ui/thread-list.tsx b/client/packages/lowcoder/src/components/assistant-ui/thread-list.tsx
new file mode 100644
index 0000000000..4f0eb7873d
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/thread-list.tsx
@@ -0,0 +1,201 @@
+import { Button } from "./ui/button";
+import {
+ AuiIf,
+ ThreadListItemMorePrimitive,
+ ThreadListItemPrimitive,
+ ThreadListPrimitive,
+ useAui,
+ useAuiState,
+} from "@assistant-ui/react";
+import {
+ MoreHorizontalIcon,
+ PencilIcon,
+ PlusIcon,
+ TrashIcon,
+} from "lucide-react";
+import { trans } from "i18n";
+import type { FC, KeyboardEvent } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
+import {
+ SkeletonBar,
+ SkeletonRow,
+ SkeletonStack,
+ StyledMenuContent,
+ StyledMenuItem,
+ StyledThreadRenameForm,
+ StyledThreadRenameInput,
+ StyledNewThreadButton,
+ StyledThreadListItem,
+ StyledThreadListRoot,
+ StyledThreadListTrigger,
+ ThreadTitle,
+} from "./thread-list.styles";
+
+const ThreadListSkeleton: FC = () => {
+ return (
+
+ {Array.from({ length: 5 }, (_, i) => (
+
+
+
+ ))}
+
+ );
+};
+
+export const ThreadList: FC = () => {
+ return (
+
+
+ s.threads.isLoading}>
+
+
+ !s.threads.isLoading}>
+
+ {() => }
+
+
+
+ );
+};
+
+const ThreadListNew: FC = () => {
+ return (
+
+
+
+ {trans("chat.newThread")}
+
+
+ );
+};
+
+const ThreadListItem: FC = () => {
+ const aui = useAui();
+ const title =
+ useAuiState((s) => s.threadListItem.title) || trans("chat.newChatTitle");
+ const [isEditing, setIsEditing] = useState(false);
+ const [draftTitle, setDraftTitle] = useState(title);
+ const inputRef = useRef(null);
+ const isSavingRef = useRef(false);
+ const skipNextBlurSaveRef = useRef(false);
+
+ useEffect(() => {
+ if (!isEditing) setDraftTitle(title);
+ }, [isEditing, title]);
+
+ useEffect(() => {
+ if (!isEditing) return;
+
+ inputRef.current?.focus();
+ inputRef.current?.select();
+ }, [isEditing]);
+
+ const startEditing = useCallback(() => {
+ skipNextBlurSaveRef.current = false;
+ setDraftTitle(title);
+ setIsEditing(true);
+ }, [title]);
+
+ const cancelEditing = useCallback(() => {
+ skipNextBlurSaveRef.current = true;
+ setDraftTitle(title);
+ setIsEditing(false);
+ }, [title]);
+
+ const saveTitle = useCallback(async () => {
+ if (isSavingRef.current) return;
+ isSavingRef.current = true;
+
+ const nextTitle = draftTitle.trim();
+
+ try {
+ if (nextTitle && nextTitle !== title) {
+ await aui.threadListItem().rename(nextTitle);
+ }
+ setIsEditing(false);
+ } finally {
+ isSavingRef.current = false;
+ }
+ }, [aui, draftTitle, title]);
+
+ const handleRenameKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ void saveTitle();
+ return;
+ }
+
+ if (event.key === "Escape") {
+ event.preventDefault();
+ cancelEditing();
+ }
+ };
+
+ return (
+
+ {isEditing ? (
+ {
+ event.preventDefault();
+ void saveTitle();
+ }}
+ >
+ {
+ if (skipNextBlurSaveRef.current) {
+ skipNextBlurSaveRef.current = false;
+ return;
+ }
+
+ void saveTitle();
+ }}
+ onChange={(event) => setDraftTitle(event.target.value)}
+ onKeyDown={handleRenameKeyDown}
+ />
+
+ ) : (
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+const ThreadListItemMore: FC<{ onRename: () => void }> = ({ onRename }) => {
+ return (
+
+
+
+
+
+
+
+
+
+ {trans("rename")}
+
+
+
+
+ Delete
+
+
+
+
+ );
+};
diff --git a/client/packages/lowcoder/src/components/assistant-ui/thread-message.tsx b/client/packages/lowcoder/src/components/assistant-ui/thread-message.tsx
new file mode 100644
index 0000000000..1d86416cd5
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/thread-message.tsx
@@ -0,0 +1,264 @@
+import {
+ ActionBarMorePrimitive,
+ ActionBarPrimitive,
+ AuiIf,
+ BranchPickerPrimitive,
+ ErrorPrimitive,
+ getMcpAppFromToolPart,
+ MessagePrimitive,
+ useAuiState,
+} from "@assistant-ui/react";
+import {
+ CheckIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ CopyIcon,
+ DownloadIcon,
+ MoreHorizontalIcon,
+ PencilIcon,
+} from "lucide-react";
+import type { FC } from "react";
+
+import { AssistantMessageLoader } from "./assistant-message-loader";
+import { MarkdownText } from "./markdown-text";
+import { EditComposer } from "./thread-composer";
+import {
+ Reasoning,
+ ReasoningContent,
+ ReasoningRoot,
+ ReasoningText,
+ ReasoningTrigger,
+} from "./reasoning";
+import {
+ ToolGroupContent,
+ ToolGroupRoot,
+ ToolGroupTrigger,
+} from "./tool-group";
+import { ToolFallback } from "./tool-fallback";
+import { TooltipIconButton } from "./tooltip-icon-button";
+import { UserMessageAttachments } from "./ui/attachment";
+
+export const ThreadMessage: FC<{ showAttachments?: boolean }> = ({
+ showAttachments = true,
+}) => {
+ const role = useAuiState((s) => s.message.role);
+ const isEditing = useAuiState((s) => s.message.composer.isEditing);
+
+ if (isEditing) return ;
+ if (role === "user") return ;
+ return ;
+};
+
+const MessageError: FC = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+const AssistantMessage: FC = () => {
+ const isEmptyRunningMessage = useAuiState(
+ (s) =>
+ s.message.parts.length === 0 &&
+ (s.message.status?.type ?? "complete") === "running"
+ );
+
+ return (
+
+
+ {isEmptyRunningMessage &&
}
+
{
+ if (part.type === "reasoning")
+ return ["group-chainOfThought", "group-reasoning"];
+ if (part.type === "tool-call") {
+ if (getMcpAppFromToolPart(part)) return null;
+ return ["group-chainOfThought", "group-tool"];
+ }
+ return null;
+ }}
+ >
+ {({ part, children }) => {
+ switch (part.type) {
+ case "group-chainOfThought":
+ return {children}
;
+ case "group-reasoning": {
+ const running = part.status.type === "running";
+ return (
+
+
+
+ {children}
+
+
+ );
+ }
+ case "group-tool":
+ return (
+
+
+ {children}
+
+ );
+ case "text":
+ if (part.status?.type === "running" && part.text === "") {
+ return ;
+ }
+ return ;
+ case "reasoning":
+ return ;
+ case "tool-call":
+ return part.toolUI ?? ;
+ default:
+ return null;
+ }
+ }}
+
+
+
+
+
+
+ );
+};
+
+const AssistantActionBar: FC = () => {
+ return (
+
+
+
+ s.message.isCopied}>
+
+
+ !s.message.isCopied}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Export as Markdown
+
+
+
+
+
+ );
+};
+
+const UserMessage: FC<{ showAttachments?: boolean }> = ({
+ showAttachments = true,
+}) => {
+ return (
+
+ {showAttachments && }
+
+
+
+
+
+ );
+};
+
+const UserActionBar: FC = () => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+const BranchPicker: FC = ({
+ className,
+ ...rest
+}) => {
+ return (
+
+
+
+
+
+
+
+ /
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/packages/lowcoder/src/components/assistant-ui/thread-welcome.tsx b/client/packages/lowcoder/src/components/assistant-ui/thread-welcome.tsx
new file mode 100644
index 0000000000..538edc48c2
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/thread-welcome.tsx
@@ -0,0 +1,49 @@
+import { ThreadPrimitive } from "@assistant-ui/react";
+import type { FC } from "react";
+import { trans } from "i18n";
+
+export const ThreadWelcome: FC = () => {
+ return (
+
+
+
+
+ {trans("chat.welcomeMessage")}
+
+
+
+
+
+ );
+};
+
+const ThreadSuggestions: FC = () => {
+ return (
+
+
+
+
+ {trans("chat.suggestionWeather")}
+
+
+
+
+
+
+ {trans("chat.suggestionAssistant")}
+
+
+
+
+ );
+};
diff --git a/client/packages/lowcoder/src/components/assistant-ui/thread.styles.ts b/client/packages/lowcoder/src/components/assistant-ui/thread.styles.ts
new file mode 100644
index 0000000000..b8faaac3c1
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/thread.styles.ts
@@ -0,0 +1,359 @@
+import { ThreadPrimitive } from "@assistant-ui/react";
+import styled from "styled-components";
+
+export const StyledThreadRoot = styled(ThreadPrimitive.Root)`
+ background: #f9fafb;
+ color: #1f2937;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ min-height: 0;
+ overflow: hidden;
+
+ .aui-thread-viewport {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ min-height: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ scroll-behavior: smooth;
+ }
+
+ .aui-thread-layout {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ margin: 0 auto;
+ max-width: var(--thread-max-width);
+ min-height: 0;
+ padding: 16px;
+ width: 100%;
+ }
+
+ .aui-message-group {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ margin-bottom: 24px;
+ }
+
+ .aui-message-group:empty {
+ display: none;
+ }
+
+ .aui-thread-welcome-root {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ justify-content: center;
+ margin: auto 0;
+ }
+
+ .aui-thread-welcome-center {
+ align-items: center;
+ display: flex;
+ flex: 1;
+ justify-content: center;
+ width: 100%;
+ }
+
+ .aui-thread-welcome-message {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 0 16px;
+ width: 100%;
+ }
+
+ .aui-thread-welcome-message-inner {
+ color: #111827;
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 28px;
+ margin: 0;
+ }
+
+ .aui-thread-welcome-suggestions {
+ display: grid;
+ gap: 8px;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ padding-bottom: 16px;
+ width: 100%;
+ }
+
+ .aui-thread-welcome-suggestion {
+ align-items: flex-start;
+ background: #ffffff;
+ border: 1px solid #d9d9d9;
+ border-radius: 16px;
+ color: #1f2937;
+ cursor: pointer;
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 14px;
+ gap: 4px;
+ min-height: 44px;
+ padding: 10px 14px;
+ text-align: left;
+ transition:
+ background-color 0.2s ease,
+ border-color 0.2s ease;
+ width: 100%;
+ }
+
+ .aui-thread-welcome-suggestion:hover {
+ background: #f3f4f6;
+ border-color: #bfbfbf;
+ }
+
+ .aui-thread-welcome-suggestion-text-1 {
+ font-weight: 500;
+ }
+
+ .aui-thread-viewport-footer {
+ background: linear-gradient(180deg, rgba(249, 250, 251, 0), #f9fafb 24px);
+ bottom: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ margin-top: auto;
+ overflow: visible;
+ padding-bottom: 16px;
+ padding-top: 24px;
+ position: sticky;
+ z-index: 2;
+ }
+
+ .aui-thread-scroll-to-bottom {
+ align-self: center;
+ box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12);
+ }
+
+ .aui-composer-root {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ width: 100%;
+ }
+
+ .aui-composer-shell {
+ background: #ffffff;
+ border: 1px solid #d9d9d9;
+ border-radius: var(--composer-radius);
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: var(--composer-padding);
+ transition:
+ border-color 0.2s ease,
+ box-shadow 0.2s ease;
+ width: 100%;
+ }
+
+ .aui-composer-shell:focus-within {
+ border-color: #1677ff;
+ box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.12);
+ }
+
+ .aui-composer-input {
+ background: transparent;
+ border: 0;
+ color: #1f2937;
+ font-size: 14px;
+ line-height: 22px;
+ max-height: 128px;
+ min-height: 40px;
+ outline: 0;
+ padding: 4px 6px;
+ resize: none;
+ width: 100%;
+ }
+
+ .aui-composer-input::placeholder {
+ color: #8c8c8c;
+ }
+
+ .aui-composer-action-wrapper {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ min-height: 32px;
+ }
+
+ .aui-composer-add-attachment,
+ .aui-composer-send,
+ .aui-composer-cancel {
+ border-radius: 50%;
+ }
+
+ .aui-assistant-message-root {
+ margin: 0 auto;
+ max-width: var(--thread-max-width);
+ position: relative;
+ width: 100%;
+ }
+
+ .aui-assistant-message-content {
+ color: #1f2937;
+ font-size: 14px;
+ line-height: 24px;
+ padding: 0 8px;
+ word-break: break-word;
+ }
+
+ .aui-assistant-message-footer {
+ align-items: center;
+ display: flex;
+ margin-left: 8px;
+ min-height: 28px;
+ }
+
+ .aui-assistant-action-bar-root {
+ align-items: center;
+ color: #6b7280;
+ display: flex;
+ gap: 4px;
+ }
+
+ .aui-action-bar-more-content {
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
+ min-width: 144px;
+ padding: 4px;
+ z-index: 1000;
+ }
+
+ .aui-action-bar-more-item {
+ align-items: center;
+ border-radius: 6px;
+ color: #1f2937;
+ cursor: pointer;
+ display: flex;
+ font-size: 14px;
+ gap: 8px;
+ outline: none;
+ padding: 7px 8px;
+ }
+
+ .aui-action-bar-more-item:hover,
+ .aui-action-bar-more-item:focus {
+ background: #f3f4f6;
+ }
+
+ .aui-user-message-root {
+ animation: none;
+ display: grid;
+ grid-auto-rows: auto;
+ grid-template-columns: minmax(72px, 1fr) auto;
+ gap: 8px 0;
+ margin: 0 auto;
+ max-width: var(--thread-max-width);
+ padding: 0 8px;
+ width: 100%;
+ }
+
+ .aui-user-message-content-wrapper {
+ grid-column-start: 2;
+ min-width: 0;
+ position: relative;
+ }
+
+ .aui-user-message-content {
+ background: #e5e7eb;
+ border-radius: 16px;
+ color: #111827;
+ font-size: 14px;
+ line-height: 22px;
+ padding: 10px 16px;
+ word-break: break-word;
+ }
+
+ .aui-user-action-bar-wrapper {
+ padding-right: 8px;
+ position: absolute;
+ right: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+
+ .aui-user-action-bar-root {
+ align-items: flex-end;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .aui-user-branch-picker {
+ grid-column: 1 / -1;
+ grid-row-start: 3;
+ justify-content: flex-end;
+ }
+
+ .aui-edit-composer-wrapper {
+ display: flex;
+ flex-direction: column;
+ padding: 0 8px;
+ }
+
+ .aui-edit-composer-root {
+ align-self: flex-end;
+ background: #e5e7eb;
+ border-radius: 16px;
+ display: flex;
+ flex-direction: column;
+ max-width: 85%;
+ width: 100%;
+ }
+
+ .aui-edit-composer-input {
+ background: transparent;
+ border: 0;
+ color: #111827;
+ font-size: 14px;
+ min-height: 56px;
+ outline: 0;
+ padding: 16px;
+ resize: none;
+ width: 100%;
+ }
+
+ .aui-edit-composer-footer {
+ align-items: center;
+ align-self: flex-end;
+ display: flex;
+ gap: 8px;
+ margin: 0 12px 12px;
+ }
+
+ .aui-branch-picker-root {
+ align-items: center;
+ color: #6b7280;
+ display: inline-flex;
+ font-size: 12px;
+ gap: 4px;
+ margin-left: -8px;
+ margin-right: 8px;
+ }
+
+ .aui-branch-picker-state {
+ font-weight: 500;
+ min-width: 36px;
+ text-align: center;
+ }
+
+ .aui-tooltip-icon-button svg,
+ .aui-button svg {
+ height: 16px;
+ width: 16px;
+ }
+
+ .aui-assistant-message-root:has([data-status="running"]) {
+ display: none;
+ }
+
+ .aui-assistant-message-content [data-status="running"] {
+ display: none;
+ }
+`;
diff --git a/client/packages/lowcoder/src/components/assistant-ui/thread.tsx b/client/packages/lowcoder/src/components/assistant-ui/thread.tsx
new file mode 100644
index 0000000000..69032704ef
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/thread.tsx
@@ -0,0 +1,73 @@
+import { AuiIf, ThreadPrimitive } from "@assistant-ui/react";
+import { ArrowDownIcon } from "lucide-react";
+import type { FC } from "react";
+import { trans } from "i18n";
+
+import { Composer } from "./thread-composer";
+import { StyledThreadRoot } from "./thread.styles";
+import { ThreadMessage } from "./thread-message";
+import { ThreadWelcome } from "./thread-welcome";
+import { TooltipIconButton } from "./tooltip-icon-button";
+
+interface ThreadProps {
+ placeholder?: string;
+ showAttachments?: boolean;
+ autoHeight?: boolean;
+}
+
+export const Thread: FC = ({
+ placeholder = trans("chat.composerPlaceholder"),
+ showAttachments = true,
+ autoHeight = false,
+}) => {
+ return (
+
+
+
+
s.thread.isEmpty}>
+
+
+
+
+
+ {() => }
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const ThreadScrollToBottom: FC = () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/client/packages/lowcoder/src/components/assistant-ui/tool-fallback.tsx b/client/packages/lowcoder/src/components/assistant-ui/tool-fallback.tsx
new file mode 100644
index 0000000000..8c99664f5c
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/tool-fallback.tsx
@@ -0,0 +1,422 @@
+"use client";
+
+import { memo, useCallback, useRef, useState } from "react";
+import {
+ AlertCircleIcon,
+ CheckIcon,
+ LoaderIcon,
+ XCircleIcon,
+} from "lucide-react";
+import {
+ useScrollLock,
+ type ToolCallMessagePartStatus,
+ type ToolCallMessagePartComponent,
+} from "@assistant-ui/react";
+import styled, { keyframes } from "styled-components";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "./ui/collapsible";
+
+const ANIMATION_DURATION = 200;
+
+const spin = keyframes`
+ to {
+ transform: rotate(360deg);
+ }
+`;
+
+const shimmer = keyframes`
+ 0% {
+ background-position: 200% 0;
+ }
+
+ 100% {
+ background-position: -200% 0;
+ }
+`;
+
+const StyledToolFallbackRoot = styled(Collapsible)<{ $cancelled?: boolean }>`
+ background: ${({ $cancelled }) => ($cancelled ? "#f9fafb" : "#ffffff")};
+ border: 1px solid ${({ $cancelled }) => ($cancelled ? "#d1d5db" : "#e5e7eb")};
+ border-radius: 8px;
+ padding: 12px 0;
+ width: 100%;
+`;
+
+const StyledToolFallbackTrigger = styled(CollapsibleTrigger)`
+ align-items: center;
+ background: transparent;
+ border: 0;
+ color: #4b5563;
+ cursor: pointer;
+ display: flex;
+ font-size: 14px;
+ gap: 8px;
+ line-height: 20px;
+ padding: 0 16px;
+ text-align: left;
+ transition: color 0.2s ease;
+ width: 100%;
+
+ &:hover {
+ color: #111827;
+ }
+
+ .aui-tool-fallback-trigger-icon,
+ .aui-tool-fallback-trigger-chevron {
+ flex: 0 0 auto;
+ height: 16px;
+ width: 16px;
+ }
+
+ .aui-tool-fallback-trigger-icon-running {
+ animation: ${spin} 1s linear infinite;
+ }
+
+ .aui-tool-fallback-trigger-icon-cancelled,
+ .aui-tool-fallback-trigger-label-cancelled {
+ color: #8c8c8c;
+ }
+
+ .aui-tool-fallback-trigger-label-cancelled {
+ text-decoration: line-through;
+ }
+
+ .aui-tool-fallback-trigger-label-wrapper {
+ display: inline-block;
+ flex: 1;
+ line-height: 1;
+ min-width: 0;
+ position: relative;
+ text-align: start;
+ }
+
+ .aui-tool-fallback-trigger-shimmer {
+ background: linear-gradient(90deg, transparent, rgba(22, 119, 255, 0.35), transparent);
+ background-size: 200% 100%;
+ color: transparent;
+ inset: 0;
+ pointer-events: none;
+ position: absolute;
+ -webkit-background-clip: text;
+ animation: ${shimmer} 1.6s linear infinite;
+ }
+
+ .aui-tool-fallback-trigger-chevron {
+ transform: rotate(0deg);
+ transition: transform var(--animation-duration, 200ms) ease-out;
+ }
+
+ &[data-state="closed"] .aui-tool-fallback-trigger-chevron {
+ transform: rotate(-90deg);
+ }
+`;
+
+const StyledToolFallbackContent = styled(CollapsibleContent)`
+ color: #1f2937;
+ font-size: 14px;
+ outline: none;
+ overflow: hidden;
+ position: relative;
+`;
+
+const ToolFallbackContentInner = styled.div`
+ border-top: 1px solid #e5e7eb;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-top: 12px;
+ padding-top: 8px;
+`;
+
+const ToolFallbackSection = styled.div`
+ padding: 0 16px;
+`;
+
+const ToolFallbackDashedSection = styled(ToolFallbackSection)`
+ border-top: 1px dashed #e5e7eb;
+ padding-top: 8px;
+`;
+
+const ToolFallbackHeader = styled.p`
+ color: #4b5563;
+ font-weight: 600;
+ margin: 0 0 4px;
+`;
+
+const ToolFallbackText = styled.p`
+ color: #6b7280;
+ margin: 0;
+`;
+
+const ToolFallbackPre = styled.pre`
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-word;
+`;
+
+export type ToolFallbackRootProps = Omit<
+ React.ComponentPropsWithoutRef,
+ "open" | "onOpenChange"
+> & {
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ defaultOpen?: boolean;
+ cancelled?: boolean;
+};
+
+function ToolFallbackRoot({
+ className,
+ open: controlledOpen,
+ onOpenChange: controlledOnOpenChange,
+ defaultOpen = false,
+ cancelled = false,
+ children,
+ ...props
+}: ToolFallbackRootProps) {
+ const collapsibleRef = useRef(null);
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
+ const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);
+
+ const isControlled = controlledOpen !== undefined;
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
+
+ const handleOpenChange = useCallback(
+ (open: boolean) => {
+ if (!open) {
+ lockScroll();
+ }
+ if (!isControlled) {
+ setUncontrolledOpen(open);
+ }
+ controlledOnOpenChange?.(open);
+ },
+ [lockScroll, isControlled, controlledOnOpenChange],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+type ToolStatus = ToolCallMessagePartStatus["type"];
+
+const statusIconMap: Record = {
+ running: LoaderIcon,
+ complete: CheckIcon,
+ incomplete: XCircleIcon,
+ "requires-action": AlertCircleIcon,
+};
+
+function ToolFallbackTrigger({
+ toolName,
+ status,
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef & {
+ toolName: string;
+ status?: ToolCallMessagePartStatus;
+}) {
+ const statusType = status?.type ?? "complete";
+ const isRunning = statusType === "running";
+ const isCancelled =
+ status?.type === "incomplete" && status.reason === "cancelled";
+
+ const Icon = statusIconMap[statusType];
+ const label = isCancelled ? "Cancelled tool" : "Used tool";
+
+ return (
+
+
+
+
+ {label}: {toolName}
+
+ {isRunning && (
+
+ {label}: {toolName}
+
+ )}
+
+
+ );
+}
+
+function ToolFallbackContent({
+ className,
+ children,
+ ...props
+}: React.ComponentPropsWithoutRef) {
+ return (
+
+ {children}
+
+ );
+}
+
+function ToolFallbackArgs({
+ argsText,
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef<"div"> & {
+ argsText?: string;
+}) {
+ if (!argsText) return null;
+
+ return (
+
+
+ {argsText}
+
+
+ );
+}
+
+function ToolFallbackResult({
+ result,
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef<"div"> & {
+ result?: unknown;
+}) {
+ if (result === undefined) return null;
+
+ return (
+
+ Result:
+
+ {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
+
+
+ );
+}
+
+function ToolFallbackError({
+ status,
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef<"div"> & {
+ status?: ToolCallMessagePartStatus;
+}) {
+ if (status?.type !== "incomplete") return null;
+
+ const error = status.error;
+ const errorText = error
+ ? typeof error === "string"
+ ? error
+ : JSON.stringify(error)
+ : null;
+
+ if (!errorText) return null;
+
+ const isCancelled = status.reason === "cancelled";
+ const headerText = isCancelled ? "Cancelled reason:" : "Error:";
+
+ return (
+
+
+ {headerText}
+
+
+ {errorText}
+
+
+ );
+}
+
+const ToolFallbackImpl: ToolCallMessagePartComponent = ({
+ toolName,
+ status,
+}) => {
+ const isCancelled =
+ status?.type === "incomplete" && status.reason === "cancelled";
+
+ return (
+
+
+
+ );
+};
+
+const ToolFallback = memo(
+ ToolFallbackImpl,
+) as unknown as ToolCallMessagePartComponent & {
+ Root: typeof ToolFallbackRoot;
+ Trigger: typeof ToolFallbackTrigger;
+ Content: typeof ToolFallbackContent;
+ Args: typeof ToolFallbackArgs;
+ Result: typeof ToolFallbackResult;
+ Error: typeof ToolFallbackError;
+};
+
+ToolFallback.displayName = "ToolFallback";
+ToolFallback.Root = ToolFallbackRoot;
+ToolFallback.Trigger = ToolFallbackTrigger;
+ToolFallback.Content = ToolFallbackContent;
+ToolFallback.Args = ToolFallbackArgs;
+ToolFallback.Result = ToolFallbackResult;
+ToolFallback.Error = ToolFallbackError;
+
+export {
+ ToolFallback,
+ ToolFallbackRoot,
+ ToolFallbackTrigger,
+ ToolFallbackContent,
+ ToolFallbackArgs,
+ ToolFallbackResult,
+ ToolFallbackError,
+};
diff --git a/client/packages/lowcoder/src/components/assistant-ui/tool-group.tsx b/client/packages/lowcoder/src/components/assistant-ui/tool-group.tsx
new file mode 100644
index 0000000000..99c4f8b4f1
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/tool-group.tsx
@@ -0,0 +1,288 @@
+"use client";
+
+import {
+ memo,
+ useCallback,
+ useRef,
+ useState,
+ type FC,
+ type PropsWithChildren,
+} from "react";
+import { ChevronDownIcon, LoaderIcon } from "lucide-react";
+import { useScrollLock } from "@assistant-ui/react";
+import styled, { keyframes } from "styled-components";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "./ui/collapsible";
+
+const ANIMATION_DURATION = 200;
+
+type ToolGroupVariant = "outline" | "ghost" | "muted";
+
+const spin = keyframes`
+ to {
+ transform: rotate(360deg);
+ }
+`;
+
+const shimmer = keyframes`
+ 0% {
+ background-position: 200% 0;
+ }
+
+ 100% {
+ background-position: -200% 0;
+ }
+`;
+
+const StyledToolGroupRoot = styled(Collapsible)<{ $variant: ToolGroupVariant }>`
+ background: ${({ $variant }) => ($variant === "muted" ? "#f3f4f6" : "transparent")};
+ border: ${({ $variant }) => ($variant === "ghost" ? "0" : "1px solid #e5e7eb")};
+ border-radius: ${({ $variant }) => ($variant === "ghost" ? "0" : "8px")};
+ padding: ${({ $variant }) => ($variant === "ghost" ? "0" : "12px 0")};
+ width: 100%;
+`;
+
+const StyledToolGroupTrigger = styled(CollapsibleTrigger)`
+ align-items: center;
+ background: transparent;
+ border: 0;
+ color: #4b5563;
+ cursor: pointer;
+ display: flex;
+ font-size: 14px;
+ gap: 8px;
+ line-height: 20px;
+ padding: 0 16px;
+ text-align: left;
+ transition: color 0.2s ease;
+ width: 100%;
+
+ &:hover {
+ color: #111827;
+ }
+
+ .aui-tool-group-trigger-loader,
+ .aui-tool-group-trigger-chevron {
+ flex: 0 0 auto;
+ height: 16px;
+ width: 16px;
+ }
+
+ .aui-tool-group-trigger-loader {
+ animation: ${spin} 1s linear infinite;
+ }
+
+ .aui-tool-group-trigger-label-wrapper {
+ display: inline-block;
+ flex: 1;
+ font-weight: 500;
+ line-height: 1;
+ min-width: 0;
+ position: relative;
+ text-align: start;
+ }
+
+ .aui-tool-group-trigger-shimmer {
+ background: linear-gradient(90deg, transparent, rgba(22, 119, 255, 0.35), transparent);
+ background-size: 200% 100%;
+ color: transparent;
+ inset: 0;
+ pointer-events: none;
+ position: absolute;
+ -webkit-background-clip: text;
+ animation: ${shimmer} 1.6s linear infinite;
+ }
+
+ .aui-tool-group-trigger-chevron {
+ transform: rotate(0deg);
+ transition: transform var(--animation-duration, 200ms) ease-out;
+ }
+
+ &[data-state="closed"] .aui-tool-group-trigger-chevron {
+ transform: rotate(-90deg);
+ }
+`;
+
+const StyledToolGroupContent = styled(CollapsibleContent)`
+ color: #1f2937;
+ font-size: 14px;
+ outline: none;
+ overflow: hidden;
+ position: relative;
+`;
+
+const ToolGroupContentInner = styled.div`
+ border-top: 1px solid #e5e7eb;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-top: 12px;
+ padding: 12px 16px 0;
+`;
+
+export type ToolGroupRootProps = Omit<
+ React.ComponentPropsWithoutRef,
+ "open" | "onOpenChange"
+> &
+ {
+ variant?: ToolGroupVariant;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ defaultOpen?: boolean;
+ };
+
+function ToolGroupRoot({
+ className,
+ variant,
+ open: controlledOpen,
+ onOpenChange: controlledOnOpenChange,
+ defaultOpen = false,
+ children,
+ ...props
+}: ToolGroupRootProps) {
+ const collapsibleRef = useRef(null);
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
+ const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);
+
+ const isControlled = controlledOpen !== undefined;
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
+
+ const handleOpenChange = useCallback(
+ (open: boolean) => {
+ if (!open) {
+ lockScroll();
+ }
+ if (!isControlled) {
+ setUncontrolledOpen(open);
+ }
+ controlledOnOpenChange?.(open);
+ },
+ [lockScroll, isControlled, controlledOnOpenChange],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+function ToolGroupTrigger({
+ count,
+ active = false,
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef & {
+ count: number;
+ active?: boolean;
+}) {
+ const label = `${count} tool ${count === 1 ? "call" : "calls"}`;
+
+ return (
+
+ {active && (
+
+ )}
+
+ {label}
+ {active && (
+
+ {label}
+
+ )}
+
+
+
+ );
+}
+
+function ToolGroupContent({
+ className,
+ children,
+ ...props
+}: React.ComponentPropsWithoutRef) {
+ return (
+
+ {children}
+
+ );
+}
+
+type ToolGroupComponent = FC<
+ PropsWithChildren<{ startIndex: number; endIndex: number }>
+> & {
+ Root: typeof ToolGroupRoot;
+ Trigger: typeof ToolGroupTrigger;
+ Content: typeof ToolGroupContent;
+};
+
+const ToolGroupImpl: FC<
+ PropsWithChildren<{ startIndex: number; endIndex: number }>
+> = ({ children, startIndex, endIndex }) => {
+ const toolCount = endIndex - startIndex + 1;
+
+ return (
+
+
+ {children}
+
+ );
+};
+
+/**
+ * @deprecated This wrapper targets the legacy `components.ToolGroup` prop
+ * on ``. Use `` with
+ * a `groupBy` returning `"group-tool"` and compose `ToolGroupRoot` /
+ * `ToolGroupTrigger` / `ToolGroupContent` directly. See `thread.tsx`.
+ */
+const ToolGroup = memo(ToolGroupImpl) as unknown as ToolGroupComponent;
+
+ToolGroup.displayName = "ToolGroup";
+ToolGroup.Root = ToolGroupRoot;
+ToolGroup.Trigger = ToolGroupTrigger;
+ToolGroup.Content = ToolGroupContent;
+
+export {
+ ToolGroup,
+ ToolGroupRoot,
+ ToolGroupTrigger,
+ ToolGroupContent,
+};
diff --git a/client/packages/lowcoder/src/components/assistant-ui/tooltip-icon-button.tsx b/client/packages/lowcoder/src/components/assistant-ui/tooltip-icon-button.tsx
new file mode 100644
index 0000000000..b9f57b23b0
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/tooltip-icon-button.tsx
@@ -0,0 +1,46 @@
+import { forwardRef } from "react";
+import { Tooltip } from "antd";
+
+import { Button, type ButtonProps } from "./ui/button";
+import { cn } from "./utils/cn";
+
+export type TooltipIconButtonProps = ButtonProps & {
+ tooltip: string;
+ side?: "top" | "bottom" | "left" | "right";
+};
+
+export const TooltipIconButton = forwardRef<
+ HTMLButtonElement,
+ TooltipIconButtonProps
+>(
+ (
+ {
+ children,
+ tooltip,
+ side = "bottom",
+ className,
+ variant = "ghost",
+ size = "icon-sm",
+ "aria-label": ariaLabel,
+ ...rest
+ },
+ ref,
+ ) => {
+ return (
+
+
+ {children}
+
+
+ );
+ },
+);
+
+TooltipIconButton.displayName = "TooltipIconButton";
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/attachment.tsx b/client/packages/lowcoder/src/components/assistant-ui/ui/attachment.tsx
similarity index 89%
rename from client/packages/lowcoder/src/comps/comps/chatComp/components/ui/attachment.tsx
rename to client/packages/lowcoder/src/components/assistant-ui/ui/attachment.tsx
index ea823edca1..f0af0f0917 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/attachment.tsx
+++ b/client/packages/lowcoder/src/components/assistant-ui/ui/attachment.tsx
@@ -16,7 +16,7 @@ import {
TooltipTrigger,
} from "./tooltip";
import { Avatar, AvatarImage, AvatarFallback } from "./avatar";
-import { TooltipIconButton } from "../assistant-ui/tooltip-icon-button";
+import { TooltipIconButton } from "../tooltip-icon-button";
// ============================================================================
// STYLED COMPONENTS
@@ -45,16 +45,17 @@ const StyledAvatar = styled(Avatar)`
font-size: 14px;
`;
-const AttachmentContainer = styled.div`
+const AttachmentContainer = styled.div<{ $isImage?: boolean }>`
display: flex;
- height: 48px;
- width: 160px;
+ height: ${({ $isImage }) => ($isImage ? "56px" : "48px")};
+ width: ${({ $isImage }) => ($isImage ? "56px" : "160px")};
align-items: center;
justify-content: center;
gap: 8px;
- border-radius: 8px;
+ border-radius: ${({ $isImage }) => ($isImage ? "12px" : "8px")};
border: 1px solid #e2e8f0;
- padding: 4px;
+ overflow: hidden;
+ padding: ${({ $isImage }) => ($isImage ? "0" : "4px")};
`;
const AttachmentTextContainer = styled.div`
@@ -290,8 +291,7 @@ const AttachmentUI: FC = () => {
case "file":
return "File";
default:
- const _exhaustiveCheck: never = type;
- throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
+ return "File";
}
});
@@ -300,14 +300,16 @@ const AttachmentUI: FC = () => {
-
+
-
-
-
-
- {typeLabel}
-
+ {typeLabel !== "Image" && (
+
+
+
+
+ {typeLabel}
+
+ )}
@@ -340,7 +342,9 @@ const AttachmentRemove: FC = () => {
export const UserMessageAttachments: FC = () => {
return (
-
+
+ {() => }
+
);
};
@@ -348,9 +352,9 @@ export const UserMessageAttachments: FC = () => {
export const ComposerAttachments: FC = () => {
return (
-
+
+ {() => }
+
);
};
@@ -366,4 +370,4 @@ export const ComposerAddAttachment: FC = () => {
);
-};
\ No newline at end of file
+};
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/avatar.tsx b/client/packages/lowcoder/src/components/assistant-ui/ui/avatar.tsx
similarity index 100%
rename from client/packages/lowcoder/src/comps/comps/chatComp/components/ui/avatar.tsx
rename to client/packages/lowcoder/src/components/assistant-ui/ui/avatar.tsx
diff --git a/client/packages/lowcoder/src/components/assistant-ui/ui/button.tsx b/client/packages/lowcoder/src/components/assistant-ui/ui/button.tsx
new file mode 100644
index 0000000000..98472c763e
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/ui/button.tsx
@@ -0,0 +1,147 @@
+import * as React from "react";
+import { Button as AntButton } from "antd";
+import type { ButtonProps as AntButtonProps } from "antd";
+import styled from "styled-components";
+
+import { cn } from "../utils/cn";
+
+type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
+type ButtonSize = "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg";
+
+export type ButtonProps = Omit & {
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ asChild?: boolean;
+ type?: React.ButtonHTMLAttributes["type"];
+};
+
+const StyledButton = styled(AntButton)<{
+ $variant: ButtonVariant;
+ $size: ButtonSize;
+}>`
+ align-items: center;
+ border-radius: 8px;
+ display: inline-flex;
+ font-size: 14px;
+ font-weight: 500;
+ gap: 8px;
+ justify-content: center;
+ line-height: 20px;
+ min-width: 0;
+ white-space: nowrap;
+
+ ${({ $size }) => {
+ if ($size === "icon-xs") return "height: 24px; width: 24px; padding: 0;";
+ if ($size === "icon-sm") return "height: 32px; width: 32px; padding: 0;";
+ if ($size === "icon" || $size === "icon-lg") {
+ return `${$size === "icon-lg" ? "height: 40px; width: 40px;" : "height: 36px; width: 36px;"} padding: 0;`;
+ }
+ if ($size === "xs") return "height: 24px; padding: 0 8px; font-size: 12px;";
+ if ($size === "sm") return "height: 32px; padding: 0 12px;";
+ if ($size === "lg") return "height: 40px; padding: 0 20px;";
+ return "height: 36px; padding: 0 16px;";
+ }}
+
+ ${({ $variant }) => {
+ if ($variant === "default") {
+ return `
+ background: #1677ff;
+ border-color: #1677ff;
+ color: #ffffff;
+
+ &:hover:not(:disabled) {
+ background: #4096ff !important;
+ border-color: #4096ff !important;
+ color: #ffffff !important;
+ }
+ `;
+ }
+
+ if ($variant === "destructive") {
+ return `
+ background: #ff4d4f;
+ border-color: #ff4d4f;
+ color: #ffffff;
+
+ &:hover:not(:disabled) {
+ background: #ff7875 !important;
+ border-color: #ff7875 !important;
+ color: #ffffff !important;
+ }
+ `;
+ }
+
+ if ($variant === "ghost" || $variant === "link") {
+ return `
+ background: transparent;
+ border-color: transparent;
+ box-shadow: none;
+ color: #4b5563;
+
+ &:hover:not(:disabled) {
+ background: rgba(0, 0, 0, 0.04) !important;
+ border-color: transparent !important;
+ color: #111827 !important;
+ }
+ `;
+ }
+
+ return `
+ background: #ffffff;
+ border-color: #d9d9d9;
+ color: #1f2937;
+
+ &:hover:not(:disabled) {
+ background: #f9fafb !important;
+ border-color: #1677ff !important;
+ color: #1677ff !important;
+ }
+ `;
+ }}
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.45;
+ }
+
+ svg {
+ height: 16px;
+ width: 16px;
+ flex: 0 0 auto;
+ }
+`;
+
+const Button = React.forwardRef(
+ (
+ {
+ asChild: _asChild,
+ children,
+ className,
+ variant = "default",
+ size = "default",
+ type = "button",
+ ...props
+ },
+ ref,
+ ) => {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+Button.displayName = "Button";
+
+export { Button };
diff --git a/client/packages/lowcoder/src/components/assistant-ui/ui/collapsible.tsx b/client/packages/lowcoder/src/components/assistant-ui/ui/collapsible.tsx
new file mode 100644
index 0000000000..d1823d8352
--- /dev/null
+++ b/client/packages/lowcoder/src/components/assistant-ui/ui/collapsible.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import { forwardRef } from "react";
+import { Collapsible as CollapsiblePrimitive } from "radix-ui";
+
+const Collapsible = forwardRef<
+ HTMLDivElement,
+ React.ComponentPropsWithoutRef
+>((props, ref) => {
+ return ;
+});
+
+Collapsible.displayName = "Collapsible";
+
+const CollapsibleTrigger = forwardRef<
+ HTMLButtonElement,
+ React.ComponentPropsWithoutRef
+>((props, ref) => {
+ return (
+
+ );
+});
+
+CollapsibleTrigger.displayName = "CollapsibleTrigger";
+
+const CollapsibleContent = forwardRef<
+ HTMLDivElement,
+ React.ComponentPropsWithoutRef
+>((props, ref) => {
+ return (
+
+ );
+});
+
+CollapsibleContent.displayName = "CollapsibleContent";
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/tooltip.tsx b/client/packages/lowcoder/src/components/assistant-ui/ui/tooltip.tsx
similarity index 95%
rename from client/packages/lowcoder/src/comps/comps/chatComp/components/ui/tooltip.tsx
rename to client/packages/lowcoder/src/components/assistant-ui/ui/tooltip.tsx
index ede610e327..9d6350bda1 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/tooltip.tsx
+++ b/client/packages/lowcoder/src/components/assistant-ui/ui/tooltip.tsx
@@ -3,7 +3,7 @@
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
-import { cn } from "../../utils/cn";
+import { cn } from "../utils/cn";
const TooltipProvider = TooltipPrimitive.Provider;
@@ -26,4 +26,4 @@ const TooltipContent = React.forwardRef<
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
\ No newline at end of file
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/utils/cn.ts b/client/packages/lowcoder/src/components/assistant-ui/utils/cn.ts
similarity index 100%
rename from client/packages/lowcoder/src/comps/comps/chatComp/utils/cn.ts
rename to client/packages/lowcoder/src/components/assistant-ui/utils/cn.ts
diff --git a/client/packages/lowcoder/src/comps/comps/appSettingsComp.tsx b/client/packages/lowcoder/src/comps/comps/appSettingsComp.tsx
index 5b372740cb..b38260eedf 100644
--- a/client/packages/lowcoder/src/comps/comps/appSettingsComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/appSettingsComp.tsx
@@ -458,7 +458,7 @@ function AppCanvasSettingsModal(props: ChildrenInstance) {
}}
>
- {maxWidth.propertyView({
+ {!isAggregation && maxWidth.propertyView({
dropdownLabel: trans("appSetting.canvasMaxWidth"),
inputLabel: trans("appSetting.userDefinedMaxWidth"),
inputPlaceholder: trans("appSetting.inputUserDefinedPxValue"),
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx
index 39de2e7393..a32a3ea6b2 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/chatComp.tsx
@@ -14,9 +14,10 @@ import { ChatProvider } from "./components/context/ChatContext";
import { ChatPropertyView } from "./chatPropertyView";
import { createChatStorage } from "./utils/storageFactory";
import { QueryHandler } from "./handlers/messageHandlers";
-import { useMemo, useRef, useEffect } from "react";
+import { useMemo, useRef } from "react";
import { changeChildAction } from "lowcoder-core";
-import { ChatMessage } from "./types/chatTypes";
+import { ChatMessage } from "./types/chatTypes";
+import { getTextFromThreadContent } from "./utils/assistantMessages";
import { trans } from "i18n";
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { styleControl } from "comps/controls/styleControl";
@@ -31,8 +32,8 @@ import {
} from "comps/controls/styleControlConstants";
import { AnimationStyle } from "comps/controls/styleControlConstants";
-import "@assistant-ui/styles/index.css";
-import "@assistant-ui/styles/markdown.css";
+// Assistant UI layout is styled locally with AntD and styled-components.
+// Markdown-specific styles are imported by components/assistant-ui/markdown-text.tsx.
// ============================================================================
// CHAT-SPECIFIC EVENTS
@@ -95,12 +96,12 @@ export function addSystemPromptToHistory(
systemPrompt: string
): Array<{ role: string; content: string; timestamp: number; attachments?: any[] }> {
// Format conversation history for use in queries
- const formattedHistory = conversationHistory.map(msg => {
- const baseMessage = {
- role: msg.role,
- content: msg.text,
- timestamp: msg.timestamp
- };
+ const formattedHistory = conversationHistory.map(msg => {
+ const baseMessage = {
+ role: msg.role,
+ content: getTextFromThreadContent(msg.content),
+ timestamp: msg.createdAt.getTime()
+ };
// Include attachment metadata if present (for API calls and external integrations)
if (msg.attachments && msg.attachments.length > 0) {
@@ -249,16 +250,6 @@ const ChatTmpComp = new UICompBuilder(
}
};
- // Cleanup on unmount
- useEffect(() => {
- return () => {
- const tableName = uniqueTableName.current;
- if (tableName) {
- storage.cleanup();
- }
- };
- }, []);
-
// custom styles
const styles = {
style: props.style,
@@ -308,4 +299,4 @@ export const ChatComp = withExposingConfigs(ChatCompWithAutoHeight, [
// conversationHistory is now a proper array (not JSON string) - supports setConversationHistory(), clearConversationHistory(), resetConversationHistory()
new NameConfig("conversationHistory", "Full conversation history array with system prompt (use directly in API calls, no JSON.parse needed)"),
new NameConfig("databaseName", "Database name for SQL queries (ChatDB_)"),
-]);
\ No newline at end of file
+]);
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatContainer.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatContainer.tsx
index 689e0dc289..510818aca5 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatContainer.tsx
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatContainer.tsx
@@ -4,31 +4,38 @@ import React, { useState, useEffect, useRef } from "react";
import {
useExternalStoreRuntime,
ThreadMessageLike,
- AppendMessage,
AssistantRuntimeProvider,
- ExternalStoreThreadListAdapter,
+} from "@assistant-ui/react";
+import type {
+ AppendMessage,
CompleteAttachment,
- TextContentPart,
- ThreadUserContentPart
+ ExternalStoreThreadData,
+ ExternalStoreThreadListAdapter,
} from "@assistant-ui/react";
-import { Thread } from "./assistant-ui/thread";
-import { ThreadList } from "./assistant-ui/thread-list";
+import { Thread } from "components/assistant-ui/thread";
+import { ThreadList } from "components/assistant-ui/thread-list";
import {
useChatContext,
- RegularThreadData,
- ArchivedThreadData
+ RegularThreadData,
} from "./context/ChatContext";
import { MessageHandler, ChatMessage, ChatCoreProps } from "../types/chatTypes";
import { trans } from "i18n";
import { universalAttachmentAdapter } from "../utils/attachmentAdapter";
+import {
+ createAssistantErrorMessage,
+ createUserMessage,
+ generateThreadTitle,
+ getTextFromAppendMessage,
+ getTextFromThreadContent,
+ shouldGenerateThreadTitle,
+ toChatMessage,
+} from "../utils/assistantMessages";
import { StyledChatContainer } from "./ChatContainerStyles";
// ============================================================================
// CHAT CONTAINER
// ============================================================================
-const generateId = () => Math.random().toString(36).substr(2, 9);
-
function ChatContainerView(props: ChatCoreProps) {
const { state, actions } = useChatContext();
const [isRunning, setIsRunning] = useState(false);
@@ -52,32 +59,33 @@ function ChatContainerView(props: ChatCoreProps) {
onEventRef.current?.("componentLoad");
}, []);
- const convertMessage = (message: ChatMessage): ThreadMessageLike => {
- const content: ThreadUserContentPart[] = [{ type: "text", text: message.text }];
-
- if (message.attachments && message.attachments.length > 0) {
- for (const attachment of message.attachments) {
- if (attachment.content) {
- content.push(...attachment.content);
- }
- }
+ const convertMessage = (message: ChatMessage): ThreadMessageLike => message;
+
+ const updateInitialThreadTitle = async (userMessage: ChatMessage) => {
+ const currentThread = state.threadList.find(
+ (thread) => thread.threadId === state.currentThreadId
+ );
+ const defaultTitle = trans("chat.newChatTitle");
+
+ if (
+ !shouldGenerateThreadTitle(
+ currentThread?.title,
+ defaultTitle,
+ currentMessages.length
+ )
+ ) {
+ return;
}
-
- return {
- role: message.role,
- content,
- id: message.id,
- createdAt: new Date(message.timestamp),
- ...(message.attachments && message.attachments.length > 0 && { attachments: message.attachments }),
- };
+
+ const title = generateThreadTitle(userMessage);
+ if (!title || title === currentThread?.title) return;
+
+ await actions.updateThread(state.currentThreadId, { title });
+ props.onEvent?.("threadUpdated");
};
const onNew = async (message: AppendMessage) => {
- const textPart = (message.content as ThreadUserContentPart[]).find(
- (part): part is TextContentPart => part.type === "text"
- );
-
- const text = textPart?.text?.trim() ?? "";
+ const text = getTextFromAppendMessage(message);
const completeAttachments = (message.attachments ?? []).filter(
(att): att is CompleteAttachment => att.status.type === "complete"
);
@@ -86,47 +94,29 @@ function ChatContainerView(props: ChatCoreProps) {
throw new Error("Cannot send an empty message");
}
- const userMessage: ChatMessage = {
- id: generateId(),
- role: "user",
- text,
- timestamp: Date.now(),
- attachments: completeAttachments,
- };
+ const userMessage = createUserMessage(text, completeAttachments);
await actions.addMessage(state.currentThreadId, userMessage);
+ await updateInitialThreadTitle(userMessage);
setIsRunning(true);
try {
- const response = await props.messageHandler.sendMessage(userMessage);
- props.onMessageUpdate?.(userMessage.text);
-
- const assistantMessage: ChatMessage = {
- id: generateId(),
- role: "assistant",
- text: response.content,
- timestamp: Date.now(),
- };
+ const assistantMessage = await props.messageHandler.sendMessage(userMessage);
+ props.onMessageUpdate?.(getTextFromThreadContent(userMessage.content));
await actions.addMessage(state.currentThreadId, assistantMessage);
} catch (error) {
- await actions.addMessage(state.currentThreadId, {
- id: generateId(),
- role: "assistant",
- text: trans("chat.errorUnknown"),
- timestamp: Date.now(),
- });
+ await actions.addMessage(
+ state.currentThreadId,
+ createAssistantErrorMessage(trans("chat.errorUnknown"))
+ );
} finally {
setIsRunning(false);
}
};
const onEdit = async (message: AppendMessage) => {
- const textPart = (message.content as ThreadUserContentPart[]).find(
- (part): part is TextContentPart => part.type === "text"
- );
-
- const text = textPart?.text?.trim() ?? "";
+ const text = getTextFromAppendMessage(message);
const completeAttachments = (message.attachments ?? []).filter(
(att): att is CompleteAttachment => att.status.type === "complete"
);
@@ -138,48 +128,39 @@ function ChatContainerView(props: ChatCoreProps) {
const index = currentMessages.findIndex((m) => m.id === message.parentId) + 1;
const newMessages = [...currentMessages.slice(0, index)];
- const editedMessage: ChatMessage = {
- id: generateId(),
- role: "user",
- text,
- timestamp: Date.now(),
- attachments: completeAttachments,
- };
+ const editedMessage = createUserMessage(text, completeAttachments);
newMessages.push(editedMessage);
await actions.updateMessages(state.currentThreadId, newMessages);
setIsRunning(true);
try {
- const response = await props.messageHandler.sendMessage(editedMessage);
- props.onMessageUpdate?.(editedMessage.text);
-
- const assistantMessage: ChatMessage = {
- id: generateId(),
- role: "assistant",
- text: response.content,
- timestamp: Date.now(),
- };
+ const assistantMessage = await props.messageHandler.sendMessage(editedMessage);
+ props.onMessageUpdate?.(getTextFromThreadContent(editedMessage.content));
newMessages.push(assistantMessage);
await actions.updateMessages(state.currentThreadId, newMessages);
} catch (error) {
- newMessages.push({
- id: generateId(),
- role: "assistant",
- text: trans("chat.errorUnknown"),
- timestamp: Date.now(),
- });
+ newMessages.push(createAssistantErrorMessage(trans("chat.errorUnknown")));
await actions.updateMessages(state.currentThreadId, newMessages);
} finally {
setIsRunning(false);
}
};
+ const toExternalThreadData = (
+ thread: RegularThreadData,
+ ): ExternalStoreThreadData<"regular"> => ({
+ id: thread.threadId,
+ status: "regular",
+ title: thread.title,
+ });
+
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"),
+ threads: state.threadList
+ .filter((t): t is RegularThreadData => t.status === "regular")
+ .map(toExternalThreadData),
onSwitchToNewThread: async () => {
const threadId = await actions.createThread(trans("chat.newChatTitle"));
@@ -196,11 +177,6 @@ function ChatContainerView(props: ChatCoreProps) {
props.onEvent?.("threadUpdated");
},
- onArchive: async (threadId) => {
- await actions.updateThread(threadId, { status: "archived" });
- props.onEvent?.("threadUpdated");
- },
-
onDelete: async (threadId) => {
await actions.deleteThread(threadId);
props.onEvent?.("threadDeleted");
@@ -209,7 +185,11 @@ function ChatContainerView(props: ChatCoreProps) {
const runtime = useExternalStoreRuntime({
messages: currentMessages,
- setMessages: (messages) => actions.updateMessages(state.currentThreadId, messages),
+ setMessages: (messages) =>
+ actions.updateMessages(
+ state.currentThreadId,
+ messages.map(toChatMessage)
+ ),
convertMessage,
isRunning,
onNew,
@@ -239,7 +219,7 @@ function ChatContainerView(props: ChatCoreProps) {
$animationStyle={props.animationStyle}
>
-
+
);
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatContainerStyles.ts b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatContainerStyles.ts
index 1f2d4580db..37275f232e 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatContainerStyles.ts
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatContainerStyles.ts
@@ -18,8 +18,11 @@ export interface StyledChatContainerProps {
export const StyledChatContainer = styled.div`
display: flex;
+ align-items: stretch;
height: ${(props) => (props.$autoHeight ? "auto" : "100%")};
min-height: ${(props) => (props.$autoHeight ? "300px" : "unset")};
+ min-width: 0;
+ overflow: hidden;
/* Main container styles */
background: ${(props) => props.style?.background || "transparent"};
@@ -40,9 +43,12 @@ export const StyledChatContainer = styled.div`
/* Sidebar Styles */
.aui-thread-list-root {
+ align-self: stretch;
width: ${(props) => props.$sidebarWidth || "250px"};
background-color: ${(props) => props.$sidebarStyle?.sidebarBackground || "#fff"};
padding: 10px;
+ min-height: 0;
+ overflow-y: auto;
}
.aui-thread-list-item-title {
@@ -51,15 +57,36 @@ export const StyledChatContainer = styled.div`
/* Messages Window Styles */
.aui-thread-root {
- flex: 1;
+ flex: 1 1 auto;
+ align-self: stretch;
+ min-width: 0;
+ min-height: 0;
background-color: ${(props) => props.$messagesStyle?.messagesBackground || "#f9fafb"};
- height: auto;
+ height: ${(props) => (props.$autoHeight ? "auto" : "100%")};
+ overflow: hidden;
+ }
+
+ .aui-thread-viewport {
+ flex: 1 1 auto;
+ min-height: 0;
}
/* User Message Styles */
.aui-user-message-content {
- background-color: ${(props) => props.$messagesStyle?.userMessageBackground || "#3b82f6"};
- color: ${(props) => props.$messagesStyle?.userMessageText || "#ffffff"};
+ background-color: ${(props) => props.$messagesStyle?.userMessageBackground || "#e5e7eb"};
+ color: ${(props) => props.$messagesStyle?.userMessageText || "#111827"};
+ }
+
+ .aui-user-message-content:empty {
+ display: none;
+ }
+
+ .aui-user-message-content img {
+ display: block;
+ height: auto;
+ max-height: 320px;
+ max-width: 100%;
+ object-fit: contain;
}
/* Assistant Message Styles */
@@ -69,11 +96,9 @@ export const StyledChatContainer = styled.div`
}
/* Input Field Styles */
- form.aui-composer-root {
- background-color: ${(props) => props.$inputStyle?.inputBackground || "#ffffff"};
- color: ${(props) => props.$inputStyle?.inputText || "inherit"};
- border-color: ${(props) => props.$inputStyle?.inputBorder || "#d1d5db"};
- }
+
+
+
/* Send Button Styles */
.aui-composer-send {
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanel.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanel.tsx
index f4823011e6..e7e70f8b17 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanel.tsx
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanel.tsx
@@ -1,46 +1,46 @@
// client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanel.tsx
-import { useMemo, useEffect } from "react";
+import { useMemo, useContext, useRef, useEffect } from "react";
import { ChatPanelContainer } from "./ChatPanelContainer";
import { createChatStorage } from "../utils/storageFactory";
-import { N8NHandler } from "../handlers/messageHandlers";
+import { AIAssistantQueryHandler } from "../handlers/messageHandlers";
import { ChatPanelProps } from "../types/chatTypes";
-import { trans } from "i18n";
+import { EditorContext } from "@lowcoder-ee/comps/editorState";
-import "@assistant-ui/styles/index.css";
-import "@assistant-ui/styles/markdown.css";
-
-// ============================================================================
-// CHAT PANEL - SIMPLIFIED BOTTOM PANEL (NO STYLING CONTROLS)
+// ============================================================================
+// CHAT PANEL - SIMPLIFIED BOTTOM PANEL (QUERY-BASED + AUTOMATOR)
+// ----------------------------------------------------------------------------
+// We capture the EditorState in a ref so the message handler always reads
+// the *latest* canvas snapshot at send-time (instead of being frozen at
+// mount time, which would defeat the whole point of context awareness).
// ============================================================================
-export function ChatPanel({
- tableName,
- modelHost,
- systemPrompt = trans("chat.defaultSystemPrompt"),
- streaming = true,
- onMessageUpdate
-}: ChatPanelProps) {
+export function ChatPanel({
+ tableName,
+ chatQuery,
+ onMessageUpdate,
+}: ChatPanelProps) {
+ const editorState = useContext(EditorContext);
+ const editorStateRef = useRef(editorState);
+
+ useEffect(() => {
+ editorStateRef.current = editorState;
+ }, [editorState]);
+
const storage = useMemo(() =>
createChatStorage(tableName),
[tableName]
);
- const messageHandler = useMemo(() =>
- new N8NHandler({
- modelHost,
- systemPrompt,
- streaming
- }),
- [modelHost, systemPrompt, streaming]
- );
-
- // Cleanup on unmount - delete chat data from storage
- useEffect(() => {
- return () => {
- storage.cleanup();
- };
- }, [storage]);
+ const messageHandler = useMemo(
+ () =>
+ new AIAssistantQueryHandler({
+ chatQuery,
+ dispatch: editorState?.rootComp?.dispatch,
+ getEditorState: () => editorStateRef.current,
+ }),
+ [chatQuery, editorState?.rootComp?.dispatch]
+ );
return (
);
-}
\ No newline at end of file
+}
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanelContainer.tsx b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanelContainer.tsx
index 9f0766cea4..3c13aaace8 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanelContainer.tsx
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/components/ChatPanelContainer.tsx
@@ -4,31 +4,117 @@ import React, { useState, useEffect, useRef, useContext } from "react";
import {
useExternalStoreRuntime,
ThreadMessageLike,
- AppendMessage,
AssistantRuntimeProvider,
+} from "@assistant-ui/react";
+import type {
+ AppendMessage,
+ ExternalStoreThreadData,
ExternalStoreThreadListAdapter,
- TextContentPart,
- ThreadUserContentPart
} from "@assistant-ui/react";
-import { Thread } from "./assistant-ui/thread";
-import { ThreadList } from "./assistant-ui/thread-list";
+import { Thread } from "components/assistant-ui/thread";
+import { ThreadList } from "components/assistant-ui/thread-list";
import {
ChatProvider,
useChatContext,
- RegularThreadData,
- ArchivedThreadData
+ RegularThreadData,
} from "./context/ChatContext";
-import { MessageHandler, ChatMessage } from "../types/chatTypes";
+import { AIAssistantMessageHandler, ChatMessage } from "../types/chatTypes";
import styled from "styled-components";
import { trans } from "i18n";
import { TooltipProvider } from "@radix-ui/react-tooltip";
+import {
+ createAssistantErrorMessage,
+ createUserMessage,
+ generateThreadTitle,
+ getAutomatorActionsFromMessage,
+ getTextFromAppendMessage,
+ getTextFromThreadContent,
+ shouldGenerateThreadTitle,
+ toChatMessage,
+} from "../utils/assistantMessages";
-import "@assistant-ui/styles/index.css";
-import "@assistant-ui/styles/markdown.css";
import { EditorContext } from "@lowcoder-ee/comps/editorState";
+import { ActionConfig, ActionExecuteParams } from "../../preLoadComp/types";
import { configureComponentAction } from "../../preLoadComp/actions/componentConfiguration";
-import { addComponentAction, moveComponentAction, nestComponentAction, resizeComponentAction } from "../../preLoadComp/actions/componentManagement";
-import { applyThemeAction, configureAppMetaAction, setCanvasSettingsAction } from "../../preLoadComp/actions/appConfiguration";
+import {
+ addComponentAction,
+ moveComponentAction,
+ nestComponentAction,
+ resizeComponentAction,
+ deleteComponentAction,
+ renameComponentAction,
+} from "../../preLoadComp/actions/componentManagement";
+import {
+ applyThemeAction,
+ configureAppMetaAction,
+ setCanvasSettingsAction,
+ applyGlobalJSAction,
+ applyCSSAction,
+ publishAppAction,
+} from "../../preLoadComp/actions/appConfiguration";
+import { applyStyleAction } from "../../preLoadComp/actions/componentStyling";
+import { addEventHandlerAction } from "../../preLoadComp/actions/componentEvents";
+import { alignComponentAction } from "../../preLoadComp/actions/componentLayout";
+import { deleteQueryAction } from "../../preLoadComp/actions/queryManagement";
+
+// ============================================================================
+// ACTION REGISTRY — maps LLM action names to their executor configs.
+// Adding a new action is one line here + one entry in actionsCatalog.ts.
+// ============================================================================
+
+const ACTION_REGISTRY: Record = {
+ place_component: addComponentAction,
+ nest_component: nestComponentAction,
+ move_component: moveComponentAction,
+ resize_component: resizeComponentAction,
+ delete_component: deleteComponentAction,
+ delete_query: deleteQueryAction,
+ rename_component: renameComponentAction,
+ set_properties: configureComponentAction,
+ set_style: applyStyleAction,
+ set_theme: applyThemeAction,
+ set_app_metadata: configureAppMetaAction,
+ set_canvas_setting: setCanvasSettingsAction,
+ set_global_javascript: applyGlobalJSAction,
+ set_global_css: applyCSSAction,
+ publish_app: publishAppAction,
+ add_event_handler: addEventHandlerAction,
+ align_component: alignComponentAction,
+};
+
+/**
+ * Translate an LLM action object into the ActionExecuteParams shape that
+ * the legacy executor functions expect. Centralises the field-mapping so
+ * each executor doesn't need to know about the automator format.
+ */
+function buildExecuteParams(
+ actionItem: Record,
+ editorState: any
+): ActionExecuteParams {
+ const ap = actionItem.action_parameters || {};
+
+ let actionValue = "";
+ switch (actionItem.action) {
+ case "rename_component": actionValue = ap.new_name || ""; break;
+ case "align_component": actionValue = ap.alignment || "center"; break;
+ case "add_event_handler": actionValue = `${ap.event || "click"}: ${ap.action_type || "message"}`; break;
+ case "set_global_javascript": actionValue = ap.code || ""; break;
+ case "set_global_css": actionValue = ap.code || ""; break;
+ }
+
+ return {
+ actionKey: actionItem.action,
+ actionValue,
+ actionPayload: actionItem,
+ selectedComponent: actionItem.component || null,
+ selectedEditorComponent: actionItem.component_name || null,
+ selectedNestComponent: null,
+ editorState,
+ selectedDynamicLayoutIndex: null,
+ selectedTheme: null,
+ selectedCustomShortcutAction: null,
+ };
+}
// ============================================================================
// STYLED CONTAINER - SIMPLE FIXED STYLING FOR BOTTOM PANEL
@@ -41,6 +127,8 @@ const StyledChatContainer = styled.div<{
display: flex;
height: ${(props) => (props.autoHeight ? "auto" : "100%")};
min-height: ${(props) => (props.autoHeight ? "300px" : "unset")};
+ min-width: 0;
+ overflow: hidden;
p {
margin: 0;
@@ -50,12 +138,21 @@ const StyledChatContainer = styled.div<{
width: ${(props) => props.sidebarWidth || "250px"};
background-color: #fff;
padding: 10px;
+ min-height: 0;
+ overflow-y: auto;
}
.aui-thread-root {
- flex: 1;
+ flex: 1 1 auto;
+ min-width: 0;
+ min-height: 0;
background-color: #f9fafb;
- height: auto;
+ height: 100%;
+ overflow: hidden;
+ }
+
+ .aui-thread-viewport {
+ min-height: 0;
}
.aui-thread-list-item {
@@ -73,11 +170,9 @@ const StyledChatContainer = styled.div<{
// CHAT PANEL CONTAINER - DIRECT RENDERING
// ============================================================================
-const generateId = () => Math.random().toString(36).substr(2, 9);
-
export interface ChatPanelContainerProps {
storage: any;
- messageHandler: MessageHandler;
+ messageHandler: AIAssistantMessageHandler;
placeholder?: string;
onMessageUpdate?: (message: string) => void;
}
@@ -98,205 +193,100 @@ function ChatPanelView({ messageHandler, placeholder, onMessageUpdate }: Omit {
if (!editorStateRef.current) {
- console.error("No editorStateRef found");
+ console.error("[Automator] no editorState — skipping actions");
return;
}
-
- const comp = editorStateRef.current.getUIComp().children.comp;
- if (!comp) {
- console.error("No comp found");
- return;
- }
- // const layout = comp.children.layout.getView();
- // console.log("LAYOUT", layout);
-
+
+ console.log(`[Automator] executing ${actions.length} action(s)`);
+ let executed = 0;
+
for (const actionItem of actions) {
- const { action, component, ...action_payload } = actionItem;
-
- switch (action) {
- case "place_component":
- await addComponentAction.execute({
- actionKey: action,
- actionValue: "",
- actionPayload: action_payload,
- selectedComponent: component,
- selectedEditorComponent: null,
- selectedNestComponent: null,
- editorState: editorStateRef.current,
- selectedDynamicLayoutIndex: null,
- selectedTheme: null,
- selectedCustomShortcutAction: null
- });
- break;
- case "nest_component":
- await nestComponentAction.execute({
- actionKey: action,
- actionValue: "",
- actionPayload: action_payload,
- selectedComponent: component,
- selectedEditorComponent: null,
- selectedNestComponent: null,
- editorState: editorStateRef.current,
- selectedDynamicLayoutIndex: null,
- selectedTheme: null,
- selectedCustomShortcutAction: null
- });
- break;
- case "move_component":
- await moveComponentAction.execute({
- actionKey: action,
- actionValue: "",
- actionPayload: action_payload,
- selectedComponent: component,
- selectedEditorComponent: null,
- selectedNestComponent: null,
- editorState: editorStateRef.current,
- selectedDynamicLayoutIndex: null,
- selectedTheme: null,
- selectedCustomShortcutAction: null
- });
- break;
- case "resize_component":
- await resizeComponentAction.execute({
- actionKey: action,
- actionValue: "",
- actionPayload: action_payload,
- selectedComponent: component,
- selectedEditorComponent: null,
- selectedNestComponent: null,
- editorState: editorStateRef.current,
- selectedDynamicLayoutIndex: null,
- selectedTheme: null,
- selectedCustomShortcutAction: null
- });
- break;
- case "set_properties":
- await configureComponentAction.execute({
- actionKey: action,
- actionValue: component,
- actionPayload: action_payload,
- selectedEditorComponent: null,
- selectedComponent: null,
- selectedNestComponent: null,
- editorState: editorStateRef.current,
- selectedDynamicLayoutIndex: null,
- selectedTheme: null,
- selectedCustomShortcutAction: null
- });
- break;
- case "set_theme":
- await applyThemeAction.execute({
- actionKey: action,
- actionValue: component,
- actionPayload: action_payload,
- selectedEditorComponent: null,
- selectedComponent: null,
- selectedNestComponent: null,
- editorState: editorStateRef.current,
- selectedDynamicLayoutIndex: null,
- selectedTheme: null,
- selectedCustomShortcutAction: null
- });
- break;
- case "set_app_metadata":
- await configureAppMetaAction.execute({
- actionKey: action,
- actionValue: component,
- actionPayload: action_payload,
- selectedEditorComponent: null,
- selectedComponent: null,
- selectedNestComponent: null,
- editorState: editorStateRef.current,
- selectedDynamicLayoutIndex: null,
- selectedTheme: null,
- selectedCustomShortcutAction: null
- });
- break;
- case "set_canvas_setting":
- await setCanvasSettingsAction.execute({
- actionKey: action,
- actionValue: component,
- actionPayload: action_payload,
- selectedEditorComponent: null,
- selectedComponent: null,
- selectedNestComponent: null,
- editorState: editorStateRef.current,
- selectedDynamicLayoutIndex: null,
- selectedTheme: null,
- selectedCustomShortcutAction: null
- });
- break;
- default:
- break;
+ const executor = ACTION_REGISTRY[actionItem.action];
+ if (!executor) {
+ console.warn(`[Automator] unsupported action: ${actionItem.action}`);
+ continue;
+ }
+ try {
+ const params = buildExecuteParams(actionItem, editorStateRef.current);
+ await executor.execute(params);
+ executed++;
+ } catch (err) {
+ console.error(`[Automator] action "${actionItem.action}" failed:`, err);
}
- await new Promise(resolve => setTimeout(resolve, 1000));
+ await new Promise((r) => setTimeout(r, 500));
}
+
+ console.log(`[Automator] done: ${executed}/${actions.length} succeeded`);
};
- const convertMessage = (message: ChatMessage): ThreadMessageLike => {
- const content: ThreadUserContentPart[] = [{ type: "text", text: message.text }];
-
- return {
- role: message.role,
- content,
- id: message.id,
- createdAt: new Date(message.timestamp),
- };
+ const convertMessage = (message: ChatMessage): ThreadMessageLike => message;
+
+ const updateInitialThreadTitle = async (userMessage: ChatMessage) => {
+ const currentThread = state.threadList.find(
+ (thread) => thread.threadId === state.currentThreadId
+ );
+ const defaultTitle = trans("chat.newChatTitle");
+
+ if (
+ !shouldGenerateThreadTitle(
+ currentThread?.title,
+ defaultTitle,
+ currentMessages.length
+ )
+ ) {
+ return;
+ }
+
+ const title = generateThreadTitle(userMessage);
+ if (!title || title === currentThread?.title) return;
+
+ await actions.updateThread(state.currentThreadId, { title });
};
const onNew = async (message: AppendMessage) => {
- const textPart = (message.content as ThreadUserContentPart[]).find(
- (part): part is TextContentPart => part.type === "text"
- );
-
- const text = textPart?.text?.trim() ?? "";
+ const text = getTextFromAppendMessage(message);
if (!text) {
throw new Error("Cannot send an empty message");
}
- const userMessage: ChatMessage = {
- id: generateId(),
- role: "user",
- text,
- timestamp: Date.now(),
- };
+ const userMessage = createUserMessage(text);
+
+ const conversationHistory = [...currentMessages, userMessage];
await actions.addMessage(state.currentThreadId, userMessage);
+ await updateInitialThreadTitle(userMessage);
setIsRunning(true);
try {
- const response = await messageHandler.sendMessage(userMessage, state.currentThreadId);
- onMessageUpdate?.(userMessage.text);
-
- if (response?.actions?.length) {
- performAction(response.actions);
+ const assistantMessage = await messageHandler.sendMessage(
+ userMessage,
+ state.currentThreadId,
+ conversationHistory
+ );
+ onMessageUpdate?.(getTextFromThreadContent(userMessage.content));
+
+ const automatorActions = getAutomatorActionsFromMessage(assistantMessage);
+ if (automatorActions.length) {
+ await performAction(automatorActions);
}
- await actions.addMessage(state.currentThreadId, {
- id: generateId(),
- role: "assistant",
- text: response.content,
- timestamp: Date.now(),
- });
+ await actions.addMessage(
+ state.currentThreadId,
+ assistantMessage
+ );
} catch (error) {
- await actions.addMessage(state.currentThreadId, {
- id: generateId(),
- role: "assistant",
- text: trans("chat.errorUnknown"),
- timestamp: Date.now(),
- });
+ await actions.addMessage(
+ state.currentThreadId,
+ createAssistantErrorMessage(trans("chat.errorUnknown"))
+ );
} finally {
setIsRunning(false);
}
};
const onEdit = async (message: AppendMessage) => {
- const textPart = (message.content as ThreadUserContentPart[]).find(
- (part): part is TextContentPart => part.type === "text"
- );
-
- const text = textPart?.text?.trim() ?? "";
+ const text = getTextFromAppendMessage(message);
if (!text) {
throw new Error("Cannot send an empty message");
@@ -305,44 +295,47 @@ function ChatPanelView({ messageHandler, placeholder, onMessageUpdate }: Omit m.id === message.parentId) + 1;
const newMessages = [...currentMessages.slice(0, index)];
- newMessages.push({
- id: generateId(),
- role: "user",
- text,
- timestamp: Date.now(),
- });
+ newMessages.push(createUserMessage(text));
await actions.updateMessages(state.currentThreadId, newMessages);
setIsRunning(true);
try {
- const response = await messageHandler.sendMessage(newMessages[newMessages.length - 1]);
+ const assistantMessage = await messageHandler.sendMessage(
+ newMessages[newMessages.length - 1],
+ state.currentThreadId,
+ newMessages
+ );
onMessageUpdate?.(text);
-
- newMessages.push({
- id: generateId(),
- role: "assistant",
- text: response.content,
- timestamp: Date.now(),
- });
+
+ const automatorActions = getAutomatorActionsFromMessage(assistantMessage);
+ if (automatorActions.length) {
+ await performAction(automatorActions);
+ }
+
+ newMessages.push(assistantMessage);
await actions.updateMessages(state.currentThreadId, newMessages);
} catch (error) {
- newMessages.push({
- id: generateId(),
- role: "assistant",
- text: trans("chat.errorUnknown"),
- timestamp: Date.now(),
- });
+ newMessages.push(createAssistantErrorMessage(trans("chat.errorUnknown")));
await actions.updateMessages(state.currentThreadId, newMessages);
} finally {
setIsRunning(false);
}
};
+ const toExternalThreadData = (
+ thread: RegularThreadData,
+ ): ExternalStoreThreadData<"regular"> => ({
+ id: thread.threadId,
+ status: "regular",
+ title: thread.title,
+ });
+
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"),
+ threads: state.threadList
+ .filter((t): t is RegularThreadData => t.status === "regular")
+ .map(toExternalThreadData),
onSwitchToNewThread: async () => {
const threadId = await actions.createThread(trans("chat.newChatTitle"));
@@ -357,10 +350,6 @@ function ChatPanelView({ messageHandler, placeholder, onMessageUpdate }: Omit {
- await actions.updateThread(threadId, { status: "archived" });
- },
-
onDelete: async (threadId) => {
await actions.deleteThread(threadId);
},
@@ -368,7 +357,11 @@ function ChatPanelView({ messageHandler, placeholder, onMessageUpdate }: Omit actions.updateMessages(state.currentThreadId, messages),
+ setMessages: (messages) =>
+ actions.updateMessages(
+ state.currentThreadId,
+ messages.map(toChatMessage)
+ ),
convertMessage,
isRunning,
onNew,
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
deleted file mode 100644
index 46bf98eed4..0000000000
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread-list.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-import type { FC } from "react";
-import { useState } from "react";
-import {
- ThreadListItemPrimitive,
- ThreadListPrimitive,
- useThreadListItem,
-} from "@assistant-ui/react";
-import { PencilIcon, PlusIcon, Trash2Icon } from "lucide-react";
-import { TooltipIconButton } from "./tooltip-icon-button";
-import { useThreadListItemRuntime } from "@assistant-ui/react";
-import { Button, Flex, Input } from "antd";
-import { trans } from "i18n";
-
-import styled from "styled-components";
-
-const StyledPrimaryButton = styled(Button)`
- // padding: 20px;
- // margin-bottom: 20px;
-`;
-
-
-export const ThreadList: FC = () => {
- return (
-
-
-
-
-
-
- );
-};
-
-const ThreadListNew: FC = () => {
- return (
-
- }>
- {trans("chat.newThread")}
-
-
- );
-};
-
-const ThreadListItems: FC = () => {
- return ;
-};
-
-const ThreadListItem: FC = () => {
- const [editing, setEditing] = useState(false);
-
- return (
-
-
- {editing ? (
- setEditing(false)}
- />
- ) : (
-
- )}
-
- setEditing(true)}
- editing={editing}
- />
-
-
- );
-};
-
-const ThreadListItemTitle: FC = () => {
- return (
-
-
-
- );
-};
-
-const ThreadListItemDelete: FC = () => {
- return (
-
-
-
-
-
- );
-};
-
-
-
-const ThreadListItemEditInput: FC<{ onFinish: () => void }> = ({ onFinish }) => {
- const threadItem = useThreadListItem();
- const threadRuntime = useThreadListItemRuntime();
-
- const currentTitle = threadItem?.title || trans("chat.newChatTitle");
-
- const handleRename = async (newTitle: string) => {
- if (!newTitle.trim() || newTitle === currentTitle){
- onFinish();
- return;
- }
-
- try {
- await threadRuntime.rename(newTitle);
- onFinish();
- } catch (error) {
- console.error("Failed to rename thread:", error);
- }
- };
-
- return (
- handleRename(e.target.value)}
- onPressEnter={(e) => handleRename((e.target as HTMLInputElement).value)}
- onKeyDown={(e) => {
- if (e.key === 'Escape') onFinish();
- }}
- autoFocus
- style={{ fontSize: '14px', padding: '2px 8px' }}
- />
- );
-};
-
-
-const ThreadListItemRename: FC<{ onStartEdit: () => void; editing: boolean }> = ({
- onStartEdit,
- editing
-}) => {
- if (editing) return null;
-
- return (
-
-
-
- );
-};
-
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
deleted file mode 100644
index a45e5fe147..0000000000
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/thread.tsx
+++ /dev/null
@@ -1,336 +0,0 @@
-import {
- ActionBarPrimitive,
- BranchPickerPrimitive,
- ComposerPrimitive,
- MessagePrimitive,
- ThreadPrimitive,
- } from "@assistant-ui/react";
- import { useMemo, type FC } from "react";
- import { trans } from "i18n";
- import {
- ArrowDownIcon,
- CheckIcon,
- ChevronLeftIcon,
- ChevronRightIcon,
- CopyIcon,
- PencilIcon,
- 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";
- import { Spin, Flex } from "antd";
- import { LoadingOutlined } from "@ant-design/icons";
- import styled from "styled-components";
-import { ComposerAddAttachment, ComposerAttachments, UserMessageAttachments } from "../ui/attachment";
- const SimpleANTDLoader = () => {
- const antIcon = ;
-
- return (
-
-
-
- Working on it...
-
-
- );
- };
-
- const StyledThreadRoot = styled(ThreadPrimitive.Root)`
- /* Hide entire assistant message container when it contains running status */
- .aui-assistant-message-root:has([data-status="running"]) {
- display: none;
- }
-
- /* Fallback for older browsers that don't support :has() */
- .aui-assistant-message-content [data-status="running"] {
- display: none;
- }
-`;
-
-
- interface ThreadProps {
- placeholder?: string;
- showAttachments?: boolean;
- }
-
- export const Thread: FC = ({
- placeholder = trans("chat.composerPlaceholder"),
- showAttachments = true
- }) => {
- // Stable component reference so React doesn't unmount/remount on every render
- const UserMessageComponent = useMemo(() => {
- const Wrapper: FC = () => ;
- Wrapper.displayName = "UserMessage";
- return Wrapper;
- }, [showAttachments]);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- };
-
- const ThreadScrollToBottom: FC = () => {
- return (
-
-
-
-
-
- );
- };
-
- const ThreadWelcome: FC = () => {
- return (
-
-
-
-
- {trans("chat.welcomeMessage")}
-
-
-
-
-
- );
- };
-
- const ThreadWelcomeSuggestions: FC = () => {
- return (
-
-
-
- {trans("chat.suggestionWeather")}
-
-
-
-
- {trans("chat.suggestionAssistant")}
-
-
-
- );
- };
-
- const Composer: FC<{ placeholder?: string; showAttachments?: boolean }> = ({
- placeholder = trans("chat.composerPlaceholder"),
- showAttachments = true
- }) => {
- return (
-
- {showAttachments && (
- <>
-
-
- >
- )}
-
-
-
- );
- };
-
- const ComposerAction: FC = () => {
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
- };
-
- const UserMessage: FC<{ showAttachments?: boolean }> = ({ showAttachments = true }) => {
- return (
-
-
- {showAttachments && }
-
-
-
-
-
-
-
- );
- };
-
- const UserActionBar: FC = () => {
- return (
-
-
-
-
-
-
-
- );
- };
-
- const EditComposer: FC = () => {
- return (
-
-
-
-
-
- Cancel
-
-
- Send
-
-
-
- );
- };
-
- 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
deleted file mode 100644
index d2434babff..0000000000
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/assistant-ui/tooltip-icon-button.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-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 (
-
-
-
-
- {children}
- {tooltip}
-
-
- {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
deleted file mode 100644
index 945783c696..0000000000
--- a/client/packages/lowcoder/src/comps/comps/chatComp/components/ui/button.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-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",
- },
-});
-
-const Button = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<"button"> &
- VariantProps & {
- asChild?: boolean;
- }
->(({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button";
-
- return (
-
- );
-});
-
-Button.displayName = "Button";
-
-export { Button, buttonVariants };
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/handlers/messageHandlers.ts b/client/packages/lowcoder/src/comps/comps/chatComp/handlers/messageHandlers.ts
index 3ea69fafd9..424666669a 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/handlers/messageHandlers.ts
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/handlers/messageHandlers.ts
@@ -1,88 +1,65 @@
// client/packages/lowcoder/src/comps/comps/chatComp/handlers/messageHandlers.ts
-import { MessageHandler, MessageResponse, N8NHandlerConfig, QueryHandlerConfig, ChatMessage } from "../types/chatTypes";
-import { CompAction, routeByNameAction, executeQueryAction } from "lowcoder-core";
-import { getPromiseAfterDispatch } from "util/promiseUtils";
+import { AIAssistantMessageHandler, MessageHandler, QueryHandlerConfig, ChatMessage } from "../types/chatTypes";
+import { routeByNameAction, executeQueryAction } from "lowcoder-core";
+import { getPromiseAfterDispatch } from "util/promiseUtils";
+import { buildAutomatorPayload } from "../../preLoadComp/actions/automator";
+import {
+ getTextFromThreadContent,
+ toAssistantMessage,
+} from "../utils/assistantMessages";
+
+function buildAutomatorQueryArgs(
+ payload: ReturnType
+) {
+ const ai = {
+ mode: "automator" as const,
+ messages: payload.messages,
+ tools: payload.tools,
+ };
+
+ return {
+ ai: {
+ value: ai,
+ },
+ };
+}
// ============================================================================
-// N8N HANDLER (for Bottom Panel)
-// ============================================================================
-
-export class N8NHandler implements MessageHandler {
- constructor(private config: N8NHandlerConfig) {}
-
- async sendMessage(message: ChatMessage, sessionId?: string): Promise {
- const { modelHost, systemPrompt, streaming } = this.config;
-
- if (!modelHost) {
- throw new Error("Model host is required for N8N calls");
- }
-
- try {
- const response = await fetch(modelHost, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- sessionId,
- message: message.text,
- systemPrompt: systemPrompt || "You are a helpful assistant.",
- streaming: streaming || false
- })
- });
-
- if (!response.ok) {
- throw new Error(`N8N call failed: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- if (data.output) {
- const { explanation, actions } = JSON.parse(data.output);
- return { content: explanation, actions };
- }
- // Extract content from various possible response formats
- const content = data.response || data.message || data.content || data.text || String(data);
-
- return { content };
- } catch (error) {
- throw new Error(`N8N call failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
- }
- }
-}
-
-// ============================================================================
-// QUERY HANDLER (for Canvas Components)
+// QUERY HANDLER
// ============================================================================
export class QueryHandler implements MessageHandler {
constructor(private config: QueryHandlerConfig) {}
- async sendMessage(message: ChatMessage, sessionId?: string): Promise {
- const { chatQuery, dispatch} = this.config;
-
- // If no query selected or dispatch unavailable, return mock response
- if (!chatQuery || !dispatch) {
- await new Promise((res) => setTimeout(res, 500));
- return { content: "(mock) You typed: " + message.text };
- }
+ async sendMessage(message: ChatMessage): Promise {
+ const { chatQuery, dispatch} = this.config;
+
+ if (!chatQuery) {
+ throw new Error("Select a query before sending a message");
+ }
+
+ if (!dispatch) {
+ throw new Error("Query dispatch is unavailable");
+ }
try {
+ console.log("Executing query:", chatQuery);
const result: any = await getPromiseAfterDispatch(
dispatch,
routeByNameAction(
chatQuery,
executeQueryAction({
- // Pass the full message object so attachments are available in queries
- args: {
- message: { value: message }, // Full ChatMessage object with attachments
- prompt: { value: message.text }, // Keep backward compatibility
- },
+ // Pass the full message object so attachments are available in queries
+ args: {
+ message: { value: message },
+ prompt: { value: getTextFromThreadContent(message.content) },
+ },
})
)
);
-
- return result.message
+ console.log("Query result:", result);
+ return toAssistantMessage(result);
} catch (e: any) {
throw new Error(e?.message || "Query execution failed");
}
@@ -90,37 +67,89 @@ export class QueryHandler implements MessageHandler {
}
// ============================================================================
-// MOCK HANDLER (for testing/fallbacks)
-// ============================================================================
-
-export class MockHandler implements MessageHandler {
- constructor(private delay: number = 1000) {}
+// AI ASSISTANT QUERY HANDLER (bottom panel)
+// ----------------------------------------------------------------------------
+// This handler owns the Lowcoder side of the Automator flow:
+// 1. snapshot the current editor state,
+// 2. build the system prompt, tools, catalogs, and live context,
+// 3. pass that payload to the selected user query,
+// 4. accept an Assistant UI `ThreadMessageLike` assistant message.
+//
+// Provider-specific parsing belongs in the selected query/backend bridge.
+// ============================================================================
+
+export class AIAssistantQueryHandler implements AIAssistantMessageHandler {
+ constructor(private config: QueryHandlerConfig) {}
+
+ async sendMessage(
+ _message: ChatMessage,
+ _sessionId: string | undefined,
+ conversationHistory: ChatMessage[]
+ ): Promise {
+ const { chatQuery, dispatch, getEditorState } = this.config;
+ const history = conversationHistory;
+
+ // Conversation history in the OpenAI {role, content} shape.
+ const rawHistory = history.map((msg) => ({
+ role: msg.role,
+ content: getTextFromThreadContent(msg.content),
+ }));
+
+ if (!chatQuery) {
+ throw new Error("Select an Automator query before sending a message");
+ }
+
+ if (!dispatch) {
+ throw new Error("Automator dispatch is unavailable");
+ }
+
+ if (!getEditorState) {
+ throw new Error("Automator editor state is unavailable");
+ }
+
+ const editorState = getEditorState();
+ const payload = buildAutomatorPayload({
+ history: rawHistory,
+ editorState,
+ });
+
+ try {
+ console.log("[Automator] running query:", chatQuery, {
+ contextComponents: payload.context.components.length,
+ contextQueries: payload.context.queries.length,
+ messageCount: payload.messages.length,
+ });
- async sendMessage(message: ChatMessage): Promise {
- await new Promise(resolve => setTimeout(resolve, this.delay));
- return { content: `Mock response: ${message.text}` };
+ const result: any = await getPromiseAfterDispatch(
+ dispatch,
+ routeByNameAction(
+ chatQuery,
+ executeQueryAction({
+ args: buildAutomatorQueryArgs(payload),
+ })
+ )
+ );
+
+ return toAssistantMessage(result);
+ } catch (e: any) {
+ throw new Error(e?.message || "AI assistant query execution failed");
+ }
}
}
-// ============================================================================
-// HANDLER FACTORY (creates the right handler based on type)
-// ============================================================================
-
-export function createMessageHandler(
- type: "n8n" | "query" | "mock",
- config: N8NHandlerConfig | QueryHandlerConfig
-): MessageHandler {
- switch (type) {
- case "n8n":
- return new N8NHandler(config as N8NHandlerConfig);
-
- case "query":
- return new QueryHandler(config as QueryHandlerConfig);
-
- case "mock":
- return new MockHandler();
-
- default:
- throw new Error(`Unknown message handler type: ${type}`);
- }
-}
\ No newline at end of file
+// ============================================================================
+// HANDLER FACTORY (creates the right handler based on type)
+// ============================================================================
+
+export function createMessageHandler(
+ type: "query",
+ config: QueryHandlerConfig
+): MessageHandler {
+ switch (type) {
+ case "query":
+ return new QueryHandler(config);
+
+ default:
+ throw new Error(`Unknown message handler type: ${type}`);
+ }
+}
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/types/chatTypes.ts b/client/packages/lowcoder/src/comps/comps/chatComp/types/chatTypes.ts
index d24e0ce84f..0a8035a30c 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/types/chatTypes.ts
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/types/chatTypes.ts
@@ -1,12 +1,17 @@
-import { CompleteAttachment } from "@assistant-ui/react";
-
-export interface ChatMessage {
- id: string;
- role: "user" | "assistant";
- text: string;
- timestamp: number;
- attachments?: CompleteAttachment[];
- }
+import type { ThreadMessageLike } from "@assistant-ui/react";
+
+export type ChatMessageContent = Exclude;
+
+export type ChatMessage = Omit<
+ ThreadMessageLike,
+ "id" | "role" | "content" | "createdAt" | "attachments"
+> & {
+ id: string;
+ role: "user" | "assistant";
+ content: ChatMessageContent;
+ createdAt: Date;
+ attachments?: ThreadMessageLike["attachments"];
+ };
export interface ChatThread {
threadId: string;
@@ -39,31 +44,28 @@ export interface ChatMessage {
// MESSAGE HANDLER INTERFACE (new clean abstraction)
// ============================================================================
- export interface MessageHandler {
- sendMessage(message: ChatMessage, sessionId?: string): Promise;
- // Future: sendMessageStream?(message: ChatMessage): AsyncGenerator;
- }
-
- export interface MessageResponse {
- content: string;
- metadata?: any;
- actions?: any[];
- }
+ export interface MessageHandler {
+ sendMessage(message: ChatMessage, sessionId?: string): Promise;
+ // Future: sendMessageStream?(message: ChatMessage): AsyncGenerator;
+ }
+
+ export interface AIAssistantMessageHandler {
+ sendMessage(message: ChatMessage, sessionId: string | undefined, conversationHistory: ChatMessage[]): Promise;
+ }
// ============================================================================
// CONFIGURATION TYPES (simplified)
// ============================================================================
- export interface N8NHandlerConfig {
- modelHost: string;
- systemPrompt?: string;
- streaming?: boolean;
- }
-
export interface QueryHandlerConfig {
chatQuery: string;
dispatch: any;
- }
+ /**
+ * Snapshot accessor for the live editor state. The handler calls this
+ * lazily on every send so it always has the *current* canvas state.
+ */
+ getEditorState?: () => any;
+ }
// ============================================================================
// COMPONENT PROPS (what each component actually needs)
@@ -93,8 +95,6 @@ export interface ChatCoreProps {
// Bottom Panel Props (simplified, no styling controls)
export interface ChatPanelProps {
tableName: string;
- modelHost: string;
- systemPrompt?: string;
- streaming?: boolean;
+ chatQuery: string;
onMessageUpdate?: (message: string) => void;
}
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/utils/assistantMessages.ts b/client/packages/lowcoder/src/comps/comps/chatComp/utils/assistantMessages.ts
new file mode 100644
index 0000000000..7c9d5e86a3
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/utils/assistantMessages.ts
@@ -0,0 +1,118 @@
+import type {
+ AppendMessage,
+ CompleteAttachment,
+ TextMessagePart,
+ ThreadAssistantMessagePart,
+ ThreadMessageLike,
+ ThreadUserMessagePart,
+} from "@assistant-ui/react";
+
+import type { ChatMessage, ChatMessageContent } from "../types/chatTypes";
+
+export const generateMessageId = () => Math.random().toString(36).substr(2, 9);
+
+export const getTextFromThreadContent = (
+ content: ThreadMessageLike["content"]
+) => {
+ if (typeof content === "string") return content;
+
+ return content
+ .filter((part) => part.type === "text")
+ .map((part) => part.text)
+ .join("\n")
+ .trim();
+};
+
+export const generateThreadTitle = (message: ChatMessage) => {
+ const text = getTextFromThreadContent(message.content)
+ .replace(/\s+/g, " ")
+ .trim();
+
+ if (!text) return "";
+ if (text.length <= 50) return text;
+
+ const clipped = text.slice(0, 50).replace(/\s+\S*$/, "").trim();
+ return `${clipped || text.slice(0, 50).trim()}...`;
+};
+
+export const shouldGenerateThreadTitle = (
+ existingTitle: string | undefined,
+ defaultTitle: string,
+ existingMessageCount: number
+) => {
+ return (
+ existingMessageCount === 0 &&
+ (!existingTitle || existingTitle.trim() === defaultTitle.trim())
+ );
+};
+
+export const getTextFromAppendMessage = (message: AppendMessage) => {
+ const textPart = message.content.find(
+ (part): part is TextMessagePart => part.type === "text"
+ );
+ return textPart?.text?.trim() ?? "";
+};
+
+export const createUserMessage = (
+ text: string,
+ attachments: CompleteAttachment[] = []
+): ChatMessage => {
+ const content: ThreadUserMessagePart[] = text
+ ? [{ type: "text", text }]
+ : [];
+
+ return {
+ id: generateMessageId(),
+ role: "user",
+ content,
+ createdAt: new Date(),
+ ...(attachments.length && { attachments }),
+ };
+};
+
+export const createAssistantErrorMessage = (text: string): ChatMessage => ({
+ id: generateMessageId(),
+ role: "assistant",
+ content: [{ type: "text", text }],
+ createdAt: new Date(),
+});
+
+export const toChatMessage = (message: ThreadMessageLike): ChatMessage => {
+ if (message.role === "system") {
+ throw new Error("System messages are not stored in chat threads");
+ }
+
+ const content =
+ typeof message.content === "string"
+ ? ([{ type: "text", text: message.content }] as ChatMessageContent)
+ : (message.content as ChatMessageContent);
+
+ return {
+ ...message,
+ id: message.id ?? generateMessageId(),
+ role: message.role,
+ content,
+ createdAt: message.createdAt ?? new Date(),
+ };
+};
+
+export const toAssistantMessage = (message: ThreadMessageLike): ChatMessage => {
+ const chatMessage = toChatMessage(message);
+ if (chatMessage.role !== "assistant") {
+ throw new Error("Query must return an assistant message");
+ }
+ return chatMessage;
+};
+
+export const getAutomatorActionsFromMessage = (message: ChatMessage) => {
+ const toolPart = message.content.find(
+ (part): part is Extract =>
+ part.type === "tool-call" &&
+ part.toolName === "execute_automator_actions"
+ );
+
+ if (!toolPart) return [];
+
+ const actions = (toolPart.args as any)?.actions;
+ return Array.isArray(actions) ? actions : [];
+};
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/utils/attachmentAdapter.ts b/client/packages/lowcoder/src/comps/comps/chatComp/utils/attachmentAdapter.ts
index 9ff22d4364..3e4c23e1c8 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/utils/attachmentAdapter.ts
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/utils/attachmentAdapter.ts
@@ -3,14 +3,14 @@ import type {
PendingAttachment,
CompleteAttachment,
Attachment,
- ThreadUserContentPart
+ ThreadUserMessagePart
} from "@assistant-ui/react";
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
export const universalAttachmentAdapter: AttachmentAdapter = {
- accept: "*/*",
+ accept: "*",
async add({ file }): Promise {
if (file.size > MAX_FILE_SIZE) {
@@ -38,7 +38,7 @@ import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"
async send(attachment: PendingAttachment): Promise {
const isImage = attachment.contentType?.startsWith("image/");
- let content: ThreadUserContentPart[];
+ let content: ThreadUserMessagePart[];
try {
content = isImage
@@ -93,4 +93,4 @@ import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"
function getAttachmentType(mime: string): "image" | "file" {
return mime.startsWith("image/") ? "image" : "file";
}
-
\ No newline at end of file
+
diff --git a/client/packages/lowcoder/src/comps/comps/chatComp/utils/storageFactory.ts b/client/packages/lowcoder/src/comps/comps/chatComp/utils/storageFactory.ts
index c641dbbefc..ae2a033586 100644
--- a/client/packages/lowcoder/src/comps/comps/chatComp/utils/storageFactory.ts
+++ b/client/packages/lowcoder/src/comps/comps/chatComp/utils/storageFactory.ts
@@ -1,7 +1,8 @@
// client/packages/lowcoder/src/comps/comps/chatComp/utils/storageFactory.ts
-import alasql from "alasql";
-import { ChatMessage, ChatThread, ChatStorage } from "../types/chatTypes";
+import alasql from "alasql";
+import { ChatMessage, ChatThread, ChatStorage } from "../types/chatTypes";
+import { getTextFromThreadContent } from "./assistantMessages";
// ============================================================================
// CLEAN STORAGE FACTORY (simplified from your existing implementation)
@@ -32,15 +33,22 @@ export function createChatStorage(tableName: string): ChatStorage {
// Create messages table
await alasql.promise(`
- CREATE TABLE IF NOT EXISTS ${messagesTable} (
- id STRING PRIMARY KEY,
- threadId STRING,
- role STRING,
- text STRING,
- timestamp NUMBER,
- attachments STRING
- )
- `);
+ CREATE TABLE IF NOT EXISTS ${messagesTable} (
+ id STRING PRIMARY KEY,
+ threadId STRING,
+ role STRING,
+ text STRING,
+ timestamp NUMBER,
+ attachments STRING,
+ content STRING
+ )
+ `);
+
+ try {
+ await alasql.promise(`ALTER TABLE ${messagesTable} ADD COLUMN content STRING`);
+ } catch (error) {
+ // Existing databases may already have the AUI content column.
+ }
} catch (error) {
console.error(`Failed to initialize chat database ${dbName}:`, error);
@@ -104,9 +112,18 @@ export function createChatStorage(tableName: string): ChatStorage {
// Insert or replace message
await alasql.promise(`DELETE FROM ${messagesTable} WHERE id = ?`, [message.id]);
- await alasql.promise(`
- INSERT INTO ${messagesTable} VALUES (?, ?, ?, ?, ?, ?)
- `, [message.id, threadId, message.role, message.text, message.timestamp, JSON.stringify(message.attachments || [])]);
+ await alasql.promise(`
+ INSERT INTO ${messagesTable} (id, threadId, role, text, timestamp, attachments, content)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ `, [
+ message.id,
+ threadId,
+ message.role,
+ getTextFromThreadContent(message.content),
+ message.createdAt.getTime(),
+ JSON.stringify(message.attachments || []),
+ JSON.stringify(message.content),
+ ]);
} catch (error) {
console.error("Failed to save message:", error);
throw error;
@@ -120,9 +137,18 @@ export function createChatStorage(tableName: string): ChatStorage {
// Insert all messages
for (const message of messages) {
- await alasql.promise(`
- INSERT INTO ${messagesTable} VALUES (?, ?, ?, ?, ?, ?)
- `, [message.id, threadId, message.role, message.text, message.timestamp, JSON.stringify(message.attachments || [])]);
+ await alasql.promise(`
+ INSERT INTO ${messagesTable} (id, threadId, role, text, timestamp, attachments, content)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ `, [
+ message.id,
+ threadId,
+ message.role,
+ getTextFromThreadContent(message.content),
+ message.createdAt.getTime(),
+ JSON.stringify(message.attachments || []),
+ JSON.stringify(message.content),
+ ]);
}
} catch (error) {
console.error("Failed to save messages:", error);
@@ -132,18 +158,18 @@ export function createChatStorage(tableName: string): ChatStorage {
async getMessages(threadId: string) {
try {
- const result = await alasql.promise(`
- SELECT id, role, text, timestamp, attachments FROM ${messagesTable}
- WHERE threadId = ? ORDER BY timestamp ASC
- `, [threadId]) as any[];
-
- return result.map(row => ({
- id: row.id,
- role: row.role,
- text: row.text,
- timestamp: row.timestamp,
- attachments: JSON.parse(row.attachments || '[]')
- })) as ChatMessage[];
+ const result = await alasql.promise(`
+ SELECT id, role, text, timestamp, attachments, content FROM ${messagesTable}
+ WHERE threadId = ? ORDER BY timestamp ASC
+ `, [threadId]) as any[];
+
+ return result.map(row => ({
+ id: row.id,
+ role: row.role,
+ content: JSON.parse(row.content || "null") || [{ type: "text", text: row.text || "" }],
+ createdAt: new Date(row.timestamp),
+ attachments: JSON.parse(row.attachments || '[]')
+ })) as ChatMessage[];
} catch (error) {
console.error("Failed to get messages:", error);
return [];
@@ -190,4 +216,4 @@ export function createChatStorage(tableName: string): ChatStorage {
}
}
};
-}
\ No newline at end of file
+}
diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonEditorComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonEditorComp.tsx
index 4738291683..8b1d4c8400 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonEditorComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonEditorComp.tsx
@@ -184,7 +184,17 @@ let JsonEditorTmpComp = (function () {
return (
<>
- {children.value.propertyView({ label: trans("export.jsonEditorDesc") })}
+ {children.value.propertyView({
+ label: trans("export.jsonEditorDesc"),
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "JSON Editor value",
+ fieldName: "value",
+ fieldDescription:
+ "JSON value edited by the JSON Editor component. Generate valid JSON that can be an object, array, string, number, boolean, or null.",
+ },
+ })}
diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonExplorerComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonExplorerComp.tsx
index d1c6b917cb..f929dd68ce 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonExplorerComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonExplorerComp.tsx
@@ -83,7 +83,17 @@ let JsonExplorerTmpComp = (function () {
return (
<>
- {children.value.propertyView({ label: trans("export.jsonEditorDesc") })}
+ {children.value.propertyView({
+ label: trans("export.jsonEditorDesc"),
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "JSON Explorer value",
+ fieldName: "value",
+ fieldDescription:
+ "JSON object or array displayed by the JSON Explorer component. Generate valid JSON that is useful to inspect as a nested data structure.",
+ },
+ })}
{(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && (
diff --git a/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/jsonSchemaFormComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/jsonSchemaFormComp.tsx
index 0705a745b6..d5c956b85c 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/jsonSchemaFormComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/jsonSchemaFormComp.tsx
@@ -374,6 +374,10 @@ let FormBasicComp = (function () {
})
.setPropertyViewFn((children) => {
const formType = children.formType.getView();
+ const uiSchemaFieldDescription =
+ formType === "rjsf"
+ ? "RJSF UI schema object for configuring widgets, placeholders, field order, submit button options, and other react-jsonschema-form UI behavior. It should match the JSON Schema fields."
+ : "JSONForms UI schema object for configuring controls, scopes, layouts, categories, rules, and renderer options. It should match the JSON Schema fields.";
return (
<>
{(useContext(EditorContext).editorModeStatus === "logic" ||
@@ -431,6 +435,14 @@ let FormBasicComp = (function () {
>
),
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "JSON Schema Form schema",
+ fieldName: "schema",
+ fieldDescription:
+ "JSON Schema object that defines the form fields, field types, required fields, validation constraints, enum options, titles, and descriptions.",
+ },
})}
{children.uiSchema.propertyView({
key: trans("jsonSchemaForm.uiSchema"),
@@ -471,10 +483,25 @@ let FormBasicComp = (function () {
>
),
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: `${formType === "rjsf" ? "RJSF" : "JSONForms"} UI schema`,
+ fieldName: "uiSchema",
+ fieldDescription: uiSchemaFieldDescription,
+ },
})}
{children.data.propertyView({
key: trans("jsonSchemaForm.defaultData"),
label: trans("jsonSchemaForm.defaultData"),
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "JSON Schema Form default data",
+ fieldName: "data",
+ fieldDescription:
+ "Default form data object. Generate sample initial values that match the current JSON Schema shape, field names, and value types.",
+ },
})}
{children.formType.getView() === "rjsf" && (
children.errorSchema.propertyView({
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/actionsCatalog.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/actionsCatalog.ts
new file mode 100644
index 0000000000..4c813a4a03
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/actionsCatalog.ts
@@ -0,0 +1,235 @@
+// client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/actionsCatalog.ts
+
+/**
+ * Machine-readable catalog of ALL actions the Automator may emit.
+ *
+ * Each entry tells the model:
+ * - the canonical action name (matches ACTION_REGISTRY in ChatPanelContainer)
+ * - the required and optional payload fields
+ * - one example
+ *
+ * This is the source of truth for "what the Automator can do".
+ */
+
+export type AutomatorActionName =
+ | "place_component"
+ | "nest_component"
+ | "move_component"
+ | "resize_component"
+ | "delete_component"
+ | "delete_query"
+ | "rename_component"
+ | "set_properties"
+ | "set_style"
+ | "set_theme"
+ | "set_app_metadata"
+ | "set_canvas_setting"
+ | "set_global_css"
+ | "set_global_javascript"
+ | "add_event_handler"
+ | "publish_app"
+ | "align_component";
+
+export interface ActionCatalogEntry {
+ action: AutomatorActionName;
+ purpose: string;
+ required: string[];
+ optional?: string[];
+ example: Record
;
+}
+
+export const ACTIONS_CATALOG: ActionCatalogEntry[] = [
+ // ── Component Management ──────────────────────────────────────────
+ {
+ action: "place_component",
+ purpose: "Place a new component on the root canvas.",
+ required: ["component", "component_name", "layout", "action_parameters"],
+ example: {
+ action: "place_component",
+ component: "button",
+ component_name: "submitBtn",
+ layout: { x: 0, y: 0, w: 6, h: 5 },
+ action_parameters: { text: "Submit" },
+ },
+ },
+ {
+ action: "nest_component",
+ purpose:
+ "Create a NEW component inside an existing container. Do not use this to move or reparent an existing component; if the requested component already exists, ask/explain instead of duplicating it.",
+ required: ["component", "component_name", "parent_component_name", "layout", "action_parameters"],
+ example: {
+ action: "nest_component",
+ component: "input",
+ component_name: "emailInput",
+ parent_component_name: "loginForm.container.body.0.view",
+ layout: { x: 0, y: 0, w: 24, h: 6 },
+ action_parameters: {
+ label: { text: "Email", position: "row", align: "left" },
+ placeholder: "you@example.com",
+ },
+ },
+ },
+ {
+ action: "move_component",
+ purpose: "Move an existing component to a new grid position.",
+ required: ["component_name", "layout"],
+ example: {
+ action: "move_component",
+ component_name: "submitBtn",
+ layout: { x: 6, y: 0 },
+ },
+ },
+ {
+ action: "resize_component",
+ purpose: "Change the width/height of an existing component.",
+ required: ["component_name", "layout"],
+ example: {
+ action: "resize_component",
+ component_name: "todoTable",
+ layout: { w: 24, h: 30 },
+ },
+ },
+ {
+ action: "delete_component",
+ purpose: "Remove an existing component from the canvas.",
+ required: ["component_name"],
+ example: {
+ action: "delete_component",
+ component_name: "oldButton",
+ },
+ },
+ {
+ action: "delete_query",
+ purpose:
+ "Delete an existing bottom-panel data query by name. This action only needs the query name and does not read the query configuration or body.",
+ required: ["query_name"],
+ example: {
+ action: "delete_query",
+ query_name: "getUsers",
+ },
+ },
+ {
+ action: "rename_component",
+ purpose: "Rename an existing component.",
+ required: ["component_name", "action_parameters"],
+ example: {
+ action: "rename_component",
+ component_name: "button1",
+ action_parameters: { new_name: "submitButton" },
+ },
+ },
+ {
+ action: "align_component",
+ purpose:
+ "Move a component horizontally on the canvas grid (left, center, right). This positions the COMPONENT in the canvas — it does NOT change text alignment INSIDE a component. For text/content alignment, use `set_properties` with the component's `horizontalAlignment` / `verticalAlignment` layoutProperties.",
+ required: ["component_name", "action_parameters"],
+ example: {
+ action: "align_component",
+ component_name: "title1",
+ action_parameters: { alignment: "center" },
+ },
+ },
+
+ // ── Properties & Styling ──────────────────────────────────────────
+ {
+ action: "set_properties",
+ purpose:
+ "Update top-level properties on an existing component (text, alignment, autoHeight, type, disabled, label, options, …). Use this for behaviour and layout-style props listed in the component's `layoutProperties`. Use `set_style` for CSS-like visual props.",
+ required: ["component_name", "action_parameters"],
+ example: {
+ action: "set_properties",
+ component_name: "title1",
+ action_parameters: { horizontalAlignment: "center", verticalAlignment: "center" },
+ },
+ },
+ {
+ action: "set_style",
+ purpose:
+ "Apply basic visual styles to a component. Pass an object grouped by explicit style namespace (`style`, `labelStyle`, `inputFieldStyle`, `headerStyle`, `bodyStyle`, etc.). Do not pass flat style keys and do not use animation styles.",
+ required: ["component_name", "action_parameters"],
+ example: {
+ action: "set_style",
+ component_name: "submitBtn",
+ action_parameters: {
+ style: {
+ background: "#1677ff",
+ text: "#ffffff",
+ radius: "8px",
+ textSize: "14px",
+ textWeight: "600",
+ padding: "8px 16px",
+ },
+ },
+ },
+ },
+
+ // ── Events ────────────────────────────────────────────────────────
+ {
+ action: "add_event_handler",
+ purpose: "Add an event handler (click, change, etc.) to a component.",
+ required: ["component_name", "action_parameters"],
+ example: {
+ action: "add_event_handler",
+ component_name: "submitBtn",
+ action_parameters: { event: "click", action_type: "message" },
+ },
+ optional: ["action_parameters.config"],
+ },
+
+ // ── App-Level Configuration ───────────────────────────────────────
+ {
+ action: "set_theme",
+ purpose: "Apply a theme to the whole app.",
+ required: ["action_parameters"],
+ example: {
+ action: "set_theme",
+ action_parameters: { theme: "default" },
+ },
+ },
+ {
+ action: "set_app_metadata",
+ purpose: "Set app title, description, or category.",
+ required: ["action_parameters"],
+ example: {
+ action: "set_app_metadata",
+ action_parameters: { title: "My Todo App", description: "A task tracker." },
+ },
+ },
+ {
+ action: "set_canvas_setting",
+ purpose: "Update canvas grid settings (columns, row height, padding, bg).",
+ required: ["action_parameters"],
+ example: {
+ action: "set_canvas_setting",
+ action_parameters: { gridColumns: 24, gridRowHeight: 8, maxWidth: "1440" },
+ },
+ },
+ {
+ action: "set_global_css",
+ purpose: "Set global CSS rules for the entire app.",
+ required: ["action_parameters"],
+ example: {
+ action: "set_global_css",
+ action_parameters: { code: "body { font-family: 'Inter', sans-serif; }" },
+ },
+ },
+ {
+ action: "set_global_javascript",
+ purpose: "Set global JavaScript that runs on app load.",
+ required: ["action_parameters"],
+ example: {
+ action: "set_global_javascript",
+ action_parameters: { code: "console.log('App loaded');" },
+ },
+ },
+ {
+ action: "publish_app",
+ purpose: "Publish the current app so end-users can access it.",
+ required: [],
+ example: { action: "publish_app" },
+ },
+];
+
+export const SUPPORTED_ACTIONS = new Set(
+ ACTIONS_CATALOG.map((a) => a.action)
+);
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/entries.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/entries.ts
new file mode 100644
index 0000000000..1aa61d12d0
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/entries.ts
@@ -0,0 +1,989 @@
+import type { AutomatorComponentEntry } from "./types";
+import {
+ ALIGN_HORIZONTAL,
+ AUTO_HEIGHT,
+ COMMON_STYLE_KEYS,
+ CONTAINER_STYLE_KEYS,
+ DISABLED,
+ HIDDEN,
+ IMAGE_STYLE_KEYS,
+ INPUT_LIKE_STYLE_KEYS,
+ LABEL_OBJECT,
+ LABEL_STYLE_KEYS,
+ LOADING,
+ NAVIGATION_STYLE_KEYS,
+ TEXT_HORIZONTAL_ALIGNMENT,
+ VERTICAL_ALIGNMENT,
+} from "./presets";
+
+const TEXT: AutomatorComponentEntry = {
+ type: "text",
+ defaultLayout: { w: 12, h: 4 },
+ required: ["text"],
+ optional: [
+ "type",
+ "horizontalAlignment",
+ "verticalAlignment",
+ "autoHeight",
+ "contentScrollBar",
+ "hidden",
+ ],
+ example: { text: "## Hello", type: "markdown" },
+ notes:
+ "Use type:'markdown' for headings, links, formatted text. For text alignment INSIDE the component, set the `horizontalAlignment` property (NOT the `align_component` action — that one moves the component on the canvas grid).",
+ layoutProperties: {
+ type: {
+ description: "Render mode for the value.",
+ enum: ["markdown", "text"],
+ },
+ horizontalAlignment: TEXT_HORIZONTAL_ALIGNMENT,
+ verticalAlignment: VERTICAL_ALIGNMENT,
+ autoHeight: AUTO_HEIGHT,
+ contentScrollBar: {
+ description: "Show scrollbars when content overflows (only when autoHeight=fixed).",
+ type: "boolean",
+ },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...COMMON_STYLE_KEYS, "links"],
+ },
+};
+
+const BUTTON: AutomatorComponentEntry = {
+ type: "button",
+ defaultLayout: { w: 6, h: 5 },
+ required: ["text"],
+ optional: [
+ "type",
+ "disabled",
+ "loading",
+ "form",
+ "prefixIcon",
+ "suffixIcon",
+ "tooltip",
+ "tabIndex",
+ "hidden",
+ ],
+ example: { text: "Submit", type: "" },
+ notes:
+ "Set type:'submit' and form:'' to submit a form. Leave type:'' for a default click-handler button.",
+ layoutProperties: {
+ type: {
+ description: "'' for default click-handler button, 'submit' for a form-submit button.",
+ enum: ["", "submit"],
+ },
+ disabled: DISABLED,
+ loading: LOADING,
+ hidden: HIDDEN,
+ prefixIcon: {
+ description:
+ "Icon path string ('/icon:solid/check') shown before the text. Empty string clears it.",
+ type: "string",
+ },
+ suffixIcon: {
+ description: "Icon shown after the text.",
+ type: "string",
+ },
+ tooltip: { description: "Hover tooltip text.", type: "string" },
+ tabIndex: { description: "Tab order for keyboard navigation.", type: "number" },
+ },
+ styleProperties: {
+ style: [...COMMON_STYLE_KEYS],
+ },
+};
+
+const INPUT: AutomatorComponentEntry = {
+ type: "input",
+ defaultLayout: { w: 12, h: 6 },
+ required: ["label", "placeholder"],
+ optional: [
+ "value",
+ "validationType",
+ "required",
+ "allowClear",
+ "showCount",
+ "readOnly",
+ "disabled",
+ "hidden",
+ "prefixIcon",
+ "suffixIcon",
+ ],
+ example: {
+ label: { text: "Name", position: "row", align: "left" },
+ placeholder: "Enter name",
+ allowClear: true,
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ placeholder: { description: "Placeholder text.", type: "string" },
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ showCount: { description: "Show character counter.", type: "boolean" },
+ allowClear: { description: "Show a clear button.", type: "boolean" },
+ readOnly: { description: "Read-only field.", type: "boolean" },
+ required: { description: "Mark as required for form validation.", type: "boolean" },
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ inputFieldStyle: [...INPUT_LIKE_STYLE_KEYS],
+ },
+};
+
+const NUMBER_INPUT: AutomatorComponentEntry = {
+ type: "numberInput",
+ defaultLayout: { w: 12, h: 6 },
+ required: ["label"],
+ optional: [
+ "value",
+ "min",
+ "max",
+ "step",
+ "placeholder",
+ "disabled",
+ "hidden",
+ "readOnly",
+ ],
+ example: {
+ label: { text: "Quantity", position: "row" },
+ value: 1,
+ min: 0,
+ max: 100,
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ placeholder: { description: "Placeholder text.", type: "string" },
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ readOnly: { description: "Read-only field.", type: "boolean" },
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ inputFieldStyle: [...INPUT_LIKE_STYLE_KEYS],
+ },
+};
+
+const DROPDOWN: AutomatorComponentEntry = {
+ type: "select",
+ defaultLayout: { w: 12, h: 6 },
+ required: ["label", "options", "value"],
+ optional: ["allowClear", "disabled", "hidden", "showSearch", "placeholder"],
+ example: {
+ label: { text: "Status", position: "row" },
+ options: {
+ optionType: "manual",
+ manual: {
+ manual: [
+ { value: "pending", label: "Pending" },
+ { value: "done", label: "Done" },
+ ],
+ },
+ },
+ value: "pending",
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ placeholder: { description: "Placeholder text.", type: "string" },
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ allowClear: { description: "Show a clear button.", type: "boolean" },
+ showSearch: { description: "Enable search filter.", type: "boolean" },
+ },
+ styleProperties: {
+ style: [...COMMON_STYLE_KEYS, "accent", "validate"],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ childrenInputFieldStyle: [...INPUT_LIKE_STYLE_KEYS],
+ },
+};
+
+const CHECKBOX: AutomatorComponentEntry = {
+ type: "checkbox",
+ defaultLayout: { w: 8, h: 5 },
+ required: ["label"],
+ optional: ["value", "options", "disabled", "hidden", "layout"],
+ example: {
+ label: { text: "I agree", position: "row" },
+ value: false,
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ layout: {
+ description: "Group layout direction for multi-option checkboxes.",
+ enum: ["horizontal", "vertical", "autoColumns"],
+ },
+ },
+ styleProperties: {
+ style: [...COMMON_STYLE_KEYS],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ inputFieldStyle: [...INPUT_LIKE_STYLE_KEYS, "checkedBackground", "uncheckedBackground", "uncheckedBorder", "hoverBackground"],
+ },
+};
+
+const FORM: AutomatorComponentEntry = {
+ type: "form",
+ isContainer: true,
+ defaultLayout: { w: 12, h: 30 },
+ required: ["container"],
+ optional: ["hidden", "disabled"],
+ example: {
+ container: {
+ header: {},
+ body: { "0": { view: {} } },
+ footer: {},
+ showHeader: false,
+ showBody: true,
+ showFooter: true,
+ autoHeight: "auto",
+ horizontalGridCells: 24,
+ scrollbars: false,
+ showVerticalScrollbar: false,
+ style: {},
+ },
+ },
+ notes:
+ "Nest input/select/etc. under '.container.body.0.view'. Submit button goes under '.container.footer' with type:'submit', form:''.",
+ layoutProperties: {
+ hidden: HIDDEN,
+ disabled: DISABLED,
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ headerStyle: [...CONTAINER_STYLE_KEYS],
+ bodyStyle: [...CONTAINER_STYLE_KEYS],
+ footerStyle: [...CONTAINER_STYLE_KEYS],
+ },
+};
+
+const CONTAINER: AutomatorComponentEntry = {
+ type: "container",
+ isContainer: true,
+ defaultLayout: { w: 12, h: 20 },
+ required: ["container"],
+ optional: ["hidden"],
+ example: {
+ container: {
+ header: {},
+ body: { "0": { view: {} } },
+ footer: {},
+ showHeader: false,
+ showBody: true,
+ showFooter: false,
+ autoHeight: "auto",
+ horizontalGridCells: 24,
+ style: {},
+ },
+ },
+ notes:
+ "Nest under '.container.body.0.view'.",
+ layoutProperties: {
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ headerStyle: [...CONTAINER_STYLE_KEYS],
+ bodyStyle: [...CONTAINER_STYLE_KEYS],
+ footerStyle: [...CONTAINER_STYLE_KEYS],
+ },
+};
+
+const MODAL: AutomatorComponentEntry = {
+ type: "modal",
+ isContainer: true,
+ defaultLayout: { w: 12, h: 40 },
+ required: ["title", "container"],
+ optional: ["open", "showMask", "maskClosable", "width", "hidden"],
+ example: {
+ title: "Add Item",
+ open: false,
+ container: {},
+ },
+ notes:
+ "container MUST be empty {}. Children are nested under '.container' (no body/header/footer paths).",
+ layoutProperties: {
+ open: { description: "Whether the modal is visible.", type: "boolean" },
+ showMask: { description: "Render the dim background mask.", type: "boolean" },
+ maskClosable: { description: "Allow closing by clicking the mask.", type: "boolean" },
+ width: { description: 'Modal width, e.g. "600px" or "60%".', type: "string" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ },
+};
+
+const DRAWER: AutomatorComponentEntry = {
+ type: "drawer",
+ isContainer: true,
+ defaultLayout: { w: 12, h: 40 },
+ required: ["title", "container"],
+ optional: ["open", "placement", "showMask", "maskClosable", "width", "hidden"],
+ example: { title: "Edit", open: false, container: {} },
+ notes:
+ "Same flat-container rule as modal. Nest under '.container'.",
+ layoutProperties: {
+ open: { description: "Whether the drawer is visible.", type: "boolean" },
+ placement: {
+ description: "Edge from which the drawer slides in.",
+ enum: ["top", "right", "bottom", "left"],
+ },
+ showMask: { description: "Render the dim background mask.", type: "boolean" },
+ maskClosable: { description: "Allow closing by clicking the mask.", type: "boolean" },
+ width: { description: 'Drawer width, e.g. "400px".', type: "string" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ },
+};
+
+const TABLE: AutomatorComponentEntry = {
+ type: "table",
+ defaultLayout: { w: 24, h: 30 },
+ required: ["columns", "data"],
+ optional: [
+ "pagination",
+ "showRowGridBorder",
+ "showHeader",
+ "size",
+ "hidden",
+ "rowAutoHeight",
+ ],
+ layoutProperties: {
+ showHeader: { description: "Render the column header row.", type: "boolean" },
+ showRowGridBorder: { description: "Outline each row.", type: "boolean" },
+ size: { description: "Row density.", enum: ["small", "middle", "large"] },
+ rowAutoHeight: { description: "Auto-size each row to content.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ headerStyle: [...COMMON_STYLE_KEYS],
+ rowStyle: [...COMMON_STYLE_KEYS],
+ cellStyle: [...COMMON_STYLE_KEYS],
+ },
+ example: {
+ columns: [
+ {
+ title: "Task",
+ dataIndex: "task",
+ render: { compType: "text", comp: { text: "{{currentCell}}" } },
+ },
+ {
+ title: "Status",
+ dataIndex: "status",
+ render: { compType: "text", comp: { text: "{{currentCell}}" } },
+ },
+ ],
+ data: '[{"task":"Buy groceries","status":"Pending"},{"task":"Call Alice","status":"Done"}]',
+ pagination: { pageSizeOptions: "[5, 10, 20, 50]" },
+ showRowGridBorder: true,
+ },
+ notes:
+ "`data` MUST be a stringified JSON array. Use {{currentCell}} in render unless told otherwise.",
+};
+
+const LIST_VIEW: AutomatorComponentEntry = {
+ type: "listView",
+ isContainer: true,
+ defaultLayout: { w: 24, h: 30 },
+ required: ["container"],
+ optional: [
+ "noOfRows",
+ "itemIndexName",
+ "itemDataName",
+ "noOfColumns",
+ "horizontal",
+ "scrollbars",
+ "hidden",
+ ],
+ layoutProperties: {
+ noOfColumns: { description: "Columns per row in the grid.", type: "number" },
+ horizontal: { description: "Render rows horizontally.", type: "boolean" },
+ scrollbars: { description: "Always show scrollbars.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ },
+ example: {
+ container: {},
+ noOfRows: "3",
+ itemIndexName: "i",
+ itemDataName: "currentItem",
+ },
+ notes:
+ "container is the per-item template. Nest item components directly under '.container' (flat). Do NOT use body/header/footer.",
+};
+
+const IMAGE: AutomatorComponentEntry = {
+ type: "image",
+ defaultLayout: { w: 8, h: 12 },
+ required: ["src"],
+ optional: [
+ "autoHeight",
+ "placement",
+ "enableOverflow",
+ "aspectRatio",
+ "supportPreview",
+ "hidden",
+ "clipPath",
+ ],
+ example: { src: "https://images.unsplash.com/photo-1518770660439-4636190af475" },
+ notes: "src MUST be a real, publicly accessible URL.",
+ layoutProperties: {
+ autoHeight: AUTO_HEIGHT,
+ placement: {
+ description: "Where the image sits inside the cell.",
+ enum: [
+ "top",
+ "bottom",
+ "left",
+ "right",
+ "top-left",
+ "top-right",
+ "bottom-left",
+ "bottom-right",
+ ],
+ },
+ enableOverflow: { description: "Crop image to fit instead of contain.", type: "boolean" },
+ aspectRatio: { description: 'CSS aspect-ratio (e.g. "16 / 9").', type: "string" },
+ supportPreview: { description: "Allow click-to-preview at full size.", type: "boolean" },
+ hidden: HIDDEN,
+ clipPath: { description: "CSS clip-path string.", type: "string" },
+ },
+ styleProperties: {
+ style: [...IMAGE_STYLE_KEYS],
+ },
+};
+
+const DIVIDER: AutomatorComponentEntry = {
+ type: "divider",
+ defaultLayout: { w: 24, h: 2 },
+ required: [],
+ optional: ["title", "align", "type", "dashed", "hidden"],
+ example: {},
+ layoutProperties: {
+ title: { description: "Optional label rendered in the divider.", type: "string" },
+ align: ALIGN_HORIZONTAL,
+ type: { description: "Orientation.", enum: ["horizontal", "vertical"] },
+ dashed: { description: "Render with a dashed line.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...COMMON_STYLE_KEYS],
+ },
+};
+
+const DATE: AutomatorComponentEntry = {
+ type: "date",
+ defaultLayout: { w: 12, h: 6 },
+ required: ["label"],
+ optional: ["value", "format", "placeholder", "disabled", "hidden", "showTime"],
+ example: {
+ label: { text: "Due date", position: "row" },
+ format: "YYYY-MM-DD",
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ placeholder: { description: "Placeholder text.", type: "string" },
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ showTime: { description: "Include time picker.", type: "boolean" },
+ format: { description: 'Display/parse format, e.g. "YYYY-MM-DD".', type: "string" },
+ },
+ styleProperties: {
+ style: [...COMMON_STYLE_KEYS, "accent", "validate"],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ inputFieldStyle: [...INPUT_LIKE_STYLE_KEYS],
+ },
+};
+
+const SWITCH: AutomatorComponentEntry = {
+ type: "switch",
+ defaultLayout: { w: 6, h: 5 },
+ required: ["label"],
+ optional: ["value", "disabled", "hidden"],
+ example: {
+ label: { text: "Enabled", position: "row" },
+ value: true,
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: ["handle", "unchecked", "checked", "margin", "padding"],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ },
+};
+
+const TEXT_AREA: AutomatorComponentEntry = {
+ type: "textArea",
+ defaultLayout: { w: 12, h: 8 },
+ required: ["label"],
+ optional: [
+ "placeholder",
+ "value",
+ "autoHeight",
+ "disabled",
+ "hidden",
+ "readOnly",
+ "showCount",
+ "allowClear",
+ ],
+ example: {
+ label: { text: "Description", position: "row" },
+ placeholder: "Enter description...",
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ placeholder: { description: "Placeholder text.", type: "string" },
+ autoHeight: AUTO_HEIGHT,
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ readOnly: { description: "Read-only field.", type: "boolean" },
+ showCount: { description: "Show character counter.", type: "boolean" },
+ allowClear: { description: "Show a clear button.", type: "boolean" },
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ inputFieldStyle: [...INPUT_LIKE_STYLE_KEYS],
+ },
+};
+
+const PASSWORD: AutomatorComponentEntry = {
+ type: "password",
+ defaultLayout: { w: 12, h: 6 },
+ required: ["label"],
+ optional: ["placeholder", "disabled", "hidden", "visibilityToggle"],
+ example: {
+ label: { text: "Password", position: "row" },
+ placeholder: "Enter password",
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ placeholder: { description: "Placeholder text.", type: "string" },
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ visibilityToggle: { description: "Show the eye toggle to reveal the password.", type: "boolean" },
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ inputFieldStyle: [...INPUT_LIKE_STYLE_KEYS],
+ },
+};
+
+const CHART: AutomatorComponentEntry = {
+ type: "chart",
+ defaultLayout: { w: 12, h: 20 },
+ required: ["chartType", "data"],
+ optional: ["title", "xAxisKey", "hidden", "showLegend"],
+ example: {
+ chartType: "bar",
+ data: '[{"category":"A","value":30},{"category":"B","value":50},{"category":"C","value":20}]',
+ title: "Sales by Category",
+ xAxisKey: "category",
+ },
+ notes: "chartType: 'bar', 'line', 'pie', 'scatter'. data is a stringified JSON array.",
+ layoutProperties: {
+ chartType: {
+ description: "Visualisation kind.",
+ enum: ["bar", "line", "pie", "scatter"],
+ },
+ title: { description: "Chart title.", type: "string" },
+ showLegend: { description: "Display the legend.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [
+ "chartBackgroundColor",
+ "chartGradientColor",
+ "chartShadowColor",
+ "chartBorderColor",
+ "chartTextColor",
+ "chartTextSize",
+ "chartTextWeight",
+ "chartFontFamily",
+ "chartFontStyle",
+ "chartBorderStyle",
+ "chartBorderRadius",
+ "chartBorderWidth",
+ "chartOpacity",
+ "chartBoxShadow",
+ "margin",
+ "padding",
+ ],
+ },
+};
+
+const CARD: AutomatorComponentEntry = {
+ type: "card",
+ isContainer: true,
+ defaultLayout: { w: 8, h: 15 },
+ required: ["title"],
+ optional: [
+ "size",
+ "showTitle",
+ "extraTitle",
+ "cardType",
+ "CoverImg",
+ "imgSrc",
+ "imgHeight",
+ "showMeta",
+ "metaTitle",
+ "metaDesc",
+ "hoverable",
+ "showActionIcon",
+ "hidden",
+ ],
+ example: {
+ title: "Card Title",
+ cardType: "common",
+ CoverImg: true,
+ imgSrc: "https://images.unsplash.com/photo-1518770660439-4636190af475",
+ imgHeight: "180px",
+ showMeta: true,
+ metaTitle: "Project name",
+ metaDesc: "Short project description",
+ },
+ notes:
+ "Nest content inside '.container'. For the built-in card cover image, use `set_properties` on the card with `imgSrc` (not `src`) and set `CoverImg: true`; `src` is only for standalone Image components.",
+ layoutProperties: {
+ size: { description: "Card density.", enum: ["default", "small"] },
+ showTitle: { description: "Render the title bar.", type: "boolean" },
+ extraTitle: { description: "Text rendered in the card header extra link.", type: "string" },
+ cardType: { description: "Card mode. Built-in cover/meta fields require common.", enum: ["common", "custom"] },
+ CoverImg: { description: "Show the built-in cover image.", type: "boolean" },
+ imgSrc: { description: "Built-in card cover image URL. Use this for card cover image edits.", type: "string" },
+ imgHeight: { description: 'Built-in card cover image height, e.g. "180px" or "auto".', type: "string" },
+ showMeta: { description: "Show built-in card meta title and description.", type: "boolean" },
+ metaTitle: { description: "Built-in card meta title.", type: "string" },
+ metaDesc: { description: "Built-in card meta description.", type: "string" },
+ hoverable: { description: "Lift on hover.", type: "boolean" },
+ showActionIcon: { description: "Show built-in card action icons.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS, "IconColor", "activateColor"],
+ headerStyle: [...COMMON_STYLE_KEYS],
+ bodyStyle: [...CONTAINER_STYLE_KEYS],
+ },
+};
+
+const TABBED_CONTAINER: AutomatorComponentEntry = {
+ type: "tabbedContainer",
+ isContainer: true,
+ defaultLayout: { w: 24, h: 30 },
+ required: ["container"],
+ optional: ["tabs", "tabPosition", "showHeader", "hidden"],
+ example: { container: {} },
+ notes: "Nest content per tab. Tabs are managed via properties.",
+ layoutProperties: {
+ tabPosition: {
+ description: "Tab bar placement.",
+ enum: ["top", "right", "bottom", "left"],
+ },
+ showHeader: { description: "Show the tabs bar.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...CONTAINER_STYLE_KEYS],
+ headerStyle: [...COMMON_STYLE_KEYS],
+ bodyStyle: [...CONTAINER_STYLE_KEYS],
+ tabsStyle: [...COMMON_STYLE_KEYS],
+ },
+};
+
+const VIDEO: AutomatorComponentEntry = {
+ type: "video",
+ defaultLayout: { w: 12, h: 15 },
+ required: ["src"],
+ optional: ["controls", "autoPlay", "loop", "muted", "hidden"],
+ example: { src: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", controls: true },
+ notes: "src must be a real URL. Set layout.h >= 10.",
+ layoutProperties: {
+ controls: { description: "Show native player controls.", type: "boolean" },
+ autoPlay: { description: "Auto-play on mount (often requires muted=true).", type: "boolean" },
+ loop: { description: "Loop the video.", type: "boolean" },
+ muted: { description: "Start muted.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: ["margin", "padding"],
+ },
+};
+
+const AVATAR: AutomatorComponentEntry = {
+ type: "avatar",
+ defaultLayout: { w: 6, h: 6 },
+ required: ["icon", "iconSize"],
+ optional: ["src", "avatarLabel", "avatarCatption", "shape", "hidden"],
+ example: {
+ icon: "/icon:solid/user",
+ iconSize: "40",
+ shape: "circle",
+ avatarLabel: "John Doe",
+ },
+ layoutProperties: {
+ shape: { description: "Avatar shape.", enum: ["circle", "square"] },
+ iconSize: { description: "Icon pixel size as a string, e.g. '40'.", type: "string" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: ["background", "fill"],
+ avatarLabelStyle: [...COMMON_STYLE_KEYS],
+ avatarContainerStyle: [...CONTAINER_STYLE_KEYS],
+ },
+};
+
+const PROGRESS: AutomatorComponentEntry = {
+ type: "progress",
+ defaultLayout: { w: 12, h: 4 },
+ required: ["value"],
+ optional: ["showInfo", "hidden"],
+ example: { value: "75" },
+ layoutProperties: {
+ showInfo: { description: "Display the percentage label.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: ["text", "textSize", "textWeight", "fontFamily", "fontStyle", "radius", "margin", "padding", "lineHeight", "track", "fill", "success"],
+ },
+};
+
+const RATING: AutomatorComponentEntry = {
+ type: "rating",
+ defaultLayout: { w: 8, h: 5 },
+ required: ["label"],
+ optional: ["value", "max", "allowHalf", "disabled", "hidden"],
+ example: {
+ label: { text: "Rating", position: "row" },
+ value: "3",
+ max: "5",
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ allowHalf: { description: "Allow half-star ratings.", type: "boolean" },
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: ["checked", "unchecked", "margin", "padding"],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ },
+};
+
+const SLIDER: AutomatorComponentEntry = {
+ type: "slider",
+ defaultLayout: { w: 12, h: 5 },
+ required: ["label"],
+ optional: ["value", "min", "max", "step", "disabled", "hidden", "vertical"],
+ example: {
+ label: { text: "Volume", position: "row" },
+ value: "50",
+ min: "0",
+ max: "100",
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ vertical: { description: "Render vertically.", type: "boolean" },
+ },
+ styleProperties: {
+ style: ["fill", "thumb", "thumbBorder", "track", "margin", "padding"],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ },
+};
+
+const NAVIGATION: AutomatorComponentEntry = {
+ type: "navigation",
+ defaultLayout: { w: 24, h: 5 },
+ required: ["items"],
+ optional: ["logoUrl", "horizontalAlignment", "hidden"],
+ example: {
+ items: [
+ { label: "Home", hidden: false },
+ { label: "About", hidden: false },
+ ],
+ },
+ layoutProperties: {
+ horizontalAlignment: ALIGN_HORIZONTAL,
+ hidden: HIDDEN,
+ logoUrl: { description: "Optional logo image URL.", type: "string" },
+ },
+ styleProperties: {
+ style: [...NAVIGATION_STYLE_KEYS],
+ },
+};
+
+const TIMELINE: AutomatorComponentEntry = {
+ type: "timeline",
+ defaultLayout: { w: 12, h: 15 },
+ required: ["value"],
+ optional: ["mode", "reverse", "hidden"],
+ example: {
+ value: '[{"title":"Step 1","subTitle":"Started"},{"title":"Step 2","subTitle":"In Progress"}]',
+ },
+ notes: "value must be a stringified JSON array of timeline entries.",
+ layoutProperties: {
+ mode: {
+ description: "Layout mode for entries.",
+ enum: ["left", "alternate", "right"],
+ },
+ reverse: { description: "Reverse entry order.", type: "boolean" },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [
+ "background",
+ "titleColor",
+ "subTitleColor",
+ "labelColor",
+ "margin",
+ "padding",
+ "radius",
+ ],
+ },
+};
+
+const STEP: AutomatorComponentEntry = {
+ type: "step",
+ defaultLayout: { w: 24, h: 6 },
+ required: ["value", "options"],
+ optional: ["initialValue", "direction", "size", "hidden"],
+ layoutProperties: {
+ direction: {
+ description: "Step bar orientation.",
+ enum: ["horizontal", "vertical"],
+ },
+ size: { description: "Step density.", enum: ["default", "small"] },
+ hidden: HIDDEN,
+ },
+ styleProperties: {
+ style: [...COMMON_STYLE_KEYS],
+ },
+ example: {
+ value: "1",
+ initialValue: "1",
+ options: {
+ optionType: "manual",
+ manual: {
+ manual: [
+ { value: "1", label: "Step 1" },
+ { value: "2", label: "Step 2" },
+ { value: "3", label: "Step 3" },
+ ],
+ },
+ },
+ },
+ notes: "Step values must be numbers starting from 1.",
+};
+
+const RADIO: AutomatorComponentEntry = {
+ type: "radio",
+ defaultLayout: { w: 12, h: 5 },
+ required: ["label", "options"],
+ optional: ["value", "disabled", "hidden", "layout"],
+ example: {
+ label: { text: "Priority", position: "row" },
+ options: {
+ optionType: "manual",
+ manual: {
+ manual: [
+ { value: "low", label: "Low" },
+ { value: "medium", label: "Medium" },
+ { value: "high", label: "High" },
+ ],
+ },
+ },
+ value: "medium",
+ },
+ layoutProperties: {
+ label: LABEL_OBJECT,
+ disabled: DISABLED,
+ hidden: HIDDEN,
+ layout: {
+ description: "Group layout direction.",
+ enum: ["horizontal", "vertical", "autoColumns"],
+ },
+ },
+ styleProperties: {
+ style: [...COMMON_STYLE_KEYS],
+ labelStyle: [...LABEL_STYLE_KEYS],
+ inputFieldStyle: [...INPUT_LIKE_STYLE_KEYS, "checkedBackground", "uncheckedBackground", "uncheckedBorder", "hoverBackground"],
+ },
+};
+
+const CHAT: AutomatorComponentEntry = {
+ type: "chat",
+ defaultLayout: { w: 12, h: 20 },
+ required: [],
+ optional: ["chatQuery", "tableName", "placeholder"],
+ example: {
+ tableName: "LC_AI",
+ placeholder: "Ask anything...",
+ },
+ notes: "AI chat component for embedding a conversational assistant in the app.",
+};
+
+const CHAT_BOX: AutomatorComponentEntry = {
+ type: "chatBox",
+ defaultLayout: { w: 12, h: 24 },
+ required: [],
+ optional: ["messages", "controller", "placeholder"],
+ example: {},
+ notes: "Chat UI for displaying messages and sending user input. Pair with chatController for realtime typing/presence.",
+};
+
+const CHAT_CONTROLLER: AutomatorComponentEntry = {
+ type: "chatController",
+ defaultLayout: { w: 12, h: 5 },
+ required: [],
+ optional: ["roomId"],
+ example: {},
+ notes: "Realtime chat controller hook. Use with chatBox for presence and typing indicators.",
+};
+
+export const AUTOMATOR_COMPONENTS: AutomatorComponentEntry[] = [
+ TEXT,
+ BUTTON,
+ INPUT,
+ NUMBER_INPUT,
+ TEXT_AREA,
+ PASSWORD,
+ DROPDOWN,
+ CHECKBOX,
+ RADIO,
+ SWITCH,
+ SLIDER,
+ RATING,
+ DATE,
+ FORM,
+ CONTAINER,
+ MODAL,
+ DRAWER,
+ TABLE,
+ LIST_VIEW,
+ CARD,
+ TABBED_CONTAINER,
+ IMAGE,
+ VIDEO,
+ AVATAR,
+ CHART,
+ PROGRESS,
+ NAVIGATION,
+ TIMELINE,
+ STEP,
+ DIVIDER,
+ CHAT,
+ CHAT_BOX,
+ CHAT_CONTROLLER,
+];
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/index.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/index.ts
new file mode 100644
index 0000000000..b6df97a977
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/index.ts
@@ -0,0 +1,18 @@
+import { AUTOMATOR_COMPONENTS } from "./entries";
+import type { AutomatorComponentEntry } from "./types";
+
+/** Returns the curated set of components Automator is allowed to use. */
+export function getAutomatorComponents(): AutomatorComponentEntry[] {
+ return [...AUTOMATOR_COMPONENTS];
+}
+
+export const AUTOMATOR_COMPONENT_TYPES: string[] = AUTOMATOR_COMPONENTS.map(
+ (component) => component.type
+);
+
+export { AUTOMATOR_COMPONENTS } from "./entries";
+export type {
+ AutomatorComponentEntry,
+ AutomatorLayoutPropertyDescriptor,
+ AutomatorStylePropertyMap,
+} from "./types";
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/presets.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/presets.ts
new file mode 100644
index 0000000000..8adf1ff28d
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/presets.ts
@@ -0,0 +1,144 @@
+import type { AutomatorLayoutPropertyDescriptor } from "./types";
+
+// ── Style key presets ────────────────────────────────────────────────────────
+// Mirror the field lists from `comps/controls/styleControlConstants.tsx` so the
+// model knows what keys it can pass to `set_style`. Keep these compact — they
+// are inlined into the system prompt.
+
+export const COMMON_STYLE_KEYS = [
+ "background",
+ "text",
+ "textTransform",
+ "textDecoration",
+ "textSize",
+ "textWeight",
+ "fontFamily",
+ "fontStyle",
+ "border",
+ "borderStyle",
+ "borderWidth",
+ "radius",
+ "margin",
+ "padding",
+ "lineHeight",
+] as const;
+
+export const CONTAINER_STYLE_KEYS = [
+ "background",
+ "border",
+ "borderStyle",
+ "borderWidth",
+ "radius",
+ "margin",
+ "padding",
+ "boxShadow",
+ "boxShadowColor",
+ "opacity",
+] as const;
+
+export const INPUT_LIKE_STYLE_KEYS = [
+ "background",
+ "boxShadow",
+ "boxShadowColor",
+ "text",
+ "textTransform",
+ "textDecoration",
+ "textSize",
+ "textWeight",
+ "fontFamily",
+ "fontStyle",
+ "border",
+ "borderStyle",
+ "borderWidth",
+ "radius",
+ "margin",
+ "padding",
+ "placeholder",
+ "accent",
+ "validate",
+] as const;
+
+export const LABEL_STYLE_KEYS = [
+ "background",
+ "label",
+ "textTransform",
+ "textDecoration",
+ "textSize",
+ "textWeight",
+ "fontFamily",
+ "fontStyle",
+ "borderStyle",
+ "borderWidth",
+ "margin",
+ "padding",
+ "placeholder",
+ "accent",
+ "validate",
+] as const;
+
+export const IMAGE_STYLE_KEYS = [
+ "margin",
+ "padding",
+ "border",
+ "borderStyle",
+ "borderWidth",
+ "radius",
+ "opacity",
+ "boxShadow",
+ "boxShadowColor",
+] as const;
+
+export const NAVIGATION_STYLE_KEYS = [
+ "background",
+ "border",
+ "borderStyle",
+ "borderWidth",
+ "radius",
+ "margin",
+ "padding",
+ "accent",
+] as const;
+
+// ── Layout property presets ─────────────────────────────────────────────────
+
+export const TEXT_HORIZONTAL_ALIGNMENT: AutomatorLayoutPropertyDescriptor = {
+ description: "Horizontal text alignment inside the component.",
+ enum: ["left", "center", "right", "justify"],
+};
+
+export const ALIGN_HORIZONTAL: AutomatorLayoutPropertyDescriptor = {
+ description: "Horizontal alignment.",
+ enum: ["left", "center", "right"],
+};
+
+export const VERTICAL_ALIGNMENT: AutomatorLayoutPropertyDescriptor = {
+ description: "Vertical alignment.",
+ enum: ["flex-start", "center", "flex-end"],
+};
+
+export const AUTO_HEIGHT: AutomatorLayoutPropertyDescriptor = {
+ description: "Whether the component auto-sizes its height to its content.",
+ enum: ["auto", "fixed"],
+};
+
+export const HIDDEN: AutomatorLayoutPropertyDescriptor = {
+ description: "Hide the component at runtime.",
+ type: "boolean",
+};
+
+export const DISABLED: AutomatorLayoutPropertyDescriptor = {
+ description: "Disable the component at runtime.",
+ type: "boolean",
+};
+
+export const LOADING: AutomatorLayoutPropertyDescriptor = {
+ description: "Show a loading indicator on the component.",
+ type: "boolean",
+};
+
+export const LABEL_OBJECT: AutomatorLayoutPropertyDescriptor = {
+ description:
+ "Field label config: { text, position: 'row'|'column', align: 'left'|'center'|'right', width: number, hidden?: boolean, tooltip?: string }.",
+ type: "object",
+ example: { text: "Email", position: "row", align: "left" },
+};
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/types.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/types.ts
new file mode 100644
index 0000000000..a30d120d4d
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/automatorComponents/types.ts
@@ -0,0 +1,44 @@
+/**
+ * Component instructions exposed to the Automator model.
+ *
+ * These are curated references, not a dump of every Lowcoder component. If a
+ * component appears here, Automator is allowed to place it and has enough shape
+ * information to configure it with reasonable confidence.
+ */
+export interface AutomatorLayoutPropertyDescriptor {
+ /** Human-readable hint shown to the model. */
+ description?: string;
+ /** Allowed string values when this property is a fixed enum. */
+ enum?: readonly string[];
+ /** Primitive type when the value is not enum-restricted. */
+ type?: "string" | "number" | "boolean" | "object";
+ /** Sample value the model can imitate verbatim. */
+ example?: unknown;
+}
+
+/**
+ * Map of style namespace -> list of style keys that can be passed to
+ * `set_style`. The executor expects values grouped by explicit namespace.
+ */
+export type AutomatorStylePropertyMap = Record;
+
+export interface AutomatorComponentEntry {
+ /** Component type used by Lowcoder action executors. */
+ type: string;
+ /** Whether the component can have children nested under `.container`. */
+ isContainer?: boolean;
+ /** Default grid layout (w / h) for sensible initial sizing. */
+ defaultLayout: { w: number; h: number };
+ /** Required property keys for `action_parameters`. */
+ required: string[];
+ /** Optional property keys worth knowing about. */
+ optional?: string[];
+ /** Realistic example `action_parameters` payload. */
+ example: Record;
+ /** Notes the model should heed. */
+ notes?: string;
+ /** Top-level UI / behavior properties for `set_properties`. */
+ layoutProperties?: Record;
+ /** Style properties grouped by style namespace, used by `set_style`. */
+ styleProperties?: AutomatorStylePropertyMap;
+}
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/editorSnapshot.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/editorSnapshot.ts
new file mode 100644
index 0000000000..3d5638c676
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/editorSnapshot.ts
@@ -0,0 +1,202 @@
+// client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/editorSnapshot.ts
+
+import type { EditorState } from "@lowcoder-ee/comps/editorState";
+/**
+ * A compact, JSON-serialisable view of the live editor state.
+ *
+ * This is what we hand to the LLM as `EDITOR_CONTEXT`, so the model can
+ * reason about what already exists on the canvas before emitting actions
+ * (avoids name collisions, layout overlap, duplicated queries, etc.).
+ *
+ * Keep this small. We intentionally drop heavy fields like full property
+ * trees, event handlers, deep style objects — those would balloon tokens
+ * and rarely help the model. If a deeper inspection is needed later,
+ * model can ask the user.
+ */
+
+export interface ComponentSnapshot {
+ name: string;
+ type: string;
+ layout?: { x?: number; y?: number; w?: number; h?: number };
+ /** Trimmed property hints (label text / placeholder / button text…) */
+ hints?: Record;
+}
+
+export interface QuerySnapshot {
+ name: string;
+ type: string;
+}
+
+export interface EditorSnapshot {
+ app: {
+ title?: string;
+ description?: string;
+ themeId?: string;
+ };
+ canvas: {
+ gridColumns?: number;
+ gridRowHeight?: number;
+ gridRowCount?: number | string;
+ maxWidth?: string;
+ };
+ selected: string | null;
+ components: ComponentSnapshot[];
+ queries: QuerySnapshot[];
+ tempStates: QuerySnapshot[];
+ transformers: QuerySnapshot[];
+}
+
+const HINT_KEYS = new Set([
+ "text",
+ "title",
+ "label",
+ "placeholder",
+ "src",
+ "value",
+ "options",
+ "columns",
+ "open",
+]);
+
+function pickHints(data: Record | undefined): Record | undefined {
+ if (!data || typeof data !== "object") return undefined;
+ const out: Record = {};
+ for (const k of Object.keys(data)) {
+ if (!HINT_KEYS.has(k)) continue;
+ const v = (data as any)[k];
+ // truncate long strings, drop functions
+ if (typeof v === "string") {
+ out[k] = v.length > 120 ? v.slice(0, 120) + "…" : v;
+ } else if (typeof v === "number" || typeof v === "boolean") {
+ out[k] = v;
+ } else if (v != null && typeof v === "object") {
+ // shallow-copy small objects (e.g. label.text)
+ try {
+ const json = JSON.stringify(v);
+ if (json.length <= 200) out[k] = JSON.parse(json);
+ } catch {
+ /* ignore */
+ }
+ }
+ }
+ return Object.keys(out).length > 0 ? out : undefined;
+}
+
+function safe(fn: () => T, fallback: T): T {
+ try {
+ return fn();
+ } catch {
+ return fallback;
+ }
+}
+
+/**
+ * Build a snapshot from the live `EditorState`. Always returns a valid
+ * object — never throws — so the orchestrator can call this freely without
+ * try/catch.
+ */
+export function buildEditorSnapshot(editorState: EditorState | null | undefined): EditorSnapshot {
+ if (!editorState) {
+ return {
+ app: {},
+ canvas: {},
+ selected: null,
+ components: [],
+ queries: [],
+ tempStates: [],
+ transformers: [],
+ };
+ }
+
+ const settings = safe(() => editorState.getAppSettings(), {} as any);
+
+ const components = safe(
+ () =>
+ editorState.uiCompInfoList().map((info: any): ComponentSnapshot => {
+ // The layout x/y/w/h lives on the rootComp's layout map, keyed by
+ // the same key as `getAllUICompMap`. We don't have a direct lookup
+ // here without scanning, so we leave layout undefined for now and
+ // let the model rely on `name` + `type`.
+ return {
+ name: info.name,
+ type: info.type,
+ hints: pickHints(info.data),
+ };
+ }),
+ []
+ );
+
+ // Try to enrich with layout positions from the root grid.
+ safe(() => {
+ const uiComp: any = editorState.getUIComp();
+ const compChildren = uiComp?.children?.comp?.children;
+ const layoutObj = compChildren?.layout?.getView?.() ?? {};
+ const items = compChildren?.items?.children ?? {};
+ const byName: Record = {};
+ for (const [key, layout] of Object.entries(layoutObj)) {
+ const item: any = items[key];
+ const name = item?.children?.name?.getView?.();
+ if (!name) continue;
+ byName[name] = {
+ x: layout?.x,
+ y: layout?.y,
+ w: layout?.w,
+ h: layout?.h,
+ };
+ }
+ for (const c of components) {
+ if (byName[c.name]) {
+ c.layout = byName[c.name];
+ }
+ }
+ }, undefined);
+
+ const queries: QuerySnapshot[] = safe(
+ () =>
+ editorState.queryCompInfoList().map((q: any) => ({
+ name: q.name,
+ type: q.type,
+ })),
+ []
+ );
+ const tempStates: QuerySnapshot[] = safe(
+ () =>
+ editorState.getTempStateCompInfoList().map((q: any) => ({
+ name: q.name,
+ type: q.type,
+ })),
+ []
+ );
+ const transformers: QuerySnapshot[] = safe(
+ () =>
+ editorState.getTransformerCompInfoList().map((q: any) => ({
+ name: q.name,
+ type: q.type,
+ })),
+ []
+ );
+
+ const selected = safe(() => {
+ const sel = Array.from(editorState.selectedCompNames || []);
+ return sel.length > 0 ? sel[0] : null;
+ }, null);
+
+ return {
+ app: {
+ title: settings?.title,
+ description: settings?.description,
+ themeId: settings?.themeId,
+ },
+ canvas: {
+ gridColumns: settings?.gridColumns,
+ gridRowHeight: settings?.gridRowHeight,
+ gridRowCount: settings?.gridRowCount,
+ maxWidth: settings?.maxWidth,
+ },
+ selected,
+ components,
+ queries,
+ tempStates,
+ transformers,
+ };
+}
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/index.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/index.ts
new file mode 100644
index 0000000000..3e4a1feec9
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/index.ts
@@ -0,0 +1,34 @@
+// client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/index.ts
+
+export { AUTOMATOR_SYSTEM_PROMPT, composeSystemMessage } from "./systemPrompt";
+export {
+ ACTIONS_CATALOG,
+ SUPPORTED_ACTIONS,
+ type AutomatorActionName,
+ type ActionCatalogEntry,
+} from "./actionsCatalog";
+export {
+ AUTOMATOR_COMPONENTS,
+ AUTOMATOR_COMPONENT_TYPES,
+ getAutomatorComponents,
+ type AutomatorComponentEntry,
+ type AutomatorLayoutPropertyDescriptor,
+ type AutomatorStylePropertyMap,
+} from "./automatorComponents";
+export {
+ buildEditorSnapshot,
+ type EditorSnapshot,
+ type ComponentSnapshot,
+ type QuerySnapshot,
+} from "./editorSnapshot";
+export {
+ buildAutomatorPayload,
+ type LLMMessage,
+ type OrchestratorInput,
+ type OrchestratorOutput,
+} from "./orchestrator";
+export {
+ buildToolDefinitions,
+ TOOL_NAME,
+ type OpenAIToolDefinition,
+} from "./toolDefinitions";
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/orchestrator.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/orchestrator.ts
new file mode 100644
index 0000000000..6caf2933bd
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/orchestrator.ts
@@ -0,0 +1,80 @@
+// client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/orchestrator.ts
+
+import type { EditorState } from "@lowcoder-ee/comps/editorState";
+import { ACTIONS_CATALOG } from "./actionsCatalog";
+import { buildEditorSnapshot, EditorSnapshot } from "./editorSnapshot";
+import {
+ getAutomatorComponents,
+ type AutomatorComponentEntry,
+} from "./automatorComponents";
+import { composeSystemMessage } from "./systemPrompt";
+import { buildToolDefinitions, OpenAIToolDefinition } from "./toolDefinitions";
+
+/**
+ * A "chat message" in the OpenAI-compatible shape (role + content). Almost
+ * every LLM HTTP API understands this — OpenAI, Ollama, Together, Anthropic
+ * (with a small mapping), Groq, etc.
+ *
+ * The Automator emits messages in this exact shape inside the `messages`
+ * argument that gets passed to the user-defined Lowcoder query.
+ */
+export interface LLMMessage {
+ role: "system" | "user" | "assistant";
+ content: string;
+}
+
+export interface OrchestratorInput {
+ /** The conversation history so far (already includes the latest user msg). */
+ history: { role: "user" | "assistant"; content: string }[];
+ /** Live editor state — used to build the EDITOR_CONTEXT block. */
+ editorState: EditorState | null | undefined;
+}
+
+export interface OrchestratorOutput {
+ /** Full message array including the synthesised system message. */
+ messages: LLMMessage[];
+ /** OpenAI-compatible tool definitions for function calling. */
+ tools: OpenAIToolDefinition[];
+ /** The composed system message string (also exposed for power users). */
+ system: string;
+ /** The editor context snapshot (also exposed separately). */
+ context: EditorSnapshot;
+ /** The actions catalog passed to the model. */
+ actionsCatalog: typeof ACTIONS_CATALOG;
+ /** The curated Automator component instructions passed to the model. */
+ automatorComponents: AutomatorComponentEntry[];
+}
+
+/**
+ * Build everything the user-defined Lowcoder query needs to call the LLM
+ * with full Lowcoder context. Pure function — does not mutate state, does
+ * not call the network.
+ */
+export function buildAutomatorPayload(input: OrchestratorInput): OrchestratorOutput {
+ const { history, editorState } = input;
+
+ const context = buildEditorSnapshot(editorState);
+ const automatorComponents = getAutomatorComponents();
+
+ const system = composeSystemMessage({
+ actionsCatalog: ACTIONS_CATALOG,
+ automatorComponents,
+ editorContext: context,
+ });
+
+ const tools = buildToolDefinitions(automatorComponents.map((component) => component.type));
+
+ const messages: LLMMessage[] = [{ role: "system", content: system }];
+ for (const m of history) {
+ messages.push({ role: m.role, content: m.content });
+ }
+
+ return {
+ messages,
+ tools,
+ system,
+ context,
+ actionsCatalog: ACTIONS_CATALOG,
+ automatorComponents,
+ };
+}
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/systemPrompt.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/systemPrompt.ts
new file mode 100644
index 0000000000..46e2884b92
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/systemPrompt.ts
@@ -0,0 +1,210 @@
+// client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/systemPrompt.ts
+
+/**
+ * Lean system prompt for the Lowcoder Automator.
+ *
+ * Design goals (vs the legacy 4.7K-line `Latest_prompt.md`):
+ * - Keep the *rules* short and stable (this file).
+ * - Inject the *allowed actions* and the *live editor context* dynamically
+ * at request time so the model only ever sees what is relevant.
+ * - Force a strict, parseable JSON output shape: `{ explanation, actions }`.
+ *
+ * The orchestrator combines this prompt with:
+ * - the actions catalog (what the model is allowed to emit)
+ * - Automator components (curated component instructions and examples)
+ * - the live editor snapshot (existing components, queries, canvas grid)
+ *
+ * before sending it to the user-defined Lowcoder query that proxies the LLM.
+ */
+
+export const AUTOMATOR_SYSTEM_PROMPT = `
+You are the Lowcoder Automator — an embedded assistant inside the Lowcoder
+visual app builder. Your job is to translate natural-language requests from a
+human builder into structured UI actions that the runtime will execute on the
+canvas.
+
+# How to respond
+
+The conversation history may include older user requests and older assistant
+explanations. Treat them as background only. The latest user message is the
+only instruction you should execute now.
+
+Do NOT replay, repeat, combine, or re-emit actions for previous turns. Previous
+changes are already represented in EDITOR_CONTEXT. Return only the actions
+needed for the latest user message.
+
+Use \`place_component\` / \`nest_component\` only when the latest user message
+clearly asks to create, add, place, or insert a new component. For follow-up
+edits like "make it", "set it", "change this", or "move the component", update
+the existing selected or named component from EDITOR_CONTEXT instead of creating
+a duplicate.
+
+\`nest_component\` also creates a NEW component. Do not use it to move an
+existing component into a container. If the user asks to put an existing
+component inside a container and there is no action for reparenting it, explain
+that limitation instead of creating a duplicate component.
+
+You have a tool called \`execute_automator_actions\`. Use it when you are
+ready to modify the canvas or supported bottom-panel resources. When the
+request is ambiguous or you need clarification, respond with plain text
+instead — do NOT call the tool with an empty actions array.
+
+If the user explicitly says "go ahead", "do it", "build it", "implement",
+or similar approval after a clarification round, call the tool.
+
+# How to use the live context
+
+The system message includes a JSON block titled "EDITOR_CONTEXT". It
+contains:
+ - canvas: grid columns, row height, max width
+ - selected: currently selected component name (may be null)
+ - components: list of UI components already on the canvas, each with
+ { name, type, layout: { x, y, w, h }, parent?, container? }
+ - queries: list of queries / temp states / transformers already defined
+ - theme: active theme id
+
+Use this context to:
+ - reuse component names that already exist
+ - place new components without overlapping existing ones
+ - reference existing queries instead of creating duplicates
+ - delete an existing query by name with \`delete_query\` when explicitly asked
+ - generate unique, descriptive component names
+
+# How to use the action catalog
+
+You will also see a JSON block titled "ACTIONS_CATALOG" listing the EXACT
+set of actions you may emit, with their required and optional fields. The
+"AUTOMATOR_COMPONENTS" block lists the curated Lowcoder component types you
+may place or nest. You MUST NOT use any action or component type that is not
+listed there. If something is not possible with the available Automator
+components, explain why in plain text.
+
+# Layout rules (short)
+
+- Canvas grid columns default to 24. Stay within \`canvas.gridColumns\`.
+- Stack components top-to-bottom by increasing \`y\`. Each unit of \`h\` is one
+ grid row (default ~8px).
+- For nested containers (modal, drawer, listView, grid, tabbedContainer),
+ use a flat empty \`container: {}\` and add children with \`nest_component\`
+ using \`parent_component_name = ".container"\`.
+- For regular containers, you may target \`container.body.0.view\`,
+ \`container.header\`, or \`container.footer\`.
+- Populate data-driven components (table, listView, grid) with 3+ realistic
+ sample rows. Stringify JSON for the \`data\` field of \`table\`.
+
+# Styling & layout edits
+
+There are TWO families of UI edits, and each has its own action:
+
+1. **\`set_properties\`** — top-level UI / behaviour properties exposed as
+ direct children of the component. Use this for things controlled by the
+ component's own controls (alignment, autoHeight, type, label, placeholder,
+ options, disabled, hidden, loading, placement, …). For each component the
+ \`layoutProperties\` field in AUTOMATOR_COMPONENTS lists the exact keys and
+ their allowed values.
+
+2. **\`set_style\`** — basic visual / CSS-like properties living inside the
+ component's style namespaces (\`style\`, \`labelStyle\`, \`inputFieldStyle\`,
+ \`headerStyle\`, \`bodyStyle\`, …). Always group values by explicit
+ namespace. Do NOT pass flat style keys.
+
+ Correct:
+ \`{ "style": { "background": "#1677ff", "text": "#ffffff" } }\`
+
+ Correct for an input label:
+ \`{ "labelStyle": { "label": "#1677ff", "textSize": "14px" } }\`
+
+ Incorrect:
+ \`{ "background": "#1677ff", "text": "#ffffff" }\`
+
+ For each component the \`styleProperties\` field in AUTOMATOR_COMPONENTS
+ lists which keys live in which namespace.
+
+ Common style-key vocabulary:
+ - text/colour: \`text\` (foreground), \`label\`, \`background\`, \`links\`, \`accent\`
+ - typography: \`textSize\`, \`textWeight\`, \`fontFamily\`, \`fontStyle\`,
+ \`textTransform\`, \`textDecoration\`, \`lineHeight\`
+ - box model: \`margin\`, \`padding\`, \`border\`, \`borderStyle\`,
+ \`borderWidth\`, \`radius\`, \`opacity\`, \`boxShadow\`, \`boxShadowColor\`
+ - input hints: \`placeholder\`, \`validate\`
+
+3. **\`align_component\`** — moves the COMPONENT to the left/center/right of
+ the canvas grid. It does NOT change text or content alignment inside the
+ component. For "center the text" / "right-align this label" use
+ \`set_properties\` with \`horizontalAlignment\`.
+
+# Common UI recipes
+
+- Center text inside a Text component:
+ set_properties { horizontalAlignment: "center" }
+- Larger heading text:
+ set_style { style: { textSize: "24px", textWeight: "700", lineHeight: "1.3" } }
+- Coloured primary button:
+ set_style { style: { background: "#1677ff", text: "#ffffff", radius: "8px",
+ padding: "8px 16px", textWeight: "600" } }
+- Accent input border + larger label:
+ set_style { inputFieldStyle: { border: "#1677ff",
+ borderWidth: "2px", radius: "6px" } }
+ set_style { labelStyle: { textSize: "14px", textWeight: "600" } }
+- Soft card with shadow:
+ set_style { style: { background: "#ffffff", radius: "12px", border: "#e5e7eb",
+ borderWidth: "1px", padding: "16px",
+ boxShadow: "0 4px 12px", boxShadowColor: "rgba(0,0,0,0.08)" } }
+- Hide / disable a component:
+ set_properties { hidden: true }
+ set_properties { disabled: true }
+
+# UX defaults
+
+- Apps that show data: title (text) → filters (input/dropdown) → primary
+ action (button) → table/listView. Edit/Create flows go in a modal/drawer.
+- Login/signup pages: image (logo) → title (text) → form on the canvas.
+ Avoid wrapping in modals unless explicitly requested.
+- Use the simplest component that satisfies the request. Avoid over-nesting.
+
+# Reminders
+
+- All field names match the Automator component instructions exactly
+ (snake_case where shown).
+- Every action MUST include \`action\` and (when relevant) \`component\` and
+ \`component_name\`.
+- Component names must be unique across the app. If reusing an existing
+ component referenced in EDITOR_CONTEXT, use its existing name.
+- Prefer the per-component \`layoutProperties\` / \`styleProperties\` listed in
+ AUTOMATOR_COMPONENTS over invented keys. If a property is not listed and you
+ are unsure it exists, ask the user instead of guessing.
+`.trim();
+
+/**
+ * Build the final system message string by combining the static prompt
+ * with the dynamic actions catalog and live editor snapshot.
+ *
+ * Kept as a single string so it works with any LLM (OpenAI, Claude,
+ * Ollama, etc.) — they all accept a single `system` message.
+ */
+export function composeSystemMessage(args: {
+ actionsCatalog: unknown;
+ automatorComponents: unknown;
+ editorContext: unknown;
+}): string {
+ const { actionsCatalog, automatorComponents, editorContext } = args;
+
+ return [
+ AUTOMATOR_SYSTEM_PROMPT,
+ "",
+ "ACTIONS_CATALOG:",
+ "```json",
+ JSON.stringify(actionsCatalog, null, 2),
+ "```",
+ "",
+ "AUTOMATOR_COMPONENTS:",
+ "```json",
+ JSON.stringify(automatorComponents, null, 2),
+ "```",
+ "",
+ "EDITOR_CONTEXT:",
+ "```json",
+ JSON.stringify(editorContext, null, 2),
+ "```",
+ ].join("\n");
+}
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/toolDefinitions.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/toolDefinitions.ts
new file mode 100644
index 0000000000..176360a65e
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/toolDefinitions.ts
@@ -0,0 +1,127 @@
+// client/packages/lowcoder/src/comps/comps/preLoadComp/actions/automator/toolDefinitions.ts
+
+/**
+ * Generates OpenAI-compatible tool (function-calling) definitions from
+ * the ACTIONS_CATALOG.
+ *
+ * Instead of asking the model to emit raw JSON inside its text content
+ * (fragile, needs custom parsing), we register a single tool —
+ * `execute_automator_actions` — that the model **calls** when it wants
+ * to mutate the canvas. The API guarantees `tool_calls[].function.arguments`
+ * is valid JSON, so no balanced-brace extraction or fence-stripping needed.
+ *
+ * When the model needs clarification it simply responds with text (no tool
+ * call), which naturally replaces the old `"actions": []` convention.
+ *
+ * The tool definition is provider-agnostic: OpenAI, Groq, Together, and
+ * Ollama all accept the same `tools` shape. Other providers can map this
+ * schema in the selected query/backend bridge.
+ */
+
+import { ACTIONS_CATALOG } from "./actionsCatalog";
+
+export interface OpenAIToolDefinition {
+ type: "function";
+ function: {
+ name: string;
+ description: string;
+ parameters: Record;
+ };
+}
+
+function buildActionItemSchema(componentTypes?: string[]): Record {
+ return {
+ type: "object",
+ properties: {
+ action: {
+ type: "string",
+ enum: ACTIONS_CATALOG.map((a) => a.action),
+ description: "The action to perform on the canvas.",
+ },
+ component: {
+ type: "string",
+ ...(componentTypes && componentTypes.length > 0 ? { enum: componentTypes } : {}),
+ description:
+ "Component type as registered in Lowcoder. Required for place_component and nest_component.",
+ },
+ component_name: {
+ type: "string",
+ description: "Unique name for the component on the canvas.",
+ },
+ query_name: {
+ type: "string",
+ description: "Name of the bottom-panel data query. Required for delete_query.",
+ },
+ parent_component_name: {
+ type: "string",
+ description:
+ "Parent container path for nest_component (e.g. 'form1.container.body.0.view').",
+ },
+ layout: {
+ type: "object",
+ properties: {
+ x: { type: "number", description: "Grid column position (0-based)." },
+ y: { type: "number", description: "Grid row position." },
+ w: { type: "number", description: "Width in grid columns." },
+ h: { type: "number", description: "Height in grid rows." },
+ },
+ description: "Grid layout position and size.",
+ },
+ action_parameters: {
+ type: "object",
+ description:
+ "Action-specific parameters (properties, styles, event config, etc.). Shape depends on the action and component type — see AUTOMATOR_COMPONENTS in the system prompt.",
+ },
+ },
+ required: ["action"],
+ };
+}
+
+/**
+ * Build the OpenAI `tools` array to pass alongside `messages` in the
+ * chat-completions request. Currently returns a single tool; the array
+ * wrapper keeps the door open for future per-action tools if we want
+ * tighter per-action schemas.
+ */
+export function buildToolDefinitions(componentTypes?: string[]): OpenAIToolDefinition[] {
+ const actionSummary = ACTIONS_CATALOG.map(
+ (a) => ` - ${a.action}: ${a.purpose}`
+ ).join("\n");
+
+ return [
+ {
+ type: "function",
+ function: {
+ name: "execute_automator_actions",
+ description: [
+ "Execute one or more Lowcoder Automator actions on the canvas.",
+ "Call this tool when you want to place, configure, style, move,",
+ "resize, delete, or otherwise modify components or queries in the app.",
+ "Do NOT call this tool when you need clarification — just respond",
+ "with text instead.",
+ "",
+ "Available actions:",
+ actionSummary,
+ ].join("\n"),
+ parameters: {
+ type: "object",
+ properties: {
+ explanation: {
+ type: "string",
+ description:
+ "Brief markdown summary of what you are doing and why.",
+ },
+ actions: {
+ type: "array",
+ description: "Ordered list of actions to execute on the canvas.",
+ items: buildActionItemSchema(componentTypes),
+ },
+ },
+ required: ["explanation", "actions"],
+ },
+ },
+ },
+ ];
+}
+
+export const TOOL_NAME = "execute_automator_actions";
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentConfiguration.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentConfiguration.ts
index 6653b7712b..5bd20ffc88 100644
--- a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentConfiguration.ts
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentConfiguration.ts
@@ -1,6 +1,6 @@
import { message } from "antd";
+import merge from "lodash/merge";
import { ActionConfig, ActionExecuteParams } from "../types";
-import { getEditorComponentInfo } from "../utils";
export const configureComponentAction: ActionConfig = {
key: 'configure-components',
@@ -20,51 +20,35 @@ export const configureComponentAction: ActionConfig = {
}
},
execute: async (params: ActionExecuteParams) => {
- const { actionValue: name, actionValue, actionPayload, editorState } = params;
- const { component_name: selectedEditorComponent, action_parameters: compProperties } = actionPayload;
- // const { onEvent, ...compProperties } = action_parameters;
- // const { name, ...otherProps } = actionPayload;
-
- try {
- // const componentInfo = getEditorComponentInfo(editorState, name);
-
- // if (!componentInfo) {
- // message.error(`Component "${selectedEditorComponent}" not found`);
- // return;
- // }
+ const { actionPayload, editorState } = params;
+ const componentName = actionPayload?.component_name || actionPayload?.component || params.selectedEditorComponent;
+ const compProperties = actionPayload?.action_parameters;
- // const { componentKey: parentKey, items } = componentInfo;
-
- // if (!parentKey) {
- // message.error(`Parent component "${selectedEditorComponent}" not found in layout`);
- // return;
- // }
+ if (!componentName) {
+ message.error("No component name provided for set_properties");
+ return;
+ }
- // const parentItem = items[parentKey];
- // if (!parentItem) {
- // message.error(`Parent component "${selectedEditorComponent}" not found in items`);
- // return;
- // }
- const parentItem = editorState.getUICompByName(selectedEditorComponent);
- if (!parentItem) {
- message.error(`Parent component "${selectedEditorComponent}" not found in items`);
+ if (!compProperties || typeof compProperties !== "object") {
+ message.error("No properties provided for set_properties");
+ return;
+ }
+
+ try {
+ const comp = editorState.getUICompByName(componentName);
+ if (!comp) {
+ message.error(`Component "${componentName}" not found`);
return;
}
- const itemComp = parentItem.children.comp;
- const itemData = itemComp.toJsonValue();
- const config = {
- ...itemData,
- ...compProperties
- };
+ const itemComp = comp.children.comp;
+ const config = merge({}, itemComp.toJsonValue(), compProperties);
itemComp.dispatchChangeValueAction(config);
- console.log('Configuring component:', selectedEditorComponent, 'with config:', config);
- message.info(`Configure action for component "${selectedEditorComponent}"`);
-
- // TODO: Implement actual configuration logic
+ message.success(`Properties updated on "${componentName}"`);
} catch (error) {
- message.error('Invalid configuration format');
+ console.error("Error setting properties:", error);
+ message.error("Failed to set component properties");
}
}
-};
\ No newline at end of file
+};
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentManagement.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentManagement.ts
index e6489646ee..587451ddee 100644
--- a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentManagement.ts
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentManagement.ts
@@ -87,7 +87,7 @@ export const addComponentAction: ActionConfig = {
comp: compInitialValue,
};
- const currentLayout = uiComp.children.comp.children.layout.getView();
+ const currentLayout = simpleContainer.children.layout.getView();
const layoutInfo = manifest?.layoutInfo || defaultLayout(selectedComponent as UICompType);
let itemPos = 0;
@@ -107,7 +107,7 @@ export const addComponentAction: ActionConfig = {
};
await getPromiseAfterDispatch(
- uiComp.children.comp.dispatch,
+ simpleContainer.dispatch,
wrapActionExtraInfo(
multiChangeAction({
layout: changeValueAction({
@@ -641,4 +641,4 @@ export const resizeComponentAction: ActionConfig = {
message.error('Failed to resize component. Please try again.');
}
}
-};
\ No newline at end of file
+};
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentStyling.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentStyling.ts
index dbe6297a0b..3eb5841b86 100644
--- a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentStyling.ts
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/componentStyling.ts
@@ -1,113 +1,57 @@
import { message } from "antd";
+import merge from "lodash/merge";
import { ActionConfig, ActionExecuteParams } from "../types";
-import { getEditorComponentInfo } from "../utils";
-
-// Fallback constant style object to apply
-// This wil be replaced by a JSON object returned by the AI model.
-const FALLBACK_STYLE_OBJECT = {
- fontSize: "10px",
- fontWeight: "500",
- color: "#333333",
- backgroundColor: "#ffffff",
- padding: "8px",
- borderRadius: "4px",
- border: "1px solid #ddd"
-};
export const applyStyleAction: ActionConfig = {
- key: 'apply-style',
- label: 'Apply style to component',
- category: 'styling',
+ key: "apply-style",
+ label: "Apply style to component",
+ category: "styling",
requiresEditorComponentSelection: true,
requiresStyle: true,
requiresInput: true,
- inputPlaceholder: 'Enter CSS styles (JSON format)',
- inputType: 'textarea',
+ inputPlaceholder: "Enter namespaced styles as JSON",
+ inputType: "json",
validation: (value: string) => {
- if (!value.trim()) return 'Styles are required'
- else return null;
+ if (!value.trim()) return "Styles are required";
+ try {
+ JSON.parse(value);
+ return null;
+ } catch {
+ return "Invalid JSON format";
+ }
},
execute: async (params: ActionExecuteParams) => {
- const { selectedEditorComponent, actionValue, editorState } = params;
-
- if (!selectedEditorComponent || !editorState) {
- message.error('Component and editor state are required');
+ const { actionPayload, editorState } = params;
+ const componentName =
+ actionPayload?.component_name || params.selectedEditorComponent;
+ const stylePatch = { ...(actionPayload?.action_parameters || {}) };
+ delete stylePatch.animationStyle;
+
+ if (!componentName) {
+ message.error("No component name provided for set_style");
+ return;
+ }
+
+ if (!editorState) {
+ message.error("Editor state is required");
return;
}
- // A fallback constant is currently used to style the component.
- // This is a temporary solution and will be removed once we integrate the AI model with the component styling.
try {
- let styleObject: Record = {};
- let usingFallback = false;
-
- try {
- if (typeof actionValue === 'string') {
- styleObject = JSON.parse(actionValue);
- } else {
- styleObject = actionValue;
- }
- } catch (e) {
- styleObject = FALLBACK_STYLE_OBJECT;
- usingFallback = true;
- }
-
- const comp = editorState.getUICompByName(selectedEditorComponent);
-
+ const comp = editorState.getUICompByName(componentName);
if (!comp) {
- message.error(`Component "${selectedEditorComponent}" not found`);
+ message.error(`Component "${componentName}" not found`);
return;
}
- const appliedStyles: string[] = [];
-
- for (const [styleKey, styleValue] of Object.entries(styleObject)) {
- try {
- const { children } = comp.children.comp;
- const compType = comp.children.compType.getView();
-
- // This method is used in LeftLayersContent.tsx to style the component.
- if (!children.style) {
- if (children[compType]?.children?.style?.children?.[styleKey]) {
- children[compType].children.style.children[styleKey].dispatchChangeValueAction(styleValue);
- appliedStyles.push(styleKey);
- } else if (children[compType]?.children?.[styleKey]) {
- children[compType].children[styleKey].dispatchChangeValueAction(styleValue);
- appliedStyles.push(styleKey);
- } else {
- console.warn(`Style property ${styleKey} not found in component ${selectedEditorComponent}`);
- }
- } else {
- if (children.style.children?.[styleKey]) {
- children.style.children[styleKey].dispatchChangeValueAction(styleValue);
- appliedStyles.push(styleKey);
- } else if (children.style[styleKey]) {
- children.style[styleKey].dispatchChangeValueAction(styleValue);
- appliedStyles.push(styleKey);
- } else {
- console.warn(`Style property ${styleKey} not found in style object`);
- }
- }
- } catch (error) {
- console.error(`Error applying style ${styleKey}:`, error);
- }
- }
+ const itemComp = comp.children.comp;
+ const config = merge({}, itemComp.toJsonValue(), stylePatch);
+ itemComp.dispatchChangeValueAction(config);
- if (appliedStyles.length > 0) {
- editorState.setSelectedCompNames(new Set([selectedEditorComponent]), "applyStyle");
-
- if (usingFallback) {
- message.success(`Applied ${appliedStyles.length} fallback style(s) to component "${selectedEditorComponent}": ${appliedStyles.join(', ')}`);
- } else {
- message.success(`Applied ${appliedStyles.length} style(s) to component "${selectedEditorComponent}": ${appliedStyles.join(', ')}`);
- }
- } else {
- message.warning('No styles were applied. Check if the component supports styling.');
- }
-
+ message.success(`Styles updated on "${componentName}"`);
} catch (error) {
- console.error('Error applying styles:', error);
- message.error('Failed to apply styles. Please try again.');
+ console.error("Error setting styles:", error);
+ message.error("Failed to set component styles");
}
- }
-};
\ No newline at end of file
+ },
+};
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/queryManagement.ts b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/queryManagement.ts
new file mode 100644
index 0000000000..52ad6dcaa2
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/actions/queryManagement.ts
@@ -0,0 +1,49 @@
+import { message } from "antd";
+import { ActionConfig, ActionExecuteParams } from "../types";
+
+function getQueryName(params: ActionExecuteParams): string {
+ const { actionPayload, actionValue, selectedEditorComponent } = params;
+ const actionParameters = actionPayload?.action_parameters || {};
+ return (
+ actionPayload?.query_name ||
+ actionPayload?.queryName ||
+ actionParameters.query_name ||
+ actionParameters.queryName ||
+ selectedEditorComponent ||
+ actionValue ||
+ ""
+ );
+}
+
+export const deleteQueryAction: ActionConfig = {
+ key: "delete-query",
+ label: "Delete query",
+ category: "query-management",
+ requiresInput: false,
+ execute: async (params: ActionExecuteParams) => {
+ const { editorState } = params;
+ const queryName = getQueryName(params);
+
+ if (!editorState) {
+ message.error("Editor state is required");
+ return;
+ }
+
+ if (!queryName) {
+ message.error("Query name is required");
+ return;
+ }
+
+ const queriesComp = editorState.getQueriesComp?.();
+ const queryExists = queriesComp
+ ?.getView?.()
+ ?.some((query: any) => query?.children?.name?.getView?.() === queryName);
+
+ if (!queryExists) {
+ message.error(`Query "${queryName}" not found`);
+ return;
+ }
+
+ queriesComp.delete(queryName);
+ },
+};
diff --git a/client/packages/lowcoder/src/comps/comps/preLoadComp/components.tsx b/client/packages/lowcoder/src/comps/comps/preLoadComp/components.tsx
index e9de70f2b5..1167d77013 100644
--- a/client/packages/lowcoder/src/comps/comps/preLoadComp/components.tsx
+++ b/client/packages/lowcoder/src/comps/comps/preLoadComp/components.tsx
@@ -176,4 +176,4 @@ export class GlobalCSSComp extends CodeTextControl implements RunAndClearable import("pages/editor/editorView"),
@@ -174,13 +175,16 @@ const RootView = React.memo((props: RootViewProps) => {
- {Object.keys(comp.children.queries.children).map((key) => (
- {comp.children.queries.children[key].getView()}
- ))}
-
-
-
-
+
+ {Object.keys(comp.children.queries.children).map((key) => (
+ {comp.children.queries.children[key].getView()}
+ ))}
+
+
+
+
+ {!readOnly && !isUserViewMode && !isModuleRoot && }
+
diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx
index e6dec7da0a..2e2dc73f26 100644
--- a/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx
+++ b/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx
@@ -509,6 +509,14 @@ export function compTablePropertyView
{comp.children.data.propertyView({
label: dataLabel,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "Table data",
+ fieldName: "data",
+ fieldDescription:
+ "JSON array of row objects for the Table component. Generate rows with consistent object keys so table columns can be created from the data shape.",
+ },
})}
)}
diff --git a/client/packages/lowcoder/src/comps/comps/tableLiteComp/tablePropertyView.tsx b/client/packages/lowcoder/src/comps/comps/tableLiteComp/tablePropertyView.tsx
index dbe4edcf88..acf68cc140 100644
--- a/client/packages/lowcoder/src/comps/comps/tableLiteComp/tablePropertyView.tsx
+++ b/client/packages/lowcoder/src/comps/comps/tableLiteComp/tablePropertyView.tsx
@@ -498,6 +498,14 @@ export function compTablePropertyView
{comp.children.data.propertyView({
label: dataLabel,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "Table Lite data",
+ fieldName: "data",
+ fieldDescription:
+ "JSON array of row objects for the Table Lite component. Generate rows with consistent object keys so table columns can be created from the data shape.",
+ },
})}
)}
diff --git a/client/packages/lowcoder/src/comps/comps/temporaryStateComp.tsx b/client/packages/lowcoder/src/comps/comps/temporaryStateComp.tsx
index 42fb8727d8..b00b3fe382 100644
--- a/client/packages/lowcoder/src/comps/comps/temporaryStateComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/temporaryStateComp.tsx
@@ -35,6 +35,14 @@ const TemporaryStateItemCompBase = new MultiCompBuilder(
label: trans("temporaryState.value"),
tooltip: trans("temporaryState.valueTooltip"),
placement: "bottom",
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: `${children.name.getView()}.value`,
+ fieldName: "value",
+ fieldDescription:
+ "Initial JSON value for this temporary state. Supports any JSON type (object, array, string, number, boolean, null).",
+ },
extraChildren: QueryTutorials.tempState && (
<>{trans("temporaryState.documentationText")}
{trans("temporaryState.docLink")}
diff --git a/client/packages/lowcoder/src/comps/comps/transformerListComp.tsx b/client/packages/lowcoder/src/comps/comps/transformerListComp.tsx
index 0bed05a865..b3124f7434 100644
--- a/client/packages/lowcoder/src/comps/comps/transformerListComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/transformerListComp.tsx
@@ -40,6 +40,14 @@ const TransformerItemCompBase = new MultiCompBuilder(
placement: "bottom",
styleName: "medium",
width: "100%",
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "javascript",
+ label: `${children.name.getView()}.script`,
+ fieldName: "script",
+ fieldDescription:
+ "Transformer body: a JavaScript function that receives upstream data and returns the transformed value.",
+ },
})}
{QueryTutorials.transformer && (
<>{trans("transformer.documentationText")}
diff --git a/client/packages/lowcoder/src/comps/comps/treeComp/treeUtils.tsx b/client/packages/lowcoder/src/comps/comps/treeComp/treeUtils.tsx
index 4648a4cfaa..edf158e6bf 100644
--- a/client/packages/lowcoder/src/comps/comps/treeComp/treeUtils.tsx
+++ b/client/packages/lowcoder/src/comps/comps/treeComp/treeUtils.tsx
@@ -93,7 +93,18 @@ export function useTree(props: RecordConstructorToView>;
export const treeDataPropertyView = (children: TreeCommonComp) =>
- children.treeData.propertyView({ label: trans("tree.treeData"), tooltip: treeDataTooltip });
+ children.treeData.propertyView({
+ label: trans("tree.treeData"),
+ tooltip: treeDataTooltip,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "Tree data",
+ fieldName: "treeData",
+ fieldDescription:
+ "JSON array of tree nodes. Each node must include label and value, and can include children, disabled, selectable, checkable, disableCheckbox, and isLeaf.",
+ },
+ });
export const valuePropertyView = (children: TreeCommonComp) => {
return children.defaultValue.propertyView({ label: trans("tree.value") });
diff --git a/client/packages/lowcoder/src/comps/controls/actionSelector/runScript.tsx b/client/packages/lowcoder/src/comps/controls/actionSelector/runScript.tsx
index 082477c18c..b60362726c 100644
--- a/client/packages/lowcoder/src/comps/controls/actionSelector/runScript.tsx
+++ b/client/packages/lowcoder/src/comps/controls/actionSelector/runScript.tsx
@@ -31,6 +31,14 @@ export class RunScriptAction extends RunScriptTmpAction {
layout: "vertical",
styleName: "medium",
showLineNum: false,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "javascript",
+ label: "Event handler script",
+ fieldName: "script",
+ fieldDescription:
+ "JavaScript to run when this event fires. Has access to all Lowcoder component and query references.",
+ },
})}
);
diff --git a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx
index 496c7beada..8c1d3bc416 100644
--- a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx
@@ -300,15 +300,37 @@ export function mapOptionsControl(
}
)
.setControlItemData({ filterText: label })
- .setPropertyViewFn((children) => (
- <>
- {children.data.propertyView({ label })}
-
- {children.mapData.getPropertyView()}
- {OptionTip}
-
- >
- ))
+ .setPropertyViewFn((children) => {
+ const mapSchema = (() => {
+ try {
+ const json = children.mapData.toJsonValue();
+ const keys = Object.keys(json as Record);
+ return keys.length > 0 ? keys.join(", ") : "label, value";
+ } catch {
+ return "label, value";
+ }
+ })();
+
+ return (
+ <>
+ {children.data.propertyView({
+ label,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "Map mode data",
+ fieldName: "data",
+ fieldDescription:
+ `JSON array of objects. Each object is mapped to a component option via {{item}}. The mapped fields are: ${mapSchema}. Generate rows with keys matching those fields.`,
+ },
+ })}
+
+ {children.mapData.getPropertyView()}
+ {OptionTip}
+
+ >
+ );
+ })
.build();
return class extends TmpOptionControl {
diff --git a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx
index 8edfee318f..ff70e68a46 100644
--- a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx
+++ b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx
@@ -2530,9 +2530,7 @@ export const ChatMessagesStyle = [
{
name: "userMessageBackground",
label: trans("style.userMessageBackground"),
- depTheme: "primary",
- depType: DEP_TYPE.SELF,
- transformer: toSelf,
+ color: "#e5e7eb",
},
{
name: "userMessageText",
@@ -2556,23 +2554,10 @@ export const ChatMessagesStyle = [
] as const;
export const ChatInputStyle = [
- {
- name: "inputBackground",
- label: trans("style.inputBackground"),
- color: "#ffffff",
- },
{
name: "inputText",
label: trans("style.inputText"),
- depName: "inputBackground",
- depType: DEP_TYPE.CONTRAST_TEXT,
- transformer: contrastText,
- },
- {
- name: "inputBorder",
- label: trans("style.inputBorder"),
- depName: "inputBackground",
- transformer: backgroundToBorder,
+ color: "#1f2937",
},
] as const;
diff --git a/client/packages/lowcoder/src/comps/queries/esQuery.tsx b/client/packages/lowcoder/src/comps/queries/esQuery.tsx
index 902332bbdb..a9146119de 100644
--- a/client/packages/lowcoder/src/comps/queries/esQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/esQuery.tsx
@@ -278,6 +278,15 @@ const EsQueryPropertyView = (props: {
label: "Body",
placement: "bottom",
styleName: "medium",
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "Elasticsearch request body",
+ queryType: "Elasticsearch",
+ fieldName: "dsl",
+ fieldDescription:
+ "Elasticsearch Query DSL as JSON. Use query, aggs, sort, size, _source, and other Elasticsearch request body parameters.",
+ },
})}
>
);
diff --git a/client/packages/lowcoder/src/comps/queries/httpQuery/alasqlQuery.tsx b/client/packages/lowcoder/src/comps/queries/httpQuery/alasqlQuery.tsx
index 0c2a4bf07e..3a0fe8a064 100644
--- a/client/packages/lowcoder/src/comps/queries/httpQuery/alasqlQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/httpQuery/alasqlQuery.tsx
@@ -130,6 +130,15 @@ const PropertyView = (props: { comp: InstanceType; datasourc
styleName: "medium",
language: "sql",
enableMetaCompletion: true,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "sql",
+ label: "AlaSQL query",
+ queryType: "AlaSQL",
+ fieldName: "sql",
+ fieldDescription:
+ "AlaSQL SQL query for in-browser data. Supports SELECT, INSERT, UPDATE, DELETE, JOIN, and AlaSQL-specific extensions.",
+ },
})}
diff --git a/client/packages/lowcoder/src/comps/queries/httpQuery/graphqlQuery.tsx b/client/packages/lowcoder/src/comps/queries/httpQuery/graphqlQuery.tsx
index 2ccba05399..479eab163b 100644
--- a/client/packages/lowcoder/src/comps/queries/httpQuery/graphqlQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/httpQuery/graphqlQuery.tsx
@@ -3,7 +3,7 @@ import { Input } from "components/Input";
import { KeyValueList } from "components/keyValueList";
import { QueryConfigItemWrapper, QueryConfigLabel, QueryConfigWrapper } from "components/query";
import { simpleMultiComp } from "comps/generators/multi";
-import { ReactNode } from "react";
+import { ReactNode, useContext } from "react";
import { JSONValue } from "../../../util/jsonTypes";
import { keyValueListControl } from "../../controls/keyValueListControl";
import { ParamsJsonControl, ParamsStringControl } from "../../controls/paramsControl";
@@ -16,6 +16,7 @@ import {
HttpPathPropertyView,
} from "./httpQueryConstants";
import { SimpleNameComp } from "@lowcoder-ee/comps/comps/simpleNameComp";
+import { CompNameContext } from "comps/editorState";
interface VariablesControlParams {
// variables: string[]; todo support parse variables
@@ -108,6 +109,7 @@ function parseVariables(value: JSONValue): string[] {
const PropertyView = (props: { comp: InstanceType; datasourceId: string }) => {
const { comp } = props;
const { children } = comp;
+ const queryName = useContext(CompNameContext);
return (
<>
@@ -119,6 +121,17 @@ const PropertyView = (props: { comp: InstanceType; datasour
{children.body.propertyView({
styleName: "medium",
width: "100%",
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "component-field",
+ label: queryName ? `${queryName}.graphql` : "GraphQL query",
+ queryType: "GRAPHQL",
+ queryName,
+ fieldName: "body",
+ fieldDescription:
+ "GraphQL query document. Help write, explain, or improve the GraphQL operation and variables usage.",
+ targetId: queryName ? `${queryName}.graphql` : undefined,
+ },
})}
diff --git a/client/packages/lowcoder/src/comps/queries/httpQuery/httpQuery.tsx b/client/packages/lowcoder/src/comps/queries/httpQuery/httpQuery.tsx
index 2fd50921eb..4f45ff77b6 100644
--- a/client/packages/lowcoder/src/comps/queries/httpQuery/httpQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/httpQuery/httpQuery.tsx
@@ -1,9 +1,11 @@
import { Dropdown, ValueFromOption } from "components/Dropdown";
import { QueryConfigItemWrapper, QueryConfigLabel, QueryConfigWrapper } from "components/query";
+import { CompNameContext } from "comps/editorState";
import { valueComp, withDefault } from "comps/generators";
import { trans } from "i18n";
import { includes } from "lodash";
import { CompAction, MultiBaseComp } from "lowcoder-core";
+import { useContext } from "react";
import { keyValueListControl } from "../../controls/keyValueListControl";
import { ParamsJsonControl, ParamsStringControl } from "../../controls/paramsControl";
import { withTypeAndChildrenAbstract } from "../../generators/withType";
@@ -114,8 +116,11 @@ type ChildrenType = InstanceType extends MultiBaseComp {
- switch (children.bodyType.getView() as BodyTypeValue) {
+const showBodyConfig = (children: ChildrenType, queryName?: string) => {
+ const bodyType = children.bodyType.getView() as BodyTypeValue;
+ const method = children.httpMethod.getView();
+
+ switch (bodyType) {
case "application/x-www-form-urlencoded":
return children.bodyFormData.propertyView({});
case "multipart/form-data":
@@ -129,7 +134,21 @@ const showBodyConfig = (children: ChildrenType) => {
});
case "application/json":
case "text/plain":
- return children.body.propertyView({ styleName: "medium", width: "100%" });
+ return children.body.propertyView({
+ styleName: "medium",
+ width: "100%",
+ language: bodyType === "application/json" ? "json" : undefined,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: bodyType === "application/json" ? "json" : "component-field",
+ label: queryName ? `${queryName}.body` : "HTTP request body",
+ queryType: "HTTP",
+ queryName,
+ fieldName: "body",
+ fieldDescription: `HTTP ${method} request body (${bodyType}). Help generate, explain, or improve the request payload.`,
+ targetId: queryName ? `${queryName}.body` : undefined,
+ },
+ });
default:
return <>>;
}
@@ -144,6 +163,7 @@ const HttpQueryPropertyView = (props: {
}) => {
const { comp, supportHttpMethods, supportBodyTypes } = props;
const { children, dispatch } = comp;
+ const queryName = useContext(CompNameContext);
return (
<>
@@ -201,7 +221,7 @@ const HttpQueryPropertyView = (props: {
- {showBodyConfig(children)}
+ {showBodyConfig(children, queryName)}
>
);
diff --git a/client/packages/lowcoder/src/comps/queries/httpQuery/sseHttpQuery.tsx b/client/packages/lowcoder/src/comps/queries/httpQuery/sseHttpQuery.tsx
index 2271f582ef..e210f07d80 100644
--- a/client/packages/lowcoder/src/comps/queries/httpQuery/sseHttpQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/httpQuery/sseHttpQuery.tsx
@@ -1,10 +1,12 @@
// SSEHTTPQUERY.tsx
-import { Dropdown, ValueFromOption } from "components/Dropdown";
-import { QueryConfigItemWrapper, QueryConfigLabel, QueryConfigWrapper } from "components/query";
-import { valueComp, withDefault } from "comps/generators";
-import { trans } from "i18n";
-import { includes } from "lodash";
-import { CompAction, MultiBaseComp } from "lowcoder-core";
+import { Dropdown, ValueFromOption } from "components/Dropdown";
+import { QueryConfigItemWrapper, QueryConfigLabel, QueryConfigWrapper } from "components/query";
+import { CompNameContext } from "comps/editorState";
+import { valueComp, withDefault } from "comps/generators";
+import { trans } from "i18n";
+import { includes } from "lodash";
+import { CompAction, MultiBaseComp } from "lowcoder-core";
+import { useContext } from "react";
import { keyValueListControl } from "../../controls/keyValueListControl";
import { ParamsJsonControl, ParamsStringControl } from "../../controls/paramsControl";
import { withTypeAndChildrenAbstract } from "../../generators/withType";
@@ -115,8 +117,11 @@ type ChildrenType = InstanceType extends MultiBaseComp {
- switch (children.bodyType.getView() as BodyTypeValue) {
+const showBodyConfig = (children: ChildrenType, queryName?: string) => {
+ const bodyType = children.bodyType.getView() as BodyTypeValue;
+ const method = children.httpMethod.getView();
+
+ switch (bodyType) {
case "application/x-www-form-urlencoded":
return children.bodyFormData.propertyView({});
case "multipart/form-data":
@@ -128,9 +133,23 @@ const showBodyConfig = (children: ChildrenType) => {
example: "{{ {data: file1.value[0], name: file1.files[0].name} }}",
}),
});
- case "application/json":
- case "text/plain":
- return children.body.propertyView({ styleName: "medium", width: "100%" });
+ case "application/json":
+ case "text/plain":
+ return children.body.propertyView({
+ styleName: "medium",
+ width: "100%",
+ language: bodyType === "application/json" ? "json" : undefined,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: bodyType === "application/json" ? "json" : "component-field",
+ label: queryName ? `${queryName}.body` : "SSE HTTP request body",
+ queryType: "SSE_HTTP",
+ queryName,
+ fieldName: "body",
+ fieldDescription: `Server-Sent Events HTTP ${method} request body (${bodyType}). Help generate, explain, or improve the streaming request payload.`,
+ targetId: queryName ? `${queryName}.body` : undefined,
+ },
+ });
default:
return <>>;
}
@@ -143,8 +162,9 @@ const SseHttpQueryPropertyView = (props: {
supportHttpMethods?: HttpMethodValue[];
supportBodyTypes?: BodyTypeValue[];
}) => {
- const { comp, supportHttpMethods, supportBodyTypes } = props;
- const { children, dispatch } = comp;
+ const { comp, supportHttpMethods, supportBodyTypes } = props;
+ const { children, dispatch } = comp;
+ const queryName = useContext(CompNameContext);
return (
<>
@@ -204,10 +224,10 @@ const SseHttpQueryPropertyView = (props: {
}}
/>
-
-
- {showBodyConfig(children)}
-
+
+
+ {showBodyConfig(children, queryName)}
+
Streaming Options
@@ -219,4 +239,4 @@ const SseHttpQueryPropertyView = (props: {
>
);
-};
\ No newline at end of file
+};
diff --git a/client/packages/lowcoder/src/comps/queries/jsQuery.tsx b/client/packages/lowcoder/src/comps/queries/jsQuery.tsx
index 24c3689157..c92f18004a 100644
--- a/client/packages/lowcoder/src/comps/queries/jsQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/jsQuery.tsx
@@ -6,6 +6,8 @@ import { DocLink } from "lowcoder-design";
import { getGlobalSettings } from "comps/utils/globalSettings";
import { trans } from "i18n";
import { QUERY_EXECUTION_ERROR, QUERY_EXECUTION_OK } from "../../constants/queryConstants";
+import { useContext } from "react";
+import { CompNameContext } from "comps/editorState";
export const JSQuery = (function () {
const childrenMap = {
@@ -34,19 +36,31 @@ export const JSQuery = (function () {
}
};
})
- .setPropertyViewFn((children) => {
- return (
- <>
- {children.script.propertyView({
- placeholder: "return 1 + 1",
- placement: "bottom",
- styleName: "medium",
- })}
- {QueryTutorials.js && (
- {trans("query.jsQueryDocLink")}
- )}
- >
- );
- })
+ .setPropertyViewFn((children) => )
.build();
})();
+
+function JSQueryPropertyView({ children }: { children: any }) {
+ const queryName = useContext(CompNameContext);
+ return (
+ <>
+ {children.script.propertyView({
+ placeholder: "return 1 + 1",
+ placement: "bottom",
+ styleName: "medium",
+ language: "javascript",
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "javascript",
+ label: queryName ? `${queryName}.script` : "JS Query script",
+ queryType: "JS",
+ queryName,
+ targetId: queryName ? `${queryName}.script` : undefined,
+ },
+ })}
+ {QueryTutorials.js && (
+ {trans("query.jsQueryDocLink")}
+ )}
+ >
+ );
+}
diff --git a/client/packages/lowcoder/src/comps/queries/mongoQuery.tsx b/client/packages/lowcoder/src/comps/queries/mongoQuery.tsx
index 6a2b3612db..3492141df0 100644
--- a/client/packages/lowcoder/src/comps/queries/mongoQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/mongoQuery.tsx
@@ -42,6 +42,15 @@ const QueryField = withPropertyViewFn(ParamsJsonControl, (comp) =>
}`,
styleName: "medium",
enableMetaCompletion: true,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "MongoDB query filter",
+ queryType: "MongoDB",
+ fieldName: "query",
+ fieldDescription:
+ "MongoDB query/filter document. Use MongoDB query operators ($gte, $in, $regex, etc.) to match documents.",
+ },
})
);
@@ -99,6 +108,15 @@ const CommandMap: Record<
}]`,
styleName: "medium",
enableMetaCompletion: true,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "MongoDB insert documents",
+ queryType: "MongoDB",
+ fieldName: "documents",
+ fieldDescription:
+ "JSON array of documents to insert into the collection.",
+ },
})
),
}),
@@ -113,6 +131,15 @@ const CommandMap: Record<
}`,
styleName: "medium",
enableMetaCompletion: true,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "MongoDB update operators",
+ queryType: "MongoDB",
+ fieldName: "update",
+ fieldDescription:
+ "MongoDB update document. Use update operators ($set, $inc, $push, $unset, etc.) to modify matched documents.",
+ },
})
),
limit: LimitDropdownField,
@@ -146,6 +173,15 @@ const CommandMap: Record<
]`,
styleName: "medium",
enableMetaCompletion: true,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "MongoDB aggregation pipeline",
+ queryType: "MongoDB",
+ fieldName: "arrayPipelines",
+ fieldDescription:
+ "MongoDB aggregation pipeline stages as a JSON array. Use stages like $match, $group, $sort, $project, $lookup, $unwind, etc.",
+ },
})
),
limit: LimitInputField,
@@ -161,6 +197,15 @@ const CommandMap: Record<
]`,
styleName: "medium",
enableMetaCompletion: true,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "json",
+ label: "MongoDB raw command",
+ queryType: "MongoDB",
+ fieldName: "command",
+ fieldDescription:
+ "Raw MongoDB command as JSON. Supports any valid MongoDB database command.",
+ },
})
),
}),
diff --git a/client/packages/lowcoder/src/comps/queries/redisQuery.tsx b/client/packages/lowcoder/src/comps/queries/redisQuery.tsx
index 530553c7ef..0d8b3b142b 100644
--- a/client/packages/lowcoder/src/comps/queries/redisQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/redisQuery.tsx
@@ -149,6 +149,15 @@ const CommandMap: Record<
placement: "bottom",
placeholder: `INCR counter`,
styleName: "medium",
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "component-field",
+ label: "Redis raw command",
+ queryType: "Redis",
+ fieldName: "command",
+ fieldDescription:
+ "Raw Redis command string (e.g. GET key, SET key value, INCR counter, HGETALL hash). Supports any Redis CLI command.",
+ },
})
),
}),
diff --git a/client/packages/lowcoder/src/comps/queries/sqlQuery/SQLQuery.tsx b/client/packages/lowcoder/src/comps/queries/sqlQuery/SQLQuery.tsx
index b451cd0e1d..6993f9e099 100644
--- a/client/packages/lowcoder/src/comps/queries/sqlQuery/SQLQuery.tsx
+++ b/client/packages/lowcoder/src/comps/queries/sqlQuery/SQLQuery.tsx
@@ -23,6 +23,7 @@ import { ColumnNameDropdown } from "./columnNameDropdown";
import React, { useContext } from "react";
import { QueryContext } from "util/context/QueryContext";
import SupaDemoDisplay from "comps/utils/supademoDisplay";
+import { CompNameContext } from "comps/editorState";
const AllowMultiModifyComp = withPropertyViewFn(BoolPureControl, (comp) =>
comp.propertyView({
@@ -172,6 +173,7 @@ const regexp = new RegExp("(\\s|^)(update|insert|delete|drop)(\\s|$)", "i");
const SQLQueryPropertyView = (props: { comp: InstanceType }) => {
const { children } = props.comp;
const context = useContext(QueryContext);
+ const queryName = useContext(CompNameContext);
return (
<>
@@ -182,6 +184,15 @@ const SQLQueryPropertyView = (props: { comp: InstanceType }) =>
styleName: "medium",
language: "sql",
enableMetaCompletion: true,
+ enableAIHelp: true,
+ aiHelp: {
+ targetKind: "sql",
+ label: queryName ? `${queryName}.sql` : "SQL Query",
+ datasourceId: context?.datasourceId,
+ queryType: context?.resourceType,
+ queryName,
+ targetId: queryName ? `${queryName}.sql` : undefined,
+ },
})
) : (
<>
diff --git a/client/packages/lowcoder/src/pages/editor/bottom/BottomPanel.tsx b/client/packages/lowcoder/src/pages/editor/bottom/BottomPanel.tsx
index a7693b7bc8..ea45ffa359 100644
--- a/client/packages/lowcoder/src/pages/editor/bottom/BottomPanel.tsx
+++ b/client/packages/lowcoder/src/pages/editor/bottom/BottomPanel.tsx
@@ -1,20 +1,27 @@
import { BottomContent } from "pages/editor/bottom/BottomContent";
-import { ResizableBox, ResizeCallbackData } from "react-resizable";
-import styled from "styled-components";
-import * as React from "react";
-import { useMemo, useState } from "react";
-import { getPanelStyle, savePanelStyle } from "util/localStorageUtil";
+import { ResizableBox, ResizeCallbackData } from "react-resizable";
+import styled from "styled-components";
+import * as React from "react";
+import { useContext, useEffect, useMemo, useState } from "react";
+import {
+ getPanelStyle,
+ getSelectedAIQueryName,
+ savePanelStyle,
+ saveSelectedAIQueryName,
+} from "util/localStorageUtil";
import { BottomResultPanel } from "../../../components/resultPanel/BottomResultPanel";
import { AppState } from "../../../redux/reducers";
import { getUser } from "../../../redux/selectors/usersSelectors";
-import { connect } from "react-redux";
-import { Layers } from "constants/Layers";
-import Flex from "antd/es/flex";
-import type { MenuProps } from 'antd/es/menu';
-import { BuildOutlined, DatabaseOutlined } from "@ant-design/icons";
-import Menu from "antd/es/menu/menu";
-import { AIGenerate } from "lowcoder-design";
-import { ChatPanel } from "@lowcoder-ee/comps/comps/chatComp/components/ChatPanel";
+import { connect } from "react-redux";
+import { Layers } from "constants/Layers";
+import Flex from "antd/es/flex";
+import type { MenuProps } from 'antd/es/menu';
+import { DatabaseOutlined } from "@ant-design/icons";
+import Menu from "antd/es/menu/menu";
+import Select from "antd/es/select";
+import { AIGenerate } from "lowcoder-design";
+import { ChatPanel } from "@lowcoder-ee/comps/comps/chatComp/components/ChatPanel";
+import { EditorContext } from "comps/editorState";
type MenuItem = Required['items'][number];
@@ -34,11 +41,12 @@ const StyledResizableBox = styled(ResizableBox)`
}
`;
-const StyledMenu = styled(Menu)`
- width: 40px;
- padding: 6px 0;
-
- .ant-menu-item {
+const StyledMenu = styled(Menu)`
+ flex: 0 0 40px;
+ width: 40px;
+ padding: 6px 0;
+
+ .ant-menu-item {
height: 30px;
line-height: 30px;
}
@@ -60,7 +68,31 @@ const ChatTitle = styled.h3`
color: #222222;
`;
-const preventDefault = (e: any) => {
+const QuerySelectorWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 12px;
+`;
+
+const QueryLabel = styled.span`
+ font-size: 12px;
+ color: #8b8fa3;
+ white-space: nowrap;
+`;
+
+const PanelBody = styled.div`
+ display: flex;
+ height: 100%;
+ min-width: 0;
+`;
+
+const PanelContent = styled.div`
+ flex: 1 1 0;
+ min-width: 0;
+ height: 100%;
+`;
+
+const preventDefault = (e: any) => {
e.preventDefault();
};
@@ -81,9 +113,26 @@ function Bottom(props: any) {
setBottomHeight(data.size.height);
removeListener();
};
+
+ const [bottomHeight, setBottomHeight] = useState(panelStyle.bottom.h);
+ const [currentOption, setCurrentOption] = useState("data");
+ const [selectedQuery, setSelectedQuery] = useState(() => getSelectedAIQueryName());
+
+ const editorState = useContext(EditorContext);
+
+ useEffect(() => {
+ if (currentOption === "ai") {
+ setSelectedQuery(getSelectedAIQueryName());
+ }
+ }, [currentOption]);
- const [bottomHeight, setBottomHeight] = useState(panelStyle.bottom.h);
- const [currentOption, setCurrentOption] = useState("data");
+ const queryOptions = useMemo(() => {
+ if (!editorState) return [];
+ return editorState.queryCompInfoList().map((info) => ({
+ label: info.name,
+ value: info.name,
+ }));
+ }, [editorState]);
const items: MenuItem[] = [
{ key: 'data', icon: , label: 'Data Queries' },
@@ -98,36 +147,54 @@ function Bottom(props: any) {
height={panelStyle.bottom.h}
resizeHandles={["n"]}
minConstraints={[680, 285]}
- maxConstraints={[Infinity, clientHeight - 48 - 40]} // - app_header - right_header
+ maxConstraints={[Infinity, clientHeight - 48 - 40]}
onResizeStart={addListener}
onResizeStop={resizeStop}
>
-
-
+ {
- setCurrentOption(key);
- }}
- />
- { currentOption === "data" && }
- { currentOption === "ai" && (
-
-
- Lowcoder AI Assistant
-
-
-
- )}
-
-
+ setCurrentOption(key);
+ }}
+ />
+
+ {currentOption === "data" ? (
+
+ ) : (
+
+
+ Lowcoder Automator
+
+ Query:
+ {
+ const nextQuery = value || "";
+ setSelectedQuery(nextQuery);
+ saveSelectedAIQueryName(nextQuery);
+ }}
+ options={queryOptions}
+ style={{ width: 200 }}
+ size="small"
+ />
+
+
+
+
+ )}
+
+
+
>
);
}
diff --git a/client/packages/lowcoder/src/util/localStorageUtil.ts b/client/packages/lowcoder/src/util/localStorageUtil.ts
index 0710fc8d1f..6522733452 100644
--- a/client/packages/lowcoder/src/util/localStorageUtil.ts
+++ b/client/packages/lowcoder/src/util/localStorageUtil.ts
@@ -121,3 +121,17 @@ export function getLocalThemeId() {
export function setLocalThemeId(themeId: string) {
return localStorage.setItem("theme_id", themeId);
}
+
+const AIQueryStorageKey = "lc_ai_selected_query";
+
+export function getSelectedAIQueryName(): string {
+ return localStorage.getItem(AIQueryStorageKey) ?? "";
+}
+
+export function saveSelectedAIQueryName(queryName: string) {
+ if (queryName) {
+ localStorage.setItem(AIQueryStorageKey, queryName);
+ } else {
+ localStorage.removeItem(AIQueryStorageKey);
+ }
+}
diff --git a/client/yarn.lock b/client/yarn.lock
index 1a6c6f81ac..7e563bc56e 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -84,7 +84,7 @@ __metadata:
languageName: node
linkType: hard
-"@ai-sdk/provider@npm:1.1.3, @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:
@@ -93,7 +93,7 @@ __metadata:
languageName: node
linkType: hard
-"@ai-sdk/react@npm:*, @ai-sdk/react@npm:1.2.12":
+"@ai-sdk/react@npm:1.2.12":
version: 1.2.12
resolution: "@ai-sdk/react@npm:1.2.12"
dependencies:
@@ -111,7 +111,7 @@ __metadata:
languageName: node
linkType: hard
-"@ai-sdk/ui-utils@npm:*, @ai-sdk/ui-utils@npm:1.2.11":
+"@ai-sdk/ui-utils@npm:1.2.11":
version: 1.2.11
resolution: "@ai-sdk/ui-utils@npm:1.2.11"
dependencies:
@@ -222,110 +222,114 @@ __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"
+"@assistant-ui/core@npm:^0.2.2":
+ version: 0.2.2
+ resolution: "@assistant-ui/core@npm:0.2.2"
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
+ assistant-stream: ^0.3.14
+ nanoid: ^5.1.11
peerDependencies:
- "@assistant-ui/react": ^0.10.24
+ "@assistant-ui/store": ^0.2.10
+ "@assistant-ui/tap": ^0.5.11
"@types/react": "*"
- react: ^18 || ^19 || ^19.0.0-rc
+ assistant-cloud: ^0.1.27
+ react: ^18 || ^19
+ zustand: ^5.0.11
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":
+ assistant-cloud:
optional: true
- "@types/react-dom":
+ react:
+ optional: true
+ zustand:
optional: true
- checksum: 6d533f26e533b7d177689ce3aa8d6b6fe0077d1f77d9f5b3707bae09bf084b7ba629a242e92fdf7f99328edc7521f3d0845587e961f19bce55c820ece7bba828
+ checksum: 33e03587bf0cd511eef185706c66ef14475f32a9dca04eeab328214ea5d64ed9792d6270cc36a54fef77070b80a04e8fbb59b6fad6eb28e89113ea5f21ac6394
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"
+"@assistant-ui/react-markdown@npm:^0.14.0":
+ version: 0.14.0
+ resolution: "@assistant-ui/react-markdown@npm:0.14.0"
dependencies:
- "@radix-ui/react-primitive": ^2.1.3
+ "@radix-ui/react-primitive": ^2.1.4
"@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
+ "@assistant-ui/react": ^0.14.0
"@types/react": "*"
- react: ^18 || ^19 || ^19.0.0-rc
+ react: ^18 || ^19
peerDependenciesMeta:
"@types/react":
optional: true
- checksum: 85991a87a04f34c68b0290dfa6061e760b90bdcef694f7b938138b34836eaafe7dd0159faa54e262c227007dd8b912e5cab53329a99c26561a91a0a17d14a2ac
+ checksum: 71ec17e87b3a254382a5adba2668f2e3c71e6a1f1a8dd36e33349d112ae84470447a300c6c7851a378a8079bb99b67f70b75530c3c7ebd8e6d11588d643c30ec
languageName: node
linkType: hard
-"@assistant-ui/react@npm:^0.10.24":
- version: 0.10.24
- resolution: "@assistant-ui/react@npm:0.10.24"
+"@assistant-ui/react@npm:^0.14.5":
+ version: 0.14.5
+ resolution: "@assistant-ui/react@npm:0.14.5"
dependencies:
- "@radix-ui/primitive": ^1.1.2
+ "@assistant-ui/core": ^0.2.2
+ "@assistant-ui/store": ^0.2.10
+ "@assistant-ui/tap": ^0.5.11
+ "@radix-ui/primitive": ^1.1.3
"@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-context": ^1.1.3
+ "@radix-ui/react-primitive": ^2.1.4
"@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
+ assistant-cloud: ^0.1.27
+ assistant-stream: ^0.3.14
+ nanoid: ^5.1.11
+ radix-ui: ^1.4.3
react-textarea-autosize: ^8.5.9
- zod: ^3.25.64
- zustand: ^5.0.5
+ safe-content-frame: ^0.0.19
+ zod: ^4.4.3
+ zustand: ^5.0.13
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
- react: ^18 || ^19 || ^19.0.0-rc
- react-dom: ^18 || ^19 || ^19.0.0-rc
+ react: ^18 || ^19
+ react-dom: ^18 || ^19
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
- checksum: 4f9483464ac24caf7271161683dc8a8ab17c2263b16ebb5f1a4da75ea1ecda93048f93c7b06c90202e76215d72beb190408d3833ab54e4d19bdbd0ee2523a5f3
+ checksum: 0397ea04d71c720b43035b511b32dd7a7da6ec20b1466978f3fed4f28fc005f292b8778dac6ed127c2117232bab8e4f100d9d1449f226c48f41d4f578fc26761
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
+"@assistant-ui/store@npm:^0.2.10":
+ version: 0.2.10
+ resolution: "@assistant-ui/store@npm:0.2.10"
+ dependencies:
+ use-effect-event: ^2.0.3
+ peerDependencies:
+ "@assistant-ui/tap": ^0.5.11
+ "@types/react": "*"
+ react: ^18 || ^19
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: c67518d6ceebe255f78eb90bab98586efd4f731508cf2b06ae52e7b1e0cea91547c59da804fdafc959a965edffd79be9fd519f5d4daf6c00d1e30796f66360db
+ languageName: node
+ linkType: hard
+
+"@assistant-ui/tap@npm:^0.5.11":
+ version: 0.5.11
+ resolution: "@assistant-ui/tap@npm:0.5.11"
+ peerDependencies:
+ "@types/react": "*"
+ react: ^18 || ^19
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ react:
+ optional: true
+ checksum: b1f2d957b1ebc5e1e73cfaca81c33830d157db460c79129b44d645da347d8c38b9fe6a72360e8275022a8c73bc40a77315e0001861f399d4e03e4f8716a36b37
languageName: node
linkType: hard
@@ -3666,13 +3670,97 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/primitive@npm:1.1.2, @radix-ui/primitive@npm:^1.1.2":
+"@radix-ui/number@npm:1.1.1":
+ version: 1.1.1
+ resolution: "@radix-ui/number@npm:1.1.1"
+ checksum: 58717faf3f7aa180fdfcde7083cae0bc06677cbd08fd2bed5a3f8820deeb6f514f7d475f1fbb61e1f9a16cb2e7daf1000b2c614b0de3520fccfc04e3576e4566
+ languageName: node
+ linkType: hard
+
+"@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/primitive@npm:1.1.3, @radix-ui/primitive@npm:^1.1.3":
+ version: 1.1.3
+ resolution: "@radix-ui/primitive@npm:1.1.3"
+ checksum: ee27abbff0d6d305816e9314655eb35e72478ba47416bc9d5cb0581728be35e3408cfc0748313837561d635f0cb7dfaae26e61831f0e16c0fd7d669a612f2cb0
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-accessible-icon@npm:1.1.7":
+ version: 1.1.7
+ resolution: "@radix-ui/react-accessible-icon@npm:1.1.7"
+ dependencies:
+ "@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: 716230e25f7342516f8cd5530fed815356c79d2ea3c5cc31129749e49cd6fd20bea548781b04349fe8136fd71caabf693b1268f7fe8d32dcd765fa3e051dbc19
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-accordion@npm:1.2.12":
+ version: 1.2.12
+ resolution: "@radix-ui/react-accordion@npm:1.2.12"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collapsible": 1.1.12
+ "@radix-ui/react-collection": 1.1.7
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ 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: 4acc2ccbb907cde2e9a9da0aca27c006b6880fe20204b662e8be76a0f69d9c99b31c4b57d7e6da4380591cad41cffea5e4b206bd8fefe9e9e42bb0fc0154a471
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-alert-dialog@npm:1.1.15":
+ version: 1.1.15
+ resolution: "@radix-ui/react-alert-dialog@npm:1.1.15"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-dialog": 1.1.15
+ "@radix-ui/react-primitive": 2.1.3
+ "@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: f2645c7896f6c49b333e673e6bddf1cbb58bb07997457c7008dc5d1acad46e8b9c8b4237173f27df0d6937f900fd1d0dd3153732a414d47efe39d35abb0a46b4
+ 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"
@@ -3688,19 +3776,869 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: 6cdf74f06090f8994cdf6d3935a44ea3ac309163a4f59c476482c4907e8e0775f224045030abf10fa4f9e1cb7743db034429249b9e59354988e247eeb0f4fdcf
+ checksum: 6cdf74f06090f8994cdf6d3935a44ea3ac309163a4f59c476482c4907e8e0775f224045030abf10fa4f9e1cb7743db034429249b9e59354988e247eeb0f4fdcf
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-aspect-ratio@npm:1.1.7":
+ version: 1.1.7
+ resolution: "@radix-ui/react-aspect-ratio@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: 50e84b77df4f777f03e5305ef6ca1b02824babc99bec5b7a2484d5c45c83a606375d1d3da63f7ac68d03b6abb27c3ddc0cbb02296a4315715116c221e7d61582
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-avatar@npm:1.1.10, @radix-ui/react-avatar@npm:^1.1.10":
+ version: 1.1.10
+ resolution: "@radix-ui/react-avatar@npm:1.1.10"
+ dependencies:
+ "@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-is-hydrated": 0.1.0
+ "@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: 3d63c9b99549c574be0f3f24028ab3f339e51ca85fc0821887f83e30af1342a41b3a3f40bf0fc12cdb2814340342530b4aba6b758deda9e99f6846b41d2f987f
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-checkbox@npm:1.3.3":
+ version: 1.3.3
+ resolution: "@radix-ui/react-checkbox@npm:1.3.3"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-presence": 1.1.5
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-previous": 1.1.1
+ "@radix-ui/react-use-size": 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: 83e8fce516b84fc012c0c2771db646ff135daeabfb045d9d57d1fa71da8009069272bdd230efd4e3438dcd045b8476c478c122f81afe167a7db0abd34dc79922
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-collapsible@npm:1.1.12":
+ version: 1.1.12
+ resolution: "@radix-ui/react-collapsible@npm:1.1.12"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-presence": 1.1.5
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.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: 31e01f7a628882b621843004863bf57c705e22de25ab41b74032a2ae2228f45251955d430f7c139a0c53fb3a19247b9d38b49b8c05b6da9dacc5524b28d29c23
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-collection@npm:1.1.7":
+ version: 1.1.7
+ resolution: "@radix-ui/react-collection@npm:1.1.7"
+ dependencies:
+ "@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-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: dd9bb015ef86205b4246f55bc84e5ad54519bb89b4825dd83e646fe95205191fe376bb31a9e847f9d66b710d0ef7fc9353c0b0ded7e8997a5c1f5be6addf94ef
+ 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-menu@npm:2.2.16":
+ version: 2.2.16
+ resolution: "@radix-ui/react-context-menu@npm:2.2.16"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-menu": 2.1.16
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-callback-ref": 1.1.1
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ 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: cbb06ab9196b703ce047c103fb3eefe601f73d5b41dc6e9f18c29decc1d0f3891f749161b8f0ef179e37d3d5da05a74ed1261d0e5f847c39462a47d76da2adcb
+ languageName: node
+ linkType: hard
+
+"@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-context@npm:^1.1.3":
+ version: 1.1.3
+ resolution: "@radix-ui/react-context@npm:1.1.3"
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: d0df606f54044db8d1ce65206b36dcdd6a4e44e0bfbd97011c872d5d5fd00b9f32f4709f39e8b4b8a5913977358e1d5d7e44b3b6f42a4aa8c54aa0e436715e32
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-dialog@npm:1.1.15":
+ version: 1.1.15
+ resolution: "@radix-ui/react-dialog@npm:1.1.15"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-focus-guards": 1.1.3
+ "@radix-ui/react-focus-scope": 1.1.7
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-portal": 1.1.9
+ "@radix-ui/react-presence": 1.1.5
+ "@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: a0834338ec66866ce301ef46e0dad9d99accf496f03b5021eceec7e2b79d7286b4f2c5e35f2387891e2bf33ef9a11d381dde2c8fe936a2f30cd50ca4e9bf4cb5
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-dialog@npm:^1.1.14":
+ version: 1.1.14
+ resolution: "@radix-ui/react-dialog@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-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: 4928c0bf84b3a054eb3b4659b8e87192d8c120333d8437fcbd9d9311502d5eea9e9c87173929d4bfbc0db61b1134fcd98015756011d67ddcd2aed1b4a0134d7c
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-direction@npm:1.1.1":
+ version: 1.1.1
+ resolution: "@radix-ui/react-direction@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: 8cc330285f1d06829568042ca9aabd3295be4690ae93683033fc8632b5c4dfc60f5c1312f6e2cae27c196189c719de3cfbcf792ff74800f9ccae0ab4abc1bc92
+ 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-dismissable-layer@npm:1.1.11":
+ version: 1.1.11
+ resolution: "@radix-ui/react-dismissable-layer@npm:1.1.11"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@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: 8fc9f027c9f68940c69c9cc117c43e1313d1a78ae4109cf809868b82837e5e2a7d410adf78e97328d9d5a080a63e399918414985658ab029a8df7d775af23b68
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-dropdown-menu@npm:2.1.16":
+ version: 2.1.16
+ resolution: "@radix-ui/react-dropdown-menu@npm:2.1.16"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-menu": 2.1.16
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ 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: 8178faa47e4ec58870db4f5c7fc158edf060bf00e9c9ed75e8028fdbc62dd9624b63ed5c461175be8e964d136f382b658881df68bdaf328da5c2ca56f8048f88
+ 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-guards@npm:1.1.3":
+ version: 1.1.3
+ resolution: "@radix-ui/react-focus-guards@npm:1.1.3"
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: b57878f6cf0ebc3e8d7c5c6bbaad44598daac19c921551ca541c104201048a9a902f3d69196e7a09995fd46e998c309aab64dc30fa184b3609d67d187a6a9c24
+ 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-form@npm:0.1.8":
+ version: 0.1.8
+ resolution: "@radix-ui/react-form@npm:0.1.8"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-label": 2.1.7
+ "@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: 38b3f93d47ece125c1e6f5de635cd873d6337600391003de2f2b46a10fefbd2ff70303d8de211b024a33880e2014130c79d333710831e0a6fc4a5dc6d0f80337
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-hover-card@npm:1.1.15":
+ version: 1.1.15
+ resolution: "@radix-ui/react-hover-card@npm:1.1.15"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-popper": 1.2.8
+ "@radix-ui/react-portal": 1.1.9
+ "@radix-ui/react-presence": 1.1.5
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ 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: 322373ce29136a1004d0a473208ea77ef552ec9ebafa9bc8d10df187f3ef936239a1109fadcb870151bc056f31fb6b121f8b65a0e8606caddac29268ca7d7651
+ 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-label@npm:2.1.7":
+ version: 2.1.7
+ resolution: "@radix-ui/react-label@npm:2.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: 6fe47ff695ac127a87a3ee77489fb345a89515edc8df4b2b290c801a9ae14ad934cc64ddad7638cddb71b064ff898bd1c2ac88c16dd1b8236a0939d7fea95e3b
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-menu@npm:2.1.16":
+ version: 2.1.16
+ resolution: "@radix-ui/react-menu@npm:2.1.16"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collection": 1.1.7
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-focus-guards": 1.1.3
+ "@radix-ui/react-focus-scope": 1.1.7
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-popper": 1.2.8
+ "@radix-ui/react-portal": 1.1.9
+ "@radix-ui/react-presence": 1.1.5
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-roving-focus": 1.1.11
+ "@radix-ui/react-slot": 1.2.3
+ "@radix-ui/react-use-callback-ref": 1.1.1
+ 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: 622f3abf8bb3c324ceb824988d7d384865191d5b09f2ddbc2a879b95d48d3e25ed9e22c4059203f4d29eaefe7d67a36e4b3cd2ce6b51596351cfd575d45d1fec
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-menubar@npm:1.1.16":
+ version: 1.1.16
+ resolution: "@radix-ui/react-menubar@npm:1.1.16"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collection": 1.1.7
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-menu": 2.1.16
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-roving-focus": 1.1.11
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ 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: b0d7eefb89b0e25fc0d4f2f54379cc5c3f05aa61090dea2c6ed2a70253c909143b3e08bfd9e45ce934c72cadced1cfb613c3e2d1703bce3b5ff7fbc84d8b44a7
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-navigation-menu@npm:1.2.14":
+ version: 1.2.14
+ resolution: "@radix-ui/react-navigation-menu@npm:1.2.14"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collection": 1.1.7
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-presence": 1.1.5
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-callback-ref": 1.1.1
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-layout-effect": 1.1.1
+ "@radix-ui/react-use-previous": 1.1.1
+ "@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: e460e1664af866c92406cea2cce3aac166d3d26cca83b239698c69229bde44bded69f1fb813cc975be8945a1b9f30cae8371bc833309a0793c45e2d01457d923
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-one-time-password-field@npm:0.1.8":
+ version: 0.1.8
+ resolution: "@radix-ui/react-one-time-password-field@npm:0.1.8"
+ dependencies:
+ "@radix-ui/number": 1.1.1
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collection": 1.1.7
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-roving-focus": 1.1.11
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-effect-event": 0.0.2
+ "@radix-ui/react-use-is-hydrated": 0.1.0
+ "@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: 32b3be275de9e614c42ca4cf34f5608fe92c645bd1774e3331308c21a328dbd544ba6eedddc456557b75f7780789a47fe266cc89d4344eac59af0ee467e50ff6
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-password-toggle-field@npm:0.1.3":
+ version: 0.1.3
+ resolution: "@radix-ui/react-password-toggle-field@npm:0.1.3"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-effect-event": 0.0.2
+ "@radix-ui/react-use-is-hydrated": 0.1.0
+ 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: bb24a1ea18eaa08262cb8779ccfad0b505fd9ef6f5d459c118639d1c1ecd2ac897a574fe013807c13cd572b61c175fe497fe65174dde27597724decbe633cdbb
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-popover@npm:1.1.15":
+ version: 1.1.15
+ resolution: "@radix-ui/react-popover@npm:1.1.15"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-focus-guards": 1.1.3
+ "@radix-ui/react-focus-scope": 1.1.7
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-popper": 1.2.8
+ "@radix-ui/react-portal": 1.1.9
+ "@radix-ui/react-presence": 1.1.5
+ "@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: 8a9ff4c6d8755cb770bbcfa2e60c4364364ccbf57888d72ebb77d8eb73a859023d7950c125de23d29d8506b4944b2af8b8c0f59f9880768eea6b2543e66e3d36
+ 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-popper@npm:1.2.8":
+ version: 1.2.8
+ resolution: "@radix-ui/react-popper@npm:1.2.8"
+ 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: 51370bc4868542ab8b807da0b43158d699715c13f5e31a5236861a172b75eb68ab9556945bbddbc0cb408bcc8da4f4569f42d657b19925e89501797e4eb3738b
+ 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-presence@npm:1.1.5":
+ version: 1.1.5
+ resolution: "@radix-ui/react-presence@npm:1.1.5"
+ 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: 05f1b8e80d3d878efab44304ce55d0b9e6c7050e8345f9da95d0597a716121fb2467c3247c847c51a6cb27edd00e86ac36b2635e4c00ea79d91cfc26c930da81
+ languageName: node
+ linkType: hard
+
+"@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-primitive@npm:^2.1.4":
+ version: 2.1.4
+ resolution: "@radix-ui/react-primitive@npm:2.1.4"
+ dependencies:
+ "@radix-ui/react-slot": 1.2.4
+ 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: 8b4cf865b4d4d135c9c6768856fdad0f62cbe43a2819471eb682061d2076222c3368ae0b771516426b264afcaf0a0032943408b3a789cbb77273152dd4a06d05
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-progress@npm:1.1.7":
+ version: 1.1.7
+ resolution: "@radix-ui/react-progress@npm:1.1.7"
+ dependencies:
+ "@radix-ui/react-context": 1.1.2
+ "@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: 9161fe9b64819b2ec6597a600e8868eb5002757fe9fb7b2f4987f52369823fed498f27dcb6856b5a8bbd5d5f9cd5e8d296ba8be80f15b7c00fe8130425e10200
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-radio-group@npm:1.3.8":
+ version: 1.3.8
+ resolution: "@radix-ui/react-radio-group@npm:1.3.8"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-presence": 1.1.5
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-roving-focus": 1.1.11
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-previous": 1.1.1
+ "@radix-ui/react-use-size": 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: 5e35970ec8965f398f15ff9b7d52c74125119d4af303fe7cd0c91f95d00692e4630d3db01e068eba833669dfb813a14b06cd80587acde028bd4cd747cb2aff41
languageName: node
linkType: hard
-"@radix-ui/react-avatar@npm:^1.1.10":
- version: 1.1.10
- resolution: "@radix-ui/react-avatar@npm:1.1.10"
+"@radix-ui/react-roving-focus@npm:1.1.11":
+ version: 1.1.11
+ resolution: "@radix-ui/react-roving-focus@npm:1.1.11"
dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collection": 1.1.7
+ "@radix-ui/react-compose-refs": 1.1.2
"@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-id": 1.1.1
"@radix-ui/react-primitive": 2.1.3
"@radix-ui/react-use-callback-ref": 1.1.1
- "@radix-ui/react-use-is-hydrated": 0.1.0
- "@radix-ui/react-use-layout-effect": 1.1.1
+ "@radix-ui/react-use-controllable-state": 1.2.2
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
@@ -3711,52 +4649,60 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: 3d63c9b99549c574be0f3f24028ab3f339e51ca85fc0821887f83e30af1342a41b3a3f40bf0fc12cdb2814340342530b4aba6b758deda9e99f6846b41d2f987f
+ checksum: 62af05c244803359c36beea278dac89caee37d20c31b84bcba3a20c462df33b7395c2e1b08b3a8ebb471c29cec4b3fb4f97488b6a167b1b275cedf994cf436e6
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"
+"@radix-ui/react-scroll-area@npm:1.2.10":
+ version: 1.2.10
+ resolution: "@radix-ui/react-scroll-area@npm:1.2.10"
+ dependencies:
+ "@radix-ui/number": 1.1.1
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-presence": 1.1.5
+ "@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
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
- 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":
+ "@types/react-dom":
optional: true
- checksum: 6d08437f23df362672259e535ae463e70bf7a0069f09bfa06c983a5a90e15250bde19da1d63ef8e3da06df1e1b4f92afa9d28ca6aa0297bb1c8aaf6ca83d28c5
+ checksum: da557a86de05801e276838923dc96a7715bd5982ac459b17925fae982d79c26607358f9fe7a8ef8f571f65c0c95da1427ed95a09a752493d8a81ad6d1b50f0da
languageName: node
linkType: hard
-"@radix-ui/react-dialog@npm:^1.1.14":
- version: 1.1.14
- resolution: "@radix-ui/react-dialog@npm:1.1.14"
+"@radix-ui/react-select@npm:2.2.6":
+ version: 2.2.6
+ resolution: "@radix-ui/react-select@npm:2.2.6"
dependencies:
- "@radix-ui/primitive": 1.1.2
+ "@radix-ui/number": 1.1.1
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collection": 1.1.7
"@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-direction": 1.1.1
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-focus-guards": 1.1.3
"@radix-ui/react-focus-scope": 1.1.7
"@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-popper": 1.2.8
"@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-callback-ref": 1.1.1
"@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-layout-effect": 1.1.1
+ "@radix-ui/react-use-previous": 1.1.1
+ "@radix-ui/react-visually-hidden": 1.2.3
aria-hidden: ^1.2.4
react-remove-scroll: ^2.6.3
peerDependencies:
@@ -3769,19 +4715,15 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: 4928c0bf84b3a054eb3b4659b8e87192d8c120333d8437fcbd9d9311502d5eea9e9c87173929d4bfbc0db61b1134fcd98015756011d67ddcd2aed1b4a0134d7c
+ checksum: 785d998596f952d8384c48ad63f286537ab54c4711f3d4c1c1f5d784160b40d27541ef403cbeed675fbd3c005c0bc62bae8568a376b5acc06386b1fa5014379c
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"
+"@radix-ui/react-separator@npm:1.1.7":
+ version: 1.1.7
+ resolution: "@radix-ui/react-separator@npm:1.1.7"
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": "*"
@@ -3792,78 +4734,106 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: c4f31e8e93ae979a1bcd60726f8ebe7b79f23baafcd1d1e65f62cff6b322b2c6ff6132d82f2e63737f9955a8f04407849036f5b64b478e9a5678747d835957d8
+ checksum: c5c19b7991bf5395eb2b849145deeb9aa307d9fcf220380497992ff2a301753ab477d0329af51b6d500cd3c1d777edbd2504b544d9254542fb5f829ac5ddbcf3
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"
+"@radix-ui/react-slider@npm:1.3.6":
+ version: 1.3.6
+ resolution: "@radix-ui/react-slider@npm:1.3.6"
+ dependencies:
+ "@radix-ui/number": 1.1.1
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collection": 1.1.7
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-layout-effect": 1.1.1
+ "@radix-ui/react-use-previous": 1.1.1
+ "@radix-ui/react-use-size": 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
- checksum: 618658e2b98575198b94ccfdd27f41beb37f83721c9a04617e848afbc47461124ae008d703d713b9644771d96d4852e49de322cf4be3b5f10a4f94d200db5248
+ "@types/react-dom":
+ optional: true
+ checksum: 508ca499b0f979a629ef6a80cce7e0b327054bc2021cee32b2aacab664dc6af5bb2269ef599aa56948a44c67db1d46e83db09f47ae84b8214dcbee9ea16d33eb
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"
+"@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
- "@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
+ checksum: 2731089e15477dd5eef98a5757c36113dd932d0c52ff05123cd89f05f0412e95e5b205229185d1cd705cda4a674a838479cce2b3b46ed903f82f5d23d9e3f3c2
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"
+"@radix-ui/react-slot@npm:1.2.4":
+ version: 1.2.4
+ resolution: "@radix-ui/react-slot@npm:1.2.4"
dependencies:
- "@radix-ui/react-use-layout-effect": 1.1.1
+ "@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: 8d68e200778eb3038906870fc869b3d881f4a46715fb20cddd9c76cba42fdaaa4810a3365b6ec2daf0f185b9201fc99d009167f59c7921bc3a139722c2e976db
+ checksum: 6e1cda512f649ca9df8e746a5059c69aa7539b43870ff6def360103590d68d1ea3adf8a216092107b3e6476d55a3d71707e162b5be04574126bcc6fdfffefe6a
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"
+"@radix-ui/react-switch@npm:1.2.6":
+ version: 1.2.6
+ resolution: "@radix-ui/react-switch@npm:1.2.6"
dependencies:
- "@radix-ui/primitive": 1.1.2
+ "@radix-ui/primitive": 1.1.3
"@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-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-previous": 1.1.1
+ "@radix-ui/react-use-size": 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: 737ebe7cd5544455411e8a606980e4491281fb38a829eb08a4505251f51c32dcae0b9f13b9ab0b574980d4e228e352b1613971f0b2a516d0fe2eefe2bb318231
+ languageName: node
+ linkType: hard
+
+"@radix-ui/react-tabs@npm:1.1.13":
+ version: 1.1.13
+ resolution: "@radix-ui/react-tabs@npm:1.1.13"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
"@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-presence": 1.1.5
"@radix-ui/react-primitive": 2.1.3
- "@radix-ui/react-slot": 1.2.3
+ "@radix-ui/react-roving-focus": 1.1.11
"@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": "*"
@@ -3874,24 +4844,26 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: 50f146117ebf675944181ef2df4fbc2d7ca017c71a2ab78eaa67159eb8a0101c682fa02bafa2b132ea7744592b7f103d02935ace2c1f430ab9040a0ece9246c8
+ checksum: 6bb8fa404d65ddb1be12cb03912abff8d924fb9b3435da71b39836585df6b55bd25341bd989324c330724af942c0f0cdf4c51503057b0532359da40c64b08081
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"
+"@radix-ui/react-toast@npm:1.2.15":
+ version: 1.2.15
+ resolution: "@radix-ui/react-toast@npm:1.2.15"
dependencies:
- "@floating-ui/react-dom": ^2.0.0
- "@radix-ui/react-arrow": 1.1.7
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-collection": 1.1.7
"@radix-ui/react-compose-refs": 1.1.2
"@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-portal": 1.1.9
+ "@radix-ui/react-presence": 1.1.5
"@radix-ui/react-primitive": 2.1.3
"@radix-ui/react-use-callback-ref": 1.1.1
+ "@radix-ui/react-use-controllable-state": 1.2.2
"@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
+ "@radix-ui/react-visually-hidden": 1.2.3
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
@@ -3902,16 +4874,21 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: 1d672b8b635846501212eb0cd15273c8acdd31e76e78d2b9ba29ce29730d5a2d3a61a8ed49bb689c94f67f45d1dffe0d49449e0810f08c4e112d8aef8430e76d
+ checksum: 45be7c36636f7ed0ac7eb67d6cf36aeefba14fee084780b187b155c32556031f57bcecdf86c7fed02564104ffeb96c6c32ad69412830e080383c9a2acb12b04c
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"
+"@radix-ui/react-toggle-group@npm:1.1.11":
+ version: 1.1.11
+ resolution: "@radix-ui/react-toggle-group@npm:1.1.11"
dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
"@radix-ui/react-primitive": 2.1.3
- "@radix-ui/react-use-layout-effect": 1.1.1
+ "@radix-ui/react-roving-focus": 1.1.11
+ "@radix-ui/react-toggle": 1.1.10
+ "@radix-ui/react-use-controllable-state": 1.2.2
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
@@ -3922,16 +4899,17 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: bd6be39bf021d5c917e2474ecba411e2625171f7ef96862b9af04bbd68833bb3662a7f1fbdeb5a7a237111b10e811e76d2cd03e957dadd6e668ef16541bfbd68
+ checksum: 5824dd7b2373fba106029bf9d1bfe23deb0f5cfa78308fd9037c2bac79b8c19ea1d74aa855059479aabf3f4b76be09a369c97f71f9beb7b270357176cbc7bf1e
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"
+"@radix-ui/react-toggle@npm:1.1.10":
+ version: 1.1.10
+ resolution: "@radix-ui/react-toggle@npm:1.1.10"
dependencies:
- "@radix-ui/react-compose-refs": 1.1.2
- "@radix-ui/react-use-layout-effect": 1.1.1
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-use-controllable-state": 1.2.2
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
@@ -3942,15 +4920,21 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: d3b0976368fccdfa07100c1f07ca434d0092d4132d1ed4a5c213802f7318d77fc1fd61d1b7038b87e82912688fafa97d8af000a6cca4027b09d92c5477f79dd0
+ checksum: ece154bd7a0ca765040fafbe4131f753457a3d37f73dec3ae94f08f17f3889272d06f33f305ad34c986925ce8a40532ee43f6bdb7e8b99fd8bac299b01d69204
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"
+"@radix-ui/react-toolbar@npm:1.1.11":
+ version: 1.1.11
+ resolution: "@radix-ui/react-toolbar@npm:1.1.11"
dependencies:
- "@radix-ui/react-slot": 1.2.3
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-roving-focus": 1.1.11
+ "@radix-ui/react-separator": 1.1.7
+ "@radix-ui/react-toggle-group": 1.1.11
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
@@ -3961,22 +4945,37 @@ __metadata:
optional: true
"@types/react-dom":
optional: true
- checksum: 01f82e4bad76b57767198762c905e5bcea04f4f52129749791e31adfcb1b36f6fdc89c73c40017d812b6e25e4ac925d837214bb280cfeaa5dc383457ce6940b0
+ checksum: 2fef1b1b2f90046ffa1397b039604da9cd2ab2f5d09002b0b9f2b2e507c468c09545ad56002a7ddab3c80bbc748c2ba42ef2bf83eb219eee548cb5ba8484eeed
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"
+"@radix-ui/react-tooltip@npm:1.2.8":
+ version: 1.2.8
+ resolution: "@radix-ui/react-tooltip@npm:1.2.8"
dependencies:
+ "@radix-ui/primitive": 1.1.3
"@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-id": 1.1.1
+ "@radix-ui/react-popper": 1.2.8
+ "@radix-ui/react-portal": 1.1.9
+ "@radix-ui/react-presence": 1.1.5
+ "@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
- checksum: 2731089e15477dd5eef98a5757c36113dd932d0c52ff05123cd89f05f0412e95e5b205229185d1cd705cda4a674a838479cce2b3b46ed903f82f5d23d9e3f3c2
+ "@types/react-dom":
+ optional: true
+ checksum: acd2606793f05e77c0fe1cc98d97bc1e9d07cdfd48fdebd23e4555676b9acaafbf70e518fbde943a9304cd086d85c2c78bcb9470d9128c2dc8cb61b02531311e
languageName: node
linkType: hard
@@ -4097,6 +5096,19 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-use-previous@npm:1.1.1":
+ version: 1.1.1
+ resolution: "@radix-ui/react-use-previous@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: ea6ea13523a0561dda9b14b9d44e299484816a6762d7fb50b91b27b6aec89f78c85245b69d5a904750d43919dbb7ef6ce6d3823639346675aa3a5cb9de32d984
+ 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"
@@ -4929,10 +5941,10 @@ __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
+"@standard-schema/spec@npm:^1.1.0":
+ version: 1.1.0
+ resolution: "@standard-schema/spec@npm:1.1.0"
+ checksum: 6245ebef5e698bb04752a22e996a7cc40406a404d9f68a9d4e1a7a10f2422da287247508e7b495a2f32bb38f3d57b4daf2c9ab4bf22d9bca13e20a3dc5ec575e
languageName: node
linkType: hard
@@ -5664,7 +6676,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4":
+"@types/hast@npm:^3.0.0":
version: 3.0.4
resolution: "@types/hast@npm:3.0.4"
dependencies:
@@ -7455,23 +8467,31 @@ __metadata:
languageName: node
linkType: hard
-"assistant-cloud@npm:0.0.2":
- version: 0.0.2
- resolution: "assistant-cloud@npm:0.0.2"
+"assistant-cloud@npm:^0.1.27":
+ version: 0.1.27
+ resolution: "assistant-cloud@npm:0.1.27"
dependencies:
- assistant-stream: ^0.2.17
- checksum: 5a4df3ca6c40dad15eb38c3f974bffd321bf52859f41bc3459d75f912e37579211a07876a5d28ce462396407f87a863dc849034f9bc24e04c010ab31827c6f82
+ assistant-stream: ^0.3.12
+ checksum: a59c99100e53c728a094700d14e0a4411b21c8e3fc7f56a320d4e8ca697c843a82f23d4c984aaabaf5843bd29660403a1e63c123fefa3a02a4bf3027c88d6177
languageName: node
linkType: hard
-"assistant-stream@npm:^0.2.17":
- version: 0.2.17
- resolution: "assistant-stream@npm:0.2.17"
+"assistant-stream@npm:^0.3.12, assistant-stream@npm:^0.3.14":
+ version: 0.3.14
+ resolution: "assistant-stream@npm:0.3.14"
dependencies:
- "@types/json-schema": ^7.0.15
- nanoid: 5.1.5
- secure-json-parse: ^4.0.0
- checksum: 1b317f2e5c19ce013cb8673ff73461b5ba39fe80ff05c3d4b6e67704d89a70ab76337dac6974901962caedcffc45429e990856c3e551122de6a3b37649a2d8cb
+ "@standard-schema/spec": ^1.1.0
+ nanoid: ^5.1.11
+ secure-json-parse: ^4.1.0
+ peerDependencies:
+ ioredis: ^5.10.1
+ redis: ^5.12.1
+ peerDependenciesMeta:
+ ioredis:
+ optional: true
+ redis:
+ optional: true
+ checksum: eecec159f11b402fd2e3961b350b6f1cea431667e30891766988eb595fc32c88c084f1dba813cbf476c3066d406978e2f75266aff3b02b2582ec6a5bafac2838
languageName: node
linkType: hard
@@ -15716,10 +16736,8 @@ coolshapes-react@lowcoder-org/coolshapes-react:
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
+ "@assistant-ui/react": ^0.14.5
+ "@assistant-ui/react-markdown": ^0.14.0
"@bany/curl-to-json": ^1.2.8
"@codemirror/autocomplete": ^6.11.1
"@codemirror/commands": ^6.3.2
@@ -17352,15 +18370,6 @@ 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"
@@ -17370,6 +18379,15 @@ coolshapes-react@lowcoder-org/coolshapes-react:
languageName: node
linkType: hard
+"nanoid@npm:^5.1.11":
+ version: 5.1.11
+ resolution: "nanoid@npm:5.1.11"
+ bin:
+ nanoid: bin/nanoid.js
+ checksum: d5971a1d39f68bd9845565c70a27733c832ba1bd73caad3c7b999a366c0355b6fcc65064f3b0179bca6cd36b50cacd5747d4482c303f026b58d62ebe31df9868
+ languageName: node
+ linkType: hard
+
"napi-macros@npm:~2.0.0":
version: 2.0.0
resolution: "napi-macros@npm:2.0.0"
@@ -18717,6 +19735,79 @@ coolshapes-react@lowcoder-org/coolshapes-react:
languageName: node
linkType: hard
+"radix-ui@npm:^1.4.3":
+ version: 1.4.3
+ resolution: "radix-ui@npm:1.4.3"
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-accessible-icon": 1.1.7
+ "@radix-ui/react-accordion": 1.2.12
+ "@radix-ui/react-alert-dialog": 1.1.15
+ "@radix-ui/react-arrow": 1.1.7
+ "@radix-ui/react-aspect-ratio": 1.1.7
+ "@radix-ui/react-avatar": 1.1.10
+ "@radix-ui/react-checkbox": 1.3.3
+ "@radix-ui/react-collapsible": 1.1.12
+ "@radix-ui/react-collection": 1.1.7
+ "@radix-ui/react-compose-refs": 1.1.2
+ "@radix-ui/react-context": 1.1.2
+ "@radix-ui/react-context-menu": 2.2.16
+ "@radix-ui/react-dialog": 1.1.15
+ "@radix-ui/react-direction": 1.1.1
+ "@radix-ui/react-dismissable-layer": 1.1.11
+ "@radix-ui/react-dropdown-menu": 2.1.16
+ "@radix-ui/react-focus-guards": 1.1.3
+ "@radix-ui/react-focus-scope": 1.1.7
+ "@radix-ui/react-form": 0.1.8
+ "@radix-ui/react-hover-card": 1.1.15
+ "@radix-ui/react-label": 2.1.7
+ "@radix-ui/react-menu": 2.1.16
+ "@radix-ui/react-menubar": 1.1.16
+ "@radix-ui/react-navigation-menu": 1.2.14
+ "@radix-ui/react-one-time-password-field": 0.1.8
+ "@radix-ui/react-password-toggle-field": 0.1.3
+ "@radix-ui/react-popover": 1.1.15
+ "@radix-ui/react-popper": 1.2.8
+ "@radix-ui/react-portal": 1.1.9
+ "@radix-ui/react-presence": 1.1.5
+ "@radix-ui/react-primitive": 2.1.3
+ "@radix-ui/react-progress": 1.1.7
+ "@radix-ui/react-radio-group": 1.3.8
+ "@radix-ui/react-roving-focus": 1.1.11
+ "@radix-ui/react-scroll-area": 1.2.10
+ "@radix-ui/react-select": 2.2.6
+ "@radix-ui/react-separator": 1.1.7
+ "@radix-ui/react-slider": 1.3.6
+ "@radix-ui/react-slot": 1.2.3
+ "@radix-ui/react-switch": 1.2.6
+ "@radix-ui/react-tabs": 1.1.13
+ "@radix-ui/react-toast": 1.2.15
+ "@radix-ui/react-toggle": 1.1.10
+ "@radix-ui/react-toggle-group": 1.1.11
+ "@radix-ui/react-toolbar": 1.1.11
+ "@radix-ui/react-tooltip": 1.2.8
+ "@radix-ui/react-use-callback-ref": 1.1.1
+ "@radix-ui/react-use-controllable-state": 1.2.2
+ "@radix-ui/react-use-effect-event": 0.0.2
+ "@radix-ui/react-use-escape-keydown": 1.1.1
+ "@radix-ui/react-use-is-hydrated": 0.1.0
+ "@radix-ui/react-use-layout-effect": 1.1.1
+ "@radix-ui/react-use-size": 1.1.1
+ "@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: fee12632641ebdf5013b3c65cd5a63864adbc09ac8903456e4d2a18bae0075868f661443f80e8dd112bb9dd37736714fb4176ceef9566d1b9a75b9eb526411de
+ languageName: node
+ linkType: hard
+
"randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0":
version: 2.1.0
resolution: "randombytes@npm:2.1.0"
@@ -20998,6 +22089,13 @@ coolshapes-react@lowcoder-org/coolshapes-react:
languageName: node
linkType: hard
+"safe-content-frame@npm:^0.0.19":
+ version: 0.0.19
+ resolution: "safe-content-frame@npm:0.0.19"
+ checksum: c47b4eb7ab782f5addaa287c7fbb9eecb63049a670c42919022bb73a0ca2ed880dd6bc010ec6179987b7c1d36fb284b4b5d862af6b76c1e7c355270cdf2e8896
+ languageName: node
+ linkType: hard
+
"safe-push-apply@npm:^1.0.0":
version: 1.0.0
resolution: "safe-push-apply@npm:1.0.0"
@@ -21127,10 +22225,10 @@ coolshapes-react@lowcoder-org/coolshapes-react:
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
+"secure-json-parse@npm:^4.1.0":
+ version: 4.1.0
+ resolution: "secure-json-parse@npm:4.1.0"
+ checksum: 323a68f21999847dce0c28d57b67f7323e6066e51057fb8f521feb9829e57a1eedce389187db11966da548bf469b141f52ab9655662a229dc8c7ab34012f2328
languageName: node
linkType: hard
@@ -23436,6 +24534,15 @@ coolshapes-react@lowcoder-org/coolshapes-react:
languageName: node
linkType: hard
+"use-effect-event@npm:^2.0.3":
+ version: 2.0.3
+ resolution: "use-effect-event@npm:2.0.3"
+ peerDependencies:
+ react: ^18.3 || ^19.0.0-0
+ checksum: 9e86ca307d5ce82e2a09a82a1aafb76ea88cc89ba1c746b968bede51c955e0a652940890d11bfadd38fc7ed87578fe8b7821003ea436dd58e24d446691d98f0d
+ languageName: node
+ linkType: hard
+
"use-isomorphic-layout-effect@npm:^1.1.1":
version: 1.2.1
resolution: "use-isomorphic-layout-effect@npm:1.2.1"
@@ -24797,7 +25904,7 @@ 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":
+"zod-to-json-schema@npm:^3.24.1":
version: 3.24.6
resolution: "zod-to-json-schema@npm:3.24.6"
peerDependencies:
@@ -24806,13 +25913,6 @@ coolshapes-react@lowcoder-org/coolshapes-react:
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
-
"zod@npm:^3.25.76":
version: 3.25.76
resolution: "zod@npm:3.25.76"
@@ -24820,6 +25920,13 @@ coolshapes-react@lowcoder-org/coolshapes-react:
languageName: node
linkType: hard
+"zod@npm:^4.4.3":
+ version: 4.4.3
+ resolution: "zod@npm:4.4.3"
+ checksum: bf236fdee7a5a5ec645eef5bfea3aad34e7df912931c2a23bc17e5b59882482751da42392916529da52ff9bc70f584797a5d496f1fb81f2d1a4c90fdd3922d2a
+ languageName: node
+ linkType: hard
+
"zrender@npm:5.6.1, zrender@npm:^5.1.1":
version: 5.6.1
resolution: "zrender@npm:5.6.1"
@@ -24829,9 +25936,9 @@ 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"
+"zustand@npm:^5.0.13":
+ version: 5.0.13
+ resolution: "zustand@npm:5.0.13"
peerDependencies:
"@types/react": ">=18.0.0"
immer: ">=9.0.6"
@@ -24846,7 +25953,7 @@ coolshapes-react@lowcoder-org/coolshapes-react:
optional: true
use-sync-external-store:
optional: true
- checksum: 1808cd7d49e8ba6777e0d8d524a858a918a4fd0bcbde88eb19ea3f4be9c8066276ecc236a8ed071623d3f13373424c56a5ddb0013be590b641ac99bf8f9b4e19
+ checksum: 37f9c1eb888fc6c570f80a182a54844752ffe05dca75356273038effad642892df3a839f16733636a2dd4a49f68c02be27a37b9ba767af03515ef1f8d97468de
languageName: node
linkType: hard