Merge pull request #13233 from ToolJet/fix/appbuilder-02

Fix/appbuilder 02
This commit is contained in:
Johnson Cherian 2025-07-09 12:23:45 +05:30 committed by GitHub
commit 2be1a69aea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 3230 additions and 793 deletions

View file

@ -77,7 +77,7 @@ module.exports = defineConfig({
baseUrl: "http://localhost:8082",
specPattern: [
"cypress/e2e/happyPath/marketplace/commonTestcases/**/*.cy.js",
],
]
numTestsKeptInMemory: 1,
redirectionLimit: 7,
experimentalRunAllSpecs: true,

@ -1 +1 @@
Subproject commit 13eb6e8ef642973dd1be5b699407508d040b48e4
Subproject commit 40248db2afb8600966536eb9ff9f4f9dd7c248fb

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@
"@dnd-kit/utilities": "^3.2.1",
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@mdxeditor/editor": "^3.38.0",
"@microsoft/fetch-event-source": "^2.0.1",
"@radix-ui/colors": "^0.1.8",
"@radix-ui/react-avatar": "^1.0.4",
@ -156,6 +157,7 @@
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.0",
"@storybook/addon-essentials": "^7.2.1",
"@storybook/addon-interactions": "^7.2.1",
"@storybook/addon-links": "^7.2.1",
@ -192,6 +194,7 @@
"postcss": "^8.4.35",
"postcss-loader": "^8.1.0",
"prettier": "^2.8.4",
"react-refresh": "^0.17.0",
"sass": "^1.78.0",
"sass-loader": "^13.2.0",
"storybook": "^7.2.1",
@ -233,7 +236,7 @@
}
},
"scripts": {
"start": "webpack serve --port 8082 --host 0.0.0.0",
"start": "webpack serve --hot --port 8082 --host 0.0.0.0",
"build": "webpack --mode=production && cp -a ./assets/. ./build/assets/",
"lint": "eslint . '**/*.{js,jsx}'",
"format": "eslint . --fix '**/*.{js,jsx}'",

View file

@ -17,6 +17,8 @@ import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext';
import RightSidebarToggle from '@/AppBuilder/RightSideBar/RightSidebarToggle';
import { shallow } from 'zustand/shallow';
import ArtifactPreview from './ArtifactPreview';
// const EditorHeader = lazy(() => import('@/AppBuilder/Header'));
// const LeftSidebar = lazy(() => import('@/AppBuilder/LeftSidebar'));
// const AppCanvas = lazy(() => import('@/AppBuilder/AppCanvas'));
@ -32,6 +34,9 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
const isModuleEditor = appType === 'module';
const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode, shallow);
const appBuilderMode = useStore((state) => state.appStore.modules[moduleId]?.app?.appBuilderMode ?? 'visual');
const isUserInZeroToOneFlow = appBuilderMode === 'ai';
const changeToDarkMode = (newMode) => {
updateIsTJDarkMode(newMode);
@ -51,17 +56,29 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
<ErrorBoundary>
<ModuleProvider moduleId={moduleId} appType={appType} isModuleMode={false} isModuleEditor={isModuleEditor}>
<Suspense fallback={<div>Loading...</div>}>
<EditorHeader darkMode={darkMode} />
<LeftSidebar switchDarkMode={changeToDarkMode} darkMode={darkMode} />
<EditorHeader darkMode={darkMode} isUserInZeroToOneFlow={isUserInZeroToOneFlow} />
<LeftSidebar
switchDarkMode={changeToDarkMode}
darkMode={darkMode}
isUserInZeroToOneFlow={isUserInZeroToOneFlow}
/>
</Suspense>
{window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && <RealtimeCursors />}
<DndProvider backend={HTML5Backend}>
<AppCanvas moduleId={moduleId} appId={appId} switchDarkMode={switchDarkMode} darkMode={darkMode} />
<QueryPanel darkMode={darkMode} />
<RightSidebarToggle darkMode={darkMode} />
{isRightSidebarOpen && <RightSideBar darkMode={darkMode} />}{' '}
</DndProvider>
<Popups darkMode={darkMode} />
{isUserInZeroToOneFlow ? (
<ArtifactPreview darkMode={darkMode} isUserInZeroToOneFlow={isUserInZeroToOneFlow} />
) : (
<>
{window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && <RealtimeCursors />}
<DndProvider backend={HTML5Backend}>
<AppCanvas moduleId={moduleId} appId={appId} switchDarkMode={switchDarkMode} darkMode={darkMode} />
<QueryPanel darkMode={darkMode} />
<RightSidebarToggle darkMode={darkMode} />
{isRightSidebarOpen && <RightSideBar darkMode={darkMode} />}{' '}
</DndProvider>
<Popups darkMode={darkMode} />
</>
)}
</ModuleProvider>
</ErrorBoundary>
</div>

View file

@ -0,0 +1,9 @@
import React from 'react';
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
function ArtifactPreview() {
return <></>;
}
export default withEditionSpecificComponent(ArtifactPreview, 'AiBuilder');

View file

@ -0,0 +1,9 @@
import React from 'react';
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
function FixWithAi() {
return <></>;
}
export default withEditionSpecificComponent(FixWithAi, 'AiBuilder');

View file

@ -56,6 +56,8 @@ const MultiLineCodeEditor = (props) => {
renderCopilot,
setCodeEditorView,
} = props;
const editorRef = useRef(null);
const replaceIdsWithName = useStore((state) => state.replaceIdsWithName, shallow);
const wrapperRef = useRef(null);
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
@ -330,6 +332,11 @@ const MultiLineCodeEditor = (props) => {
}
}
const onAiSuggestionAccept = (newValue) => {
currentValueRef.current = newValue;
onChange(newValue);
};
return (
<div
className={`code-hinter-wrapper position-relative ${isInsideQueryPane ? 'code-editor-query-panel' : ''}`}
@ -337,7 +344,19 @@ const MultiLineCodeEditor = (props) => {
ref={wrapperRef}
>
<div className={`${className} ${darkMode && 'cm-codehinter-dark-themed'}`}>
<CodeHinterBtns view={editorView} isPanelOpen={isSearchPanelOpen} renderCopilot={renderCopilot} />
<CodeHinterBtns
view={editorView}
isPanelOpen={isSearchPanelOpen}
renderCopilot={() =>
renderCopilot({
darkMode,
language: lang,
editorRef,
onAiSuggestionAccept,
})
}
/>
<CodeHinter.PopupIcon
callback={handleTogglePopupExapand}
icon="portal-open"
@ -362,6 +381,7 @@ const MultiLineCodeEditor = (props) => {
<ErrorBoundary>
<div className="codehinter-container w-100 " data-cy={`${cyLabel}-input-field`} style={{ height: '100%' }}>
<CodeMirror
ref={editorRef}
value={initialValueWithReplacedIds}
placeholder={placeholder}
height={'100%'}

View file

@ -3,6 +3,7 @@ import { computeCoercion, getCurrentNodeType, hasDeepChildren, resolveReferences
import CodeHinter from '.';
import { copyToClipboard } from '@/_helpers/appUtils';
import { Alert } from '@/_ui/Alert/Alert';
import { Button } from '@/components/ui/Button/Button';
import _, { isEmpty } from 'lodash';
import { handleCircularStructureToJSON, hasCircularDependency, verifyConstant } from '@/_helpers/utils';
import Popover from 'react-bootstrap/Popover';
@ -15,6 +16,9 @@ import { shallow } from 'zustand/shallow';
import { Overlay } from 'react-bootstrap';
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
import { findDefault } from '../_utils/component-properties-validation';
import FixWithAi from './FixWithAi';
const sanitizeLargeDataset = (data, callback) => {
const SIZE_LIMIT_KB = 5 * 1024; // 5 KB in bytes
@ -143,7 +147,7 @@ export const PreviewBox = ({
useEffect(() => {
if (error) {
setErrorStateActive(true);
setErrorMessage(error.message);
setErrorMessage(error);
} else {
setErrorStateActive(false);
setErrorMessage(null);
@ -159,6 +163,8 @@ export const PreviewBox = ({
validationFn
);
const completeErrMessage = Array.isArray(_error) ? _error.join('.') : _error;
const resolvedValue = typeof rawResolvedValue === 'function' ? undefined : rawResolvedValue;
const newValue = typeof rawNewValue === 'function' ? undefined : rawNewValue;
@ -185,7 +191,7 @@ export const PreviewBox = ({
setError(null);
} else if (!valid && !newValue && !resolvedValue && !isSecretError) {
const err = !error ? `Invalid value for ${validationSchema?.schema?.type}` : `${_error}`;
setError({ message: err, value: resolvedValue, type: 'Invalid' });
setError({ message: err, value: resolvedValue, type: 'Invalid', completeErrorMessage: completeErrMessage });
} else {
const jsErrorType = isSecretError
? 'Error'
@ -211,6 +217,7 @@ export const PreviewBox = ({
? JSON.stringify(errValue, reservedKeywordReplacer)
: resolvedValue,
type: isSecretError ? 'Error' : jsErrorType,
completeErrorMessage: completeErrMessage,
});
setCoersionData(null);
}
@ -309,13 +316,118 @@ const PreviewContainer = ({
isPortalOpen,
previewRef,
showPreview,
onAiSuggestionAccept,
...restProps
}) => {
const { validationSchema, isWorkspaceVariable, errorStateActive, previewPlacement, validationFn } = restProps;
const [errorMessage, setErrorMessage] = useState('');
const typeofError = getCurrentNodeType(errorMessage);
const errorMsg = typeofError === 'Array' ? errorMessage[0] : errorMessage;
const {
validationSchema,
isWorkspaceVariable,
errorStateActive,
previewPlacement,
validationFn,
componentId,
paramName,
fieldMeta,
setIsFocused,
currentValue,
} = restProps;
const aiFeaturesEnabled = useStore((state) => state.ai?.aiFeaturesEnabled ?? false);
const fetchErrorFixUsingAi = useStore((state) => state.fetchErrorFixUsingAi);
const clearChatHistory = useStore((state) => state.clearChatHistory);
const componentDefinition = useStore((state) => state.getComponentDefinition(componentId), shallow); // TODO: check if moduleId needs to be passed here
const componentName = componentDefinition?.component?.name;
const componentKey = `${componentName} - ${fieldMeta?.displayName}`;
const chatList = useStore((state) => state.fixWithAiSlice?.[componentId]?.[componentKey]?.chatHistory ?? []);
const [errorMessage, setErrorMessage] = useState(null);
const [popoverToShow, setPopoverToShow] = useState('preview'); // preview | fixWithAI
const errMsg = errorMessage?.message ?? null;
const typeofError = getCurrentNodeType(errMsg);
const errorMsg = typeofError === 'Array' ? errMsg[0] : errMsg;
const darkMode = localStorage.getItem('darkMode') === 'true';
useEffect(() => {
!showPreview && setPopoverToShow('preview');
}, [showPreview]);
useEffect(() => {
setPopoverToShow('preview');
if (chatList?.length) {
clearChatHistory(componentId, componentKey);
}
}, [currentValue]);
const fetchFixUsingAi = () => {
const defaultValue = validationSchema?.defaultValue
? validationSchema?.defaultValue
: validationSchema
? findDefault(validationSchema?.schema ?? {}, errorMessage?.value)
: undefined;
const errorData = {
key: componentKey,
componentId: componentId,
message: errorMessage?.completeErrorMessage,
error: {
resolvedProperty: { [paramName]: errorMessage?.value },
effectiveProperty: { [paramName]: defaultValue },
componentId,
},
};
fetchErrorFixUsingAi(errorData, {
componentDisplayName:
componentDefinition?.component?.displayName ?? componentDefinition?.component?.component ?? componentName,
errorPropertyDisplayName: fieldMeta?.displayName,
customErrMessage: errorMessage?.message,
});
};
const handleFixErrorWithAI = () => {
setPopoverToShow('fixWithAI');
if (!componentId || chatList?.length) {
return;
}
fetchFixUsingAi();
};
const fixWithAIPopover = (
<Popover
bsPrefix="fix-with-ai-popover"
id="popover-basic"
className={`${darkMode && 'dark-theme'} tw-z-[9999] tw-w-96`}
onMouseEnter={() => setCursorInsidePreview(true)}
onMouseLeave={() => setCursorInsidePreview(false)}
>
<Popover.Body
style={{
border: '1px solid var(--slate6)',
padding: 0,
boxShadow: ' 0px 4px 8px 0px #3032331A, 0px 0px 1px 0px #3032330D',
}}
>
<FixWithAi
componentId={componentId}
componentKey={componentKey}
onApplyFix={onAiSuggestionAccept}
onRetry={fetchFixUsingAi}
onClose={() => setIsFocused(false)}
/>
</Popover.Body>
</Popover>
);
const popover = (
<Popover
bsPrefix="codehinter-preview-popover"
@ -350,6 +462,18 @@ const PreviewContainer = ({
<div className="d-flex align-items-center">
<div className="">{errorMsg !== 'null' ? errorMsg : 'Invalid'}</div>
</div>
{aiFeaturesEnabled && (
<Button
size="medium"
variant="outline"
leadingIcon="tooljetai"
className="mt-2"
onClick={handleFixErrorWithAI}
>
Fix with AI
</Button>
)}
</Alert>
</div>
)}
@ -479,7 +603,7 @@ const PreviewContainer = ({
},
}}
>
{(props) => React.cloneElement(popover, props)}
{(props) => React.cloneElement(popoverToShow === 'fixWithAI' ? fixWithAIPopover : popover, props)}
</Overlay>
)}

View file

@ -37,7 +37,7 @@ import Icon from '@/_ui/Icon/solidIcons/index';
const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => {
const { moduleId } = useModuleContext();
const { initialValue, onChange, enablePreview = true, portalProps } = restProps;
const { initialValue, onChange, enablePreview = true, portalProps, paramName } = restProps;
const { validation = {} } = fieldMeta;
const [showPreview, setShowPreview] = useState(false);
const [isFocused, setIsFocused] = useState(false);
@ -146,17 +146,24 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
enablePreview={enablePreview}
currentValue={currentValue}
isFocused={isFocused}
setIsFocused={setIsFocused}
setCursorInsidePreview={setCursorInsidePreview}
componentName={componentName}
validationSchema={validation}
setErrorStateActive={setErrorStateActive}
ignoreValidation={restProps?.ignoreValidation || isEmpty(validation)}
componentId={restProps?.componentId ?? null}
componentId={componentId ?? null}
fieldMeta={fieldMeta}
paramName={paramName}
isWorkspaceVariable={isWorkspaceVariable}
errorStateActive={errorStateActive}
previewPlacement={restProps?.cyLabel === 'canvas-bg-colour' ? 'top' : 'left-start'}
isPortalOpen={restProps?.portalProps?.isOpen}
validationFn={validationFn}
onAiSuggestionAccept={(newValue) => {
setCurrentValue(newValue);
onChange(newValue);
}}
>
<div className="code-editor-basic-wrapper d-flex">
<div className="codehinter-container w-100">
@ -176,6 +183,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
showPreview={showPreview}
wrapperRef={wrapperRef}
showSuggestions={showSuggestions}
cursorInsidePreview={cursorInsidePreview}
{...restProps}
/>
</div>
@ -211,6 +219,7 @@ const EditorInput = ({
wrapperRef,
showSuggestions,
setCodeEditorView = null, // Function to set the CodeMirror view
cursorInsidePreview = false,
}) => {
const codeHinterContext = useContext(CodeHinterContext);
const { suggestionList: paramHints } = createReferencesLookup(codeHinterContext, true);
@ -333,7 +342,8 @@ const EditorInput = ({
}, []);
const handleOnBlur = () => {
setShowPreview(false);
!cursorInsidePreview && setShowPreview(false);
if (!delayOnChange) {
setFirstTimeFocus(false);
return onBlurUpdate(currentValue);

View file

@ -15,13 +15,16 @@ import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer';
import { ModuleEditorBanner } from '@/modules/Modules/components';
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
export const EditorHeader = ({ darkMode }) => {
import Steps from './Steps';
export const EditorHeader = ({ darkMode, isUserInZeroToOneFlow }) => {
const { moduleId, isModuleEditor } = useModuleContext();
const { isSaving, saveError, isVersionReleased } = useStore(
const { isSaving, saveError, isVersionReleased, aiGenerationMetadata } = useStore(
(state) => ({
isSaving: state.appStore.modules[moduleId].app.isSaving,
saveError: state.appStore.modules[moduleId].app.saveError,
isVersionReleased: state.isVersionReleased,
aiGenerationMetadata: state.appStore.modules[moduleId].app?.aiGenerationMetadata,
}),
shallow
);
@ -80,44 +83,64 @@ export const EditorHeader = ({ darkMode }) => {
<EditAppName />
</div>
</div>
<HeaderActions darkMode={darkMode} />
<div className="d-flex align-items-center">
<div style={{ width: '100px' }}>
<span
className={cx('autosave-indicator tj-text-xsm', {
'autosave-indicator-saving': isSaving,
'text-danger': saveError,
'd-none': isVersionReleased,
})}
data-cy="autosave-indicator"
>
{getSaveIndicator()}
</span>
</div>
{shouldEnableMultiplayer && (
<div className="mx-2 p-2">
<RealtimeAvatars />
</div>
)}
{shouldEnableMultiplayer && <UpdatePresenceMultiPlayer />}
</div>
</div>
{!isModuleEditor && <div className="navbar-seperator"></div>}
</div>
<div className="d-flex align-items-center p-0">
<div className="d-flex version-manager-container p-0 mx-2 align-items-center ">
{!isModuleEditor && (
{isUserInZeroToOneFlow && (
<Steps
steps={aiGenerationMetadata?.steps?.map((step) => ({ label: step.name, value: step.id })) ?? []}
activeStep={aiGenerationMetadata?.active_step}
/>
)}
{!isUserInZeroToOneFlow && (
<>
<AppEnvironments darkMode={darkMode} />
<AppVersionsManager darkMode={darkMode} />
<GitSyncManager />
<HeaderActions darkMode={darkMode} />
<div className="d-flex align-items-center">
<div style={{ width: '100px' }}>
<span
className={cx('autosave-indicator tj-text-xsm', {
'autosave-indicator-saving': isSaving,
'text-danger': saveError,
'd-none': isVersionReleased,
})}
data-cy="autosave-indicator"
>
{getSaveIndicator()}
</span>
</div>
{shouldEnableMultiplayer && (
<div className="mx-2 p-2">
<RealtimeAvatars />
</div>
)}
{shouldEnableMultiplayer && <UpdatePresenceMultiPlayer />}
</div>
</>
)}
</div>
{!isModuleEditor && !isUserInZeroToOneFlow && <div className="navbar-seperator"></div>}
</div>
{!isUserInZeroToOneFlow && (
<div className="d-flex align-items-center p-0">
<div className="d-flex version-manager-container p-0 mx-2 align-items-center ">
{!isModuleEditor && (
<>
<AppEnvironments darkMode={darkMode} />
<AppVersionsManager darkMode={darkMode} />
<GitSyncManager />
</>
)}
</div>
</div>
)}
</div>
<RightTopHeaderButtons isModuleEditor={isModuleEditor} />
<BuildSuggestions />
{!isUserInZeroToOneFlow && (
<>
<RightTopHeaderButtons isModuleEditor={isModuleEditor} />
<BuildSuggestions />
</>
)}
</div>
</div>
</header>

View file

@ -0,0 +1,64 @@
import React, { Children } from 'react';
import { cn } from '@/lib/utils';
import CheckCircle from '@/_ui/Icon/solidIcons/CheckCircle';
import SolidArrow from '@/_ui/Icon/solidIcons/SolidArrow';
import DottedArrow from '@/_ui/Icon/solidIcons/DottedArrow';
function Step({ stepNo, label, active, completed }) {
return (
<div className="tw-flex tw-items-center tw-gap-1.5 tw-px-2.5 tw-py-1">
{completed ? (
<CheckCircle />
) : (
<span
className={cn(
'tw-bg-text-placeholder tw-text-white tw-text-[0.625rem] tw-rounded-full tw-size-3.5 tw-flex tw-justify-center tw-items-center',
{ '!tw-bg-black': active }
)}
>
{stepNo}
</span>
)}
<p
className={cn('tw-text-base tw-text-text-placeholder tw-font-medium tw-mb-0', {
'tw-text-text-primary': completed || active,
})}
>
{label}
</p>
</div>
);
}
function Connector({ completed }) {
if (completed) return <SolidArrow />;
return <DottedArrow />;
}
// sequential steps
export default function Steps({ steps, activeStep }) {
const activeStepIndex = steps.findIndex((step) => step.value === activeStep);
const currentStepIdx = activeStepIndex === -1 ? 0 : activeStepIndex;
return (
<div className="tw-flex tw-items-center tw-gap-1 tw-py-2">
{Children.toArray(
steps.map((step, index) => {
const isActive = index === currentStepIdx;
const isCompleted = index < currentStepIdx;
return (
<>
<Step stepNo={index + 1} label={step.label} active={isActive} completed={isCompleted} />
{index < steps.length - 1 && <Connector completed={isCompleted} />}
</>
);
})
)}
</div>
);
}

View file

@ -24,6 +24,7 @@ export const BaseLeftSidebar = ({
switchDarkMode,
renderAISideBarTrigger = () => null,
renderAIChat = () => null,
isUserInZeroToOneFlow,
}) => {
const { moduleId, isModuleEditor, appType } = useModuleContext();
const [
@ -72,6 +73,11 @@ export const BaseLeftSidebar = ({
};
useEffect(() => {
if (isUserInZeroToOneFlow) {
setPopoverContentHeight(((window.innerHeight - 48) / window.innerHeight) * 100);
return;
}
if (!isDraggingQueryPane) {
setPopoverContentHeight(
((window.innerHeight - (queryPanelHeight == 0 ? 40 : queryPanelHeight) - 45) / window.innerHeight) * 100
@ -80,7 +86,7 @@ export const BaseLeftSidebar = ({
setPopoverContentHeight(100);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryPanelHeight, isDraggingQueryPane]);
}, [isUserInZeroToOneFlow, queryPanelHeight, isDraggingQueryPane]);
const renderPopoverContent = () => {
if (selectedSidebarItem === null || !isSidebarOpen) return null;
@ -111,7 +117,7 @@ export const BaseLeftSidebar = ({
/>
);
case 'tooljetai':
return renderAIChat({ darkMode });
return renderAIChat({ darkMode, isUserInZeroToOneFlow });
// case 'datasource':
// return (
// <LeftSidebarDataSources
@ -211,19 +217,24 @@ export const BaseLeftSidebar = ({
tip: 'Build with AI',
ref: setSideBarBtnRefs('tooljetai'),
})}
{renderCommonItems()}
<SidebarItem
icon="settings"
selectedSidebarItem={selectedSidebarItem}
darkMode={darkMode}
// eslint-disable-next-line no-unused-vars
onClick={(e) => handleSelectedSidebarItem('settings')}
className={`left-sidebar-item left-sidebar-layout`}
badge={true}
tip="Settings"
ref={setSideBarBtnRefs('settings')}
isModuleEditor={isModuleEditor}
/>
{!isUserInZeroToOneFlow && (
<>
{renderCommonItems()}
<SidebarItem
icon="settings"
selectedSidebarItem={selectedSidebarItem}
darkMode={darkMode}
// eslint-disable-next-line no-unused-vars
onClick={(e) => handleSelectedSidebarItem('settings')}
className={`left-sidebar-item left-sidebar-layout`}
badge={true}
tip="Settings"
ref={setSideBarBtnRefs('settings')}
isModuleEditor={isModuleEditor}
/>
</>
)}
</>
);
};

View file

@ -35,7 +35,6 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc
const createDataQuery = useStore((state) => state.dataQuery.createDataQuery);
const setPreviewData = useStore((state) => state.queryPanel.setPreviewData);
const handleChangeDataSource = (source) => {
console.log({ source });
createDataQuery(source);
setPreviewData(null);
closePopup();

View file

@ -351,9 +351,24 @@ const useAppData = (
homePageId: homePageId,
isPublic: appData.is_public,
creationMode: appData.creation_mode,
appGeneratedFromPrompt: appData.app_generated_from_prompt,
aiGenerationMetadata: appData.ai_generation_metadata || {},
appBuilderMode: appData.app_builder_mode || 'visual',
},
moduleId
);
if (appData.app_builder_mode === 'ai') {
setSelectedSidebarItem('tooljetai');
toggleLeftSidebar(true);
// If the app builder mode is AI
// - Do not show zero state - if there is some conversation already done or if route state has prompt
setConversationZeroState(
state?.prompt ? true : Boolean(appData.ai_conversation?.aiConversationMessages?.length)
);
}
if (!moduleMode) {
setIsEditorFreezed(appData.should_freeze_editor);
const global_settings = mapKeys(
@ -483,17 +498,14 @@ const useAppData = (
setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))), moduleId);
initDependencyGraph(moduleId);
setCurrentMode(mode, moduleId); // TODO: set mode based on the slug/appDef
if (
!moduleMode &&
state.ai &&
state?.prompt &&
initialLoadRef.current &&
(conversation?.aiConversationMessages || []).length === 0
) {
setSelectedSidebarItem('tooljetai');
toggleLeftSidebar('true');
sendMessage(state.prompt);
setConversationZeroState(true);
showWalkthrough = false;
}
// fetchDataSources(appData.editing_version.id, editorEnvironment.id);

View file

@ -5,8 +5,8 @@ import DependencyGraph from './DependencyClass';
import { getWorkspaceId } from '@/_helpers/utils';
import { navigate } from '@/AppBuilder/_utils/misc';
import queryString from 'query-string';
import { replaceEntityReferencesWithIds, baseTheme } from '../utils';
import _ from 'lodash';
import { convertKeysToCamelCase, replaceEntityReferencesWithIds, baseTheme } from '../utils';
import _, { isEmpty } from 'lodash';
const initialState = {
isSaving: false,
@ -301,4 +301,36 @@ export const createAppSlice = (set, get) => ({
set((state) => {
state.appPermission.selectedUsers = users;
}),
updateAppGenerationMetadata: (dataToUpdate, moduleId = 'canvas') => {
set((state) => {
if (isEmpty(dataToUpdate) || !state.appStore.modules[moduleId].app?.aiGenerationMetadata) return;
// Any value at the top level of aiGenerationMetadata can be updated using this, for nested keys either send complete data or need to add separate logic to handle it
Object.keys(dataToUpdate).forEach((key) => {
if (dataToUpdate[key] !== undefined) {
state.appStore.modules[moduleId].app.aiGenerationMetadata[key] = dataToUpdate[key];
}
});
});
},
updateAppData: (dataToUpdate, moduleId = 'canvas') => {
set((state) => {
state.appStore.modules[moduleId].app = { ...state.appStore.modules[moduleId].app, ...dataToUpdate };
});
},
updateAppInfoInDB: async (payload, moduleId = 'canvas') => {
const { appId } = get().appStore.modules[moduleId].app;
if (!appId || isEmpty(payload)) return;
try {
await appsService.saveApp(appId, payload);
get().updateAppData(convertKeysToCamelCase(payload), moduleId);
} catch (error) {
console.log(error);
}
},
});

View file

@ -2057,6 +2057,7 @@ export const createComponentsSlice = (set, get) => ({
validation: componentDefinition.component.definition?.validation,
},
name: componentName,
displayName: componentDefinition.component.displayName,
parent: componentDefinition.component.parent,
},
layouts: componentDefinition.layouts,

View file

@ -57,10 +57,13 @@ export const createDataQuerySlice = (set, get) => ({
);
});
},
createDataQuery: (selectedDataSource, shouldRunQuery, customOptions = {}, moduleId = 'canvas') => {
createDataQuery: (selectedDataSource, shouldRunQuery, customOptions = {}, moduleId = 'canvas', queryName) => {
let name;
const appVersionId = get().currentVersionId;
const appId = get().appStore.modules[moduleId].app.appId;
const { options: defaultOptions, name } = getDefaultOptions(selectedDataSource);
const { options: defaultOptions, name: nameFromDefaultOptions } = getDefaultOptions(selectedDataSource);
if (!queryName) name = nameFromDefaultOptions;
else name = queryName;
const options = { ...defaultOptions, ...customOptions };
const kind = selectedDataSource.kind;
const tempId = uuidv4();
@ -77,6 +80,7 @@ export const createDataQuerySlice = (set, get) => ({
setIsAppSaving(true);
const dataQueries = get().dataQuery.queries.modules[moduleId];
const currDataQueries = [...dataQueries];
const runOnCreate = options.runOnCreate;
set((state) => {
state.dataQuery.queries.modules[moduleId] = [
{
@ -111,7 +115,7 @@ export const createDataQuerySlice = (set, get) => ({
return query;
});
});
setSelectedQuery(data.id, moduleId);
setSelectedQuery(data.id, data);
if (shouldRunQuery) setQueryToBeRun(data);
/** Checks if there is an API call cached. If yes execute it */
@ -141,6 +145,10 @@ export const createDataQuerySlice = (set, get) => ({
},
moduleId
);
if (runOnCreate) {
get().queryPanel.runQuery(data.id, data.name, undefined, undefined, {}, true, false, moduleId);
}
})
.catch((error) => {
set((state) => {

View file

@ -0,0 +1,5 @@
import { getEditionSpecificSlice } from '../../../modules/common/helpers/getEditionSpecificSlice';
const createFixWithAiSlice = getEditionSpecificSlice('createFixWithAiSlice');
export { createFixWithAiSlice };

View file

@ -1218,8 +1218,6 @@ export const createQueryPanelSlice = (set, get) => ({
},
createProxy: (obj, path = '') => {
const { queryPanel } = get();
const { createProxy } = queryPanel;
return new Proxy(obj, {
get(target, prop) {
@ -1230,7 +1228,7 @@ export const createQueryPanelSlice = (set, get) => ({
}
const value = target[prop];
return typeof value === 'object' && value !== null ? createProxy(value, fullPath) : value;
return value;
},
});
},

View file

@ -27,6 +27,7 @@ import { createCodeHinterSlice } from './slices/codeHinterSlice';
import { createDebuggerSlice } from './slices/debuggerSlice';
import { createGitSyncSlice } from './slices/gitSyncSlice';
import { createAiSlice } from './slices/aiSlice';
import { createFixWithAiSlice } from './slices/fixWithAi';
import { createWhiteLabellingSlice } from './slices/whiteLabellingSlice';
import { createFormComponentSlice } from './slices/formComponentSlice';
import { createInspectorSlice } from './slices/inspectorSlice';
@ -62,6 +63,7 @@ export default create(
...createDebuggerSlice(...state),
...createGitSyncSlice(...state),
...createAiSlice(...state),
...createFixWithAiSlice(...state),
...createWhiteLabellingSlice(...state),
...createFormComponentSlice(...state),
...createInspectorSlice(...state),

View file

@ -578,6 +578,16 @@ export function convertAllKeysToSnakeCase(o) {
return o;
}
export function convertKeysToCamelCase(object) {
if (_.isEmpty(object)) return null;
return Object.keys(object).reduce((acc, key) => {
acc[_.camelCase(key)] = object[key];
return acc;
}, {});
}
// export function createReferencesLookup(refState, forQueryParams = false, initalLoad = false) {
// if (forQueryParams && _.isEmpty(refState['parameters'])) {
// return { suggestionList: [] };

View file

@ -178,7 +178,7 @@ export const validateProperty = (resolvedProperty, propertyDefinitions, paramNam
return [_valid, errors, newValue];
};
function findDefault(definition, value) {
export function findDefault(definition, value) {
switch (definition.type) {
case 'string':
return '';

View file

@ -264,19 +264,21 @@ class HomePageComponent extends React.Component {
return 'app';
};
createApp = async (appName) => {
createApp = async (appName, type, prompt) => {
let _self = this;
_self.setState({ creatingApp: true });
try {
const data = await appsService.createApp({
icon: sample(iconList),
name: appName,
type: this.props.appType,
prompt,
});
const workspaceId = getWorkspaceId();
_self.props.navigate(`/${workspaceId}/apps/${data.id}`, { state: { commitEnabled: this.state.commitEnabled } });
toast.success(`${this.getAppType()} created successfully!`);
_self.props.navigate(`/${workspaceId}/apps/${data.id}`, {
state: { commitEnabled: this.state.commitEnabled, prompt },
});
this.props.appType !== 'front-end' && toast.success(`${capitalize(this.getAppType())} created successfully!`);
_self.setState({ creatingApp: false });
return true;
} catch (errorResponse) {
@ -476,7 +478,7 @@ class HomePageComponent extends React.Component {
let installedPluginsInfo = [];
try {
if (this.state.dependentPlugins.length) {
({ installedPluginsInfo = [] } = await pluginsService.installDependentPlugins(
({ installedPluginsInfo =[] } = await pluginsService.installDependentPlugins(
this.state.dependentPlugins,
true
));
@ -484,8 +486,7 @@ class HomePageComponent extends React.Component {
if (importJSON.app[0].definition.appV2.type !== this.props.appType) {
toast.error(
`${this.props.appType === 'module' ? 'App' : 'Module'} could not be imported in ${
this.props.appType === 'module' ? 'modules' : 'apps'
`${this.props.appType === 'module' ? 'App' : 'Module'} could not be imported in ${this.props.appType === 'module' ? 'modules' : 'apps'
} section. Switch to ${this.props.appType === 'module' ? 'apps' : 'modules'} section and try again.`,
{ style: { maxWidth: '425px' } }
);
@ -1223,9 +1224,8 @@ class HomePageComponent extends React.Component {
<div className="groups-list">
<div
className={`border rounded text-sm container ${
missingGroupsExpanded ? 'max-h-48 overflow-y-auto' : ''
}`}
className={`border rounded text-sm container ${missingGroupsExpanded ? 'max-h-48 overflow-y-auto' : ''
}`}
>
<div style={{ color: 'var(--text-placeholder)' }} className="tj-text-xsm font-weight-500">
User groups
@ -1301,8 +1301,8 @@ class HomePageComponent extends React.Component {
this.props.appType === 'workflow'
? 'homePage.deleteWorkflowAndData'
: this.props.appType === 'front-end'
? 'homePage.deleteAppAndData'
: deleteModuleText,
? 'homePage.deleteAppAndData'
: deleteModuleText,
{
appName: appToBeDeleted?.name,
}
@ -1567,11 +1567,10 @@ class HomePageComponent extends React.Component {
{this.props.appType === 'module'
? 'Create new module'
: this.props.t(
`${
this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage'
}.header.createNewApplication`,
'Create new app'
)}
`${this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage'
}.header.createNewApplication`,
'Create new app'
)}
</>
</Button>
<Dropdown.Toggle
@ -1632,8 +1631,8 @@ class HomePageComponent extends React.Component {
classes="mb-3 small"
limits={
workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
? workflowInstanceLevelLimit
: workflowWorkspaceLevelLimit
}
@ -1732,8 +1731,8 @@ class HomePageComponent extends React.Component {
appType={this.props.appType}
workflowsLimit={
workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
? workflowInstanceLevelLimit
: workflowWorkspaceLevelLimit
}

View file

@ -3,7 +3,7 @@ import { CopyToClipboard } from 'react-copy-to-clipboard';
import { toast } from 'react-hot-toast';
import { ToolTip } from '@/_components/ToolTip';
export const CopyToClipboardComponent = ({ data, callback, useCopyIcon }) => {
export const CopyToClipboardComponent = ({ children, data, callback, useCopyIcon }) => {
const [copied, setCopied] = React.useState(false);
const dataToCopy = callback(data);
const message = 'Copied to clipboard';
@ -30,12 +30,14 @@ export const CopyToClipboardComponent = ({ data, callback, useCopyIcon }) => {
toast.success(message, { position: 'top-center' });
}}
>
<span
style={{ height: '13px', width: '13px', marginBottom: useCopyIcon ? '8px' : '4px' }}
className="mx-1 copy-to-clipboard"
>
{useCopyIcon ? <CustomCopyIcon /> : <DefaultCopyIcon />}
</span>
{children ?? (
<span
style={{ height: '13px', width: '13px', marginBottom: useCopyIcon ? '8px' : '4px' }}
className="mx-1 copy-to-clipboard"
>
{useCopyIcon ? <CustomCopyIcon /> : <DefaultCopyIcon />}
</span>
)}
</CopyToClipboard>
</ToolTip>
);

View file

@ -4,7 +4,6 @@ import { fetchEventSource } from '@microsoft/fetch-event-source';
export const aiService = {
generateApp,
createComponent,
createQuery,
updateComponent,
createEvent,
@ -21,8 +20,15 @@ export const aiService = {
approvePrd,
getCopilotSuggestion,
getCreditBalance,
fixWithAI,
fixLayout,
};
async function fixLayout(body) {
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
return fetch(`${config.apiUrl}/ai/fixLayout`, requestOptions).then(handleResponse);
}
function enrichPrompt(prompt) {
const body = {
prompt,
@ -53,14 +59,6 @@ function generateApp(prompt) {
return fetch(`${config.apiUrl}/ai/generateApp`, requestOptions).then(handleResponse);
}
function createComponent(prompt) {
const body = {
prompt,
};
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
return fetch(`${config.apiUrl}/agents/create-components`, requestOptions).then(handleResponse);
}
function createQuery(prompt) {
const body = {
prompt,
@ -225,10 +223,15 @@ async function approvePrd(body, onMessage) {
async function getCopilotSuggestion(body) {
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
return fetch(`${config.apiUrl}/agents/copilot`, requestOptions).then(handleResponse);
return fetch(`${config.apiUrl}/ai/copilot`, requestOptions).then(handleResponse);
}
async function getCreditBalance() {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/ai/get-credits-balance`, requestOptions).then(handleResponse);
}
async function fixWithAI(body) {
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
return fetch(`${config.apiUrl}/ai/fix-with-ai`, requestOptions).then(handleResponse);
}

View file

@ -140,7 +140,7 @@
--background-warning-weak: #301100;
--background-error-strong: #D03F43;
--background-error-weak: #390809;
--background-Inverse: #FAFCFF;
--background-inverse: #FAFCFF;
//text

View file

@ -12,6 +12,7 @@
&.tooljetai {
width: 440px;
overflow: auto !important;
}
select {

View file

@ -209,6 +209,32 @@
}
}
@layer utilities {
.tw-hide-scrollbar {
// scrollbar-color: transparent transparent;
&::-webkit-scrollbar-thumb {
background: transparent;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.tw-show-scrollbar {
// scrollbar-color: var(--slate8);
&::-webkit-scrollbar-thumb {
background: var(--slate8);
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
}
// variables
$border-radius: 4px;
@ -16276,65 +16302,6 @@ fieldset:disabled {
}
div.actions {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
}
.left button {
background: red;
height: 28px;
width: 28px;
background-color: transparent !important;
border: none;
outline: none;
display: flex;
align-items: center;
&:hover {
background-color: #f1f1f1 !important;
}
// &:focus {
// border: 1px solid var(--border-default, #CCD1D5);
// background: var(--button-outline, #FFF);
// box-shadow: 0px 0px 0px 2px var(--Interactive-focusActive, #4368E3);
// }
}
button.submit {
height: 28px;
width: 28px;
border-radius: 6px;
border: 1px solid var(--border-weak, #E4E7EB);
background: var(--button-secondary, #FFF);
/* Elevations/100 */
box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10));
display: flex;
align-items: center;
justify-content: center;
&:hover {
border: 1px solid var(--border-default, #CCD1D5);
background: var(--button-outline-hover, rgba(136, 144, 153, 0.12));
}
// &:focus {
// border: 1px solid var(--border-default, #CCD1D5);
// background: var(--button-outline, #FFF);
// box-shadow: 0px 0px 0px 2px var(--Interactive-focusActive, #4368E3);
// }
}
}
}
.example-prompts {
@ -16392,7 +16359,6 @@ fieldset:disabled {
}
section.ai-message-prompt-input-wrapper {
border-radius: 6px;
border: 1px solid var(--border-weak, #E4E7EB);
background: var(--background-surface-layer-01, #FFFFFF);
@ -16400,10 +16366,8 @@ section.ai-message-prompt-input-wrapper {
padding: 12px;
&.inside-appbuilder {
border: none;
background: var(--slate2);
border: 1px solid var(--border-weak, #4368E3);
margin: 16px;
border-radius: 6px;
/* Elevations/300 */
box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 4px 8px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10));
@ -16443,71 +16407,6 @@ section.ai-message-prompt-input-wrapper {
.input:not(:empty)::before {
display: none;
}
div.actions {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
}
.left button {
background: red;
height: 28px;
width: 28px;
background-color: transparent !important;
border: none;
outline: none;
display: flex;
align-items: center;
&:hover {
background-color: #f1f1f1 !important;
}
// &:focus {
// border: 1px solid var(--border-default, #CCD1D5);
// background: var(--button-outline, #FFF);
// box-shadow: 0px 0px 0px 2px var(--Interactive-focusActive, #4368E3);
// }
}
button.submit {
height: 28px;
width: 28px;
border-radius: 6px;
border: 1px solid var(--border-weak, #E4E7EB);
background: var(--button-secondary, #FFF);
/* Elevations/100 */
box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10));
display: flex;
align-items: center;
justify-content: center;
&:hover {
border: 1px solid var(--border-default, #CCD1D5);
background: var(--button-outline-hover, rgba(136, 144, 153, 0.12));
}
// when disabled
&:disabled {
border-radius: 6px;
border: 1px solid var(--border-weak, #E4E7EB);
background: linear-gradient(0deg, var(--button-outline-disabled, #FFF) 0%, var(--button-outline-disabled, #FFF) 100%), var(--button-secondary, #FFF);
}
// &:focus {
// border: 1px solid var(--border-default, #CCD1D5);
// background: var(--button-outline, #FFF);
// box-shadow: 0px 0px 0px 2px var(--Interactive-focusActive, #4368E3);
// }
}
}
}
@ -17027,12 +16926,6 @@ section.ai-message-prompt-input-wrapper {
margin-top: 6px;
}
button {
border: none;
outline: none;
}
&.dark-theme {
background-color: #1f2936;
@ -17049,6 +16942,7 @@ section.ai-message-prompt-input-wrapper {
justify-content: center;
background-color: transparent;
margin-right: 5px;
border: none;
svg {
transition: ease-in-out 0.2s;
@ -17077,42 +16971,10 @@ section.ai-message-prompt-input-wrapper {
z-index: 2;
background-color: #fff;
padding: 8px 16px 0 16px;
padding: 0.5rem 1rem;
border-bottom: 1px solid var(--border-weak, #E4E7EB);
.conversation-type-toggle {
margin-top: 4px;
display: flex;
align-items: flex-start;
gap: 4px;
button {
padding: 6px 7px;
color: var(--text-default, #1B1F24);
background: transparent;
position: relative;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 18px;
/* 150% */
&.active {
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: var(--primary-accent-strong);
}
}
}
}
section {
display: flex;
align-items: center;
@ -17126,10 +16988,6 @@ section.ai-message-prompt-input-wrapper {
font-weight: 500;
line-height: 20px;
margin-bottom: 0;
.highlight {
color: #FF5F6D
}
}
button {
@ -17158,8 +17016,9 @@ section.ai-message-prompt-input-wrapper {
.conversation-wrapper {
overflow-y: auto;
flex-grow: 1;
padding: 32px;
padding: 1rem;
z-index: 2;
scroll-behavior: smooth;
// remote the scrollbar
&::-webkit-scrollbar {
@ -17419,6 +17278,7 @@ section.ai-message-prompt-input-wrapper {
border-radius: 5px;
margin-top: 2px;
align-self: flex-start;
border: none;
&.selected {
background: var(--button-primary, #4368E3);
@ -17622,59 +17482,6 @@ section.ai-message-prompt-input-wrapper {
}
}
.action-buttons {
margin-top: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
button {
border: none;
outline: none;
background-color: transparent;
}
.secondary-btn {
padding: 5px 10px;
display: flex;
align-items: center;
gap: 6px;
span {
color: var(--text-default, #1B1F24);
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 18px;
}
&:hover {
background: var(--interactive-hover, rgba(136, 144, 153, 0.12));
}
}
.primary-btn {
padding: 5px 10px;
display: flex;
align-items: center;
gap: 6px;
color: var(--text-default, #1B1F24);
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 18px;
border-radius: 6px;
border: 1px solid var(--border-weak, #E4E7EB);
background: var(--button-secondary, #FFF);
box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10));
&:hover {
background: var(--interactive-hover, rgba(136, 144, 153, 0.12));
}
}
}
.options {
display: flex;
padding: 4px;
@ -17727,6 +17534,8 @@ section.ai-message-prompt-input-wrapper {
&.user {
display: flex;
max-width: 90%;
margin-left: auto;
/* 150% */
.user-message {

View file

@ -0,0 +1,12 @@
import React from 'react';
export default function AddAppIcon({ width = 15, height = 15, fill = '#ACB2B9' }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 15 15" fill="none">
<path
d="M2.82161 6.43229C2.65634 6.43229 2.5178 6.37639 2.40599 6.26458C2.29418 6.15278 2.23828 6.01424 2.23828 5.84896V2.34896C2.23828 2.18368 2.29418 2.04514 2.40599 1.93333C2.5178 1.82153 2.65634 1.76562 2.82161 1.76562H6.32161C6.48689 1.76562 6.62543 1.82153 6.73724 1.93333C6.84905 2.04514 6.90495 2.18368 6.90495 2.34896V5.84896C6.90495 6.01424 6.84905 6.15278 6.73724 6.26458C6.62543 6.37639 6.48689 6.43229 6.32161 6.43229H2.82161ZM8.65495 6.43229C8.48967 6.43229 8.35113 6.37639 8.23932 6.26458C8.12752 6.15278 8.07161 6.01424 8.07161 5.84896V2.34896C8.07161 2.18368 8.12752 2.04514 8.23932 1.93333C8.35113 1.82153 8.48967 1.76562 8.65495 1.76562H12.1549C12.3202 1.76562 12.4588 1.82153 12.5706 1.93333C12.6824 2.04514 12.7383 2.18368 12.7383 2.34896V5.84896C12.7383 6.01424 12.6824 6.15278 12.5706 6.26458C12.4588 6.37639 12.3202 6.43229 12.1549 6.43229H8.65495ZM2.82161 12.2656C2.65634 12.2656 2.5178 12.2097 2.40599 12.0979C2.29418 11.9861 2.23828 11.8476 2.23828 11.6823V8.18229C2.23828 8.01701 2.29418 7.87847 2.40599 7.76667C2.5178 7.65486 2.65634 7.59896 2.82161 7.59896H6.32161C6.48689 7.59896 6.62543 7.65486 6.73724 7.76667C6.84905 7.87847 6.90495 8.01701 6.90495 8.18229V11.6823C6.90495 11.8476 6.84905 11.9861 6.73724 12.0979C6.62543 12.2097 6.48689 12.2656 6.32161 12.2656H2.82161ZM10.4049 12.2656C10.2397 12.2656 10.1011 12.2097 9.98932 12.0979C9.87752 11.9861 9.82161 11.8476 9.82161 11.6823V10.5156H8.64036C8.47509 10.5156 8.33898 10.4597 8.23203 10.3479C8.12509 10.2361 8.07161 10.0976 8.07161 9.93229C8.07161 9.76701 8.12752 9.62847 8.23932 9.51667C8.35113 9.40486 8.48967 9.34896 8.65495 9.34896H9.82161V8.16771C9.82161 8.00243 9.87752 7.86632 9.98932 7.75938C10.1011 7.65243 10.2397 7.59896 10.4049 7.59896C10.5702 7.59896 10.7088 7.65486 10.8206 7.76667C10.9324 7.87847 10.9883 8.01701 10.9883 8.18229V9.34896H12.1695C12.3348 9.34896 12.4709 9.40486 12.5779 9.51667C12.6848 9.62847 12.7383 9.76701 12.7383 9.93229C12.7383 10.0976 12.6824 10.2361 12.5706 10.3479C12.4588 10.4597 12.3202 10.5156 12.1549 10.5156H10.9883V11.6969C10.9883 11.8622 10.9324 11.9983 10.8206 12.1052C10.7088 12.2122 10.5702 12.2656 10.4049 12.2656Z"
fill={fill}
/>
</svg>
);
}

View file

@ -1,14 +1,21 @@
import React from 'react';
const Bug = ({ fill = '#000', width = '25', className = '', viewBox = '0 0 25 25' }) => (
const Bug = ({ fill = '#ACB2B9', width = '14', height = '14', className = '' }) => (
<svg
width={width}
fill={fill}
height={width}
viewBox={viewBox}
className={className}
xmlns="http://www.w3.org/2000/svg"
></svg>
width={width}
height={height}
viewBox="0 0 14 14"
fill="none"
className={className}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.6311 1.88845C9.6311 1.69633 9.56477 1.51208 9.4467 1.37623C9.32863 1.24038 9.1685 1.16406 9.00153 1.16406C8.83456 1.16406 8.67442 1.24038 8.55636 1.37623C8.43829 1.51208 8.37196 1.69633 8.37196 1.88845C8.37192 2.13295 8.3225 2.37409 8.2276 2.59285C8.1327 2.81161 7.9949 3.00199 7.8251 3.14898C7.27987 3.02528 6.7189 3.02528 6.17367 3.14898C6.00403 3.00188 5.86641 2.81145 5.77167 2.5927C5.67692 2.37395 5.62764 2.13286 5.62771 1.88845C5.62771 1.69633 5.56138 1.51208 5.44332 1.37623C5.32525 1.24038 5.16512 1.16406 4.99815 1.16406C4.83117 1.16406 4.67104 1.24038 4.55298 1.37623C4.43491 1.51208 4.36858 1.69633 4.36858 1.88845C4.36884 2.54115 4.55241 3.17628 4.89194 3.69916C4.49235 3.96258 4.13246 4.2987 3.82624 4.69448C3.2387 4.19457 2.5268 3.92578 1.79607 3.92797C1.7134 3.92818 1.63157 3.94712 1.55525 3.98371C1.47894 4.0203 1.40963 4.07383 1.3513 4.14124C1.29296 4.20865 1.24674 4.28863 1.21527 4.37659C1.18379 4.46456 1.16768 4.55879 1.16786 4.65392C1.16804 4.74905 1.1845 4.8432 1.2163 4.93101C1.24811 5.01882 1.29463 5.09856 1.35322 5.16568C1.4118 5.2328 1.48131 5.28599 1.55776 5.32221C1.63421 5.35842 1.71611 5.37695 1.79879 5.37675C2.28482 5.37489 2.75693 5.56343 3.13746 5.91133C2.98039 6.30583 2.87004 6.72279 2.80935 7.15106H1.79607C1.6291 7.15106 1.46897 7.22738 1.3509 7.36323C1.23284 7.49908 1.16651 7.68333 1.16651 7.87545C1.16651 8.06757 1.23284 8.25182 1.3509 8.38767C1.46897 8.52352 1.6291 8.59984 1.79607 8.59984H2.79037C2.84279 9.04706 2.94765 9.47451 3.09769 9.87493C2.72379 10.2011 2.2671 10.3769 1.79788 10.3752C1.63091 10.3746 1.47059 10.4504 1.35218 10.5859C1.23378 10.7213 1.16699 10.9054 1.16651 11.0975C1.16603 11.2896 1.2319 11.4741 1.34962 11.6103C1.46735 11.7466 1.62729 11.8234 1.79427 11.824C2.49931 11.8266 3.18761 11.5769 3.76387 11.1095C4.16219 11.6496 4.65803 12.0834 5.21688 12.3807C5.77573 12.678 6.38416 12.8315 6.99984 12.8307C7.61551 12.8315 8.22395 12.678 8.78279 12.3807C9.34164 12.0834 9.83748 11.6496 10.2358 11.1095C10.812 11.5771 11.5003 11.8268 12.2054 11.824C12.3724 11.8234 12.5323 11.7466 12.65 11.6103C12.7678 11.4741 12.8336 11.2896 12.8332 11.0975C12.8327 10.9054 12.7659 10.7213 12.6475 10.5859C12.5291 10.4504 12.3688 10.3746 12.2018 10.3752C11.7326 10.3769 11.2759 10.2011 10.902 9.87493C11.052 9.47555 11.1569 9.04706 11.2093 8.59984H12.2036C12.3706 8.59984 12.5307 8.52352 12.6488 8.38767C12.7668 8.25182 12.8332 8.06757 12.8332 7.87545C12.8332 7.68333 12.7668 7.49908 12.6488 7.36323C12.5307 7.22738 12.3706 7.15106 12.2036 7.15106H11.1912C11.1308 6.72284 11.0208 6.30588 10.864 5.91133C11.2443 5.56365 11.7161 5.37513 12.2018 5.37675C12.3688 5.3773 12.5291 5.30151 12.6475 5.16605C12.7659 5.03059 12.8327 4.84656 12.8332 4.65444C12.8336 4.46232 12.7678 4.27785 12.65 4.14161C12.5323 4.00537 12.3724 3.92852 12.2054 3.92797C11.4741 3.92534 10.7615 4.19414 10.1734 4.69448C9.86715 4.29907 9.50726 3.96331 9.10774 3.7002C9.4472 3.17729 9.63077 2.54114 9.6311 1.88845Z"
fill={fill}
/>
</svg>
);
export default Bug;

View file

@ -0,0 +1,16 @@
import React from 'react';
export default function BulbIcon({ width = 15, height = 14, fill = '#6A727C' }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 15 14" fill="none">
<path
d="M9.70182 10.3516V10.4974C9.70182 11.7861 8.65715 12.8307 7.36849 12.8307C6.07983 12.8307 5.03516 11.7861 5.03516 10.4974V10.3516H9.70182Z"
fill={fill}
/>
<path
d="M9.70182 9.47658V9.20749C9.70182 8.82611 9.89631 8.47692 10.1734 8.21489C10.9606 7.47053 11.4518 6.41632 11.4518 5.2474C11.4518 2.99223 9.62365 1.16406 7.36849 1.16406C5.11333 1.16406 3.28516 2.99223 3.28516 5.2474C3.28516 6.41632 3.77633 7.47053 4.56355 8.21489C4.84067 8.47692 5.03516 8.82611 5.03516 9.20749V9.47658H6.93099V6.59528L5.89246 5.55675C5.72161 5.3859 5.72161 5.10889 5.89246 4.93804C6.06332 4.76718 6.34033 4.76718 6.51118 4.93804L7.36849 5.79534L8.2258 4.93804C8.39665 4.76718 8.67366 4.76718 8.84452 4.93804C9.01537 5.10889 9.01537 5.3859 8.84452 5.55675L7.80599 6.59528V9.47658H9.70182Z"
fill={fill}
/>
</svg>
);
}

View file

@ -0,0 +1,14 @@
import React from 'react';
export default function CheckCircle({ width = '14', height = '14', fill = '#1E823B' }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 14 14" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.99935 12.8307C10.221 12.8307 12.8327 10.2191 12.8327 6.9974C12.8327 3.77573 10.221 1.16406 6.99935 1.16406C3.77769 1.16406 1.16602 3.77573 1.16602 6.9974C1.16602 10.2191 3.77769 12.8307 6.99935 12.8307ZM9.67803 5.51602C9.82637 5.32529 9.79201 5.05042 9.60128 4.90207C9.41056 4.75373 9.13569 4.78809 8.98734 4.97882L6.64993 7.98407C6.59795 8.05089 6.50018 8.05957 6.43726 8.00293L4.95869 6.67222C4.77909 6.51059 4.50247 6.52515 4.34083 6.70474C4.17919 6.88434 4.19375 7.16097 4.37335 7.32261L5.85191 8.65331C6.2924 9.04976 6.97678 8.98905 7.34061 8.52127L9.67803 5.51602Z"
fill={fill}
/>
</svg>
);
}

View file

@ -0,0 +1,12 @@
import React from 'react';
export default function DottedArrow({ width = '37', height = '8', fill = '#ACB2B9' }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 37 8" fill="none">
<path
d="M0.5 3.5C0.223858 3.5 0 3.72386 0 4C0 4.27614 0.223858 4.5 0.5 4.5V3.5ZM36.8536 4.35355C37.0488 4.15829 37.0488 3.84171 36.8536 3.64645L33.6716 0.464466C33.4763 0.269204 33.1597 0.269204 32.9645 0.464466C32.7692 0.659728 32.7692 0.976311 32.9645 1.17157L35.7929 4L32.9645 6.82843C32.7692 7.02369 32.7692 7.34027 32.9645 7.53553C33.1597 7.7308 33.4763 7.7308 33.6716 7.53553L36.8536 4.35355ZM2 4.5C2.27614 4.5 2.5 4.27614 2.5 4C2.5 3.72386 2.27614 3.5 2 3.5V4.5ZM5 3.5C4.72386 3.5 4.5 3.72386 4.5 4C4.5 4.27614 4.72386 4.5 5 4.5V3.5ZM8 4.5C8.27614 4.5 8.5 4.27614 8.5 4C8.5 3.72386 8.27614 3.5 8 3.5V4.5ZM11 3.5C10.7239 3.5 10.5 3.72386 10.5 4C10.5 4.27614 10.7239 4.5 11 4.5V3.5ZM14 4.5C14.2761 4.5 14.5 4.27614 14.5 4C14.5 3.72386 14.2761 3.5 14 3.5V4.5ZM17 3.5C16.7239 3.5 16.5 3.72386 16.5 4C16.5 4.27614 16.7239 4.5 17 4.5V3.5ZM20 4.5C20.2761 4.5 20.5 4.27614 20.5 4C20.5 3.72386 20.2761 3.5 20 3.5V4.5ZM23 3.5C22.7239 3.5 22.5 3.72386 22.5 4C22.5 4.27614 22.7239 4.5 23 4.5V3.5ZM26 4.5C26.2761 4.5 26.5 4.27614 26.5 4C26.5 3.72386 26.2761 3.5 26 3.5V4.5ZM29 3.5C28.7239 3.5 28.5 3.72386 28.5 4C28.5 4.27614 28.7239 4.5 29 4.5V3.5ZM32 4.5C32.2761 4.5 32.5 4.27614 32.5 4C32.5 3.72386 32.2761 3.5 32 3.5V4.5ZM35 3.5C34.7239 3.5 34.5 3.72386 34.5 4C34.5 4.27614 34.7239 4.5 35 4.5V3.5ZM0.5 4.5H2V3.5H0.5V4.5ZM5 4.5H8V3.5H5V4.5ZM11 4.5H14V3.5H11V4.5ZM17 4.5H20V3.5H17V4.5ZM23 4.5H26V3.5H23V4.5ZM29 4.5H32V3.5H29V4.5ZM35 4.5H36.5V3.5H35V4.5Z"
fill={fill}
/>
</svg>
);
}

View file

@ -0,0 +1,24 @@
import React from 'react';
export default function LoadingCircleIcon({
className,
width = '13',
height = '13',
fill = 'var(--icon-strong, #6A727C)',
}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="0 0 13 13"
fill="none"
className={className}
>
<path
d="M6.225 11.475C5.59167 11.4083 5.00208 11.2313 4.45625 10.9438C3.91042 10.6562 3.4375 10.2896 3.0375 9.84375C2.6375 9.39792 2.32292 8.8875 2.09375 8.3125C1.86458 7.7375 1.75 7.12917 1.75 6.4875C1.75 5.19583 2.17708 4.07708 3.03125 3.13125C3.88542 2.18542 4.95417 1.64167 6.2375 1.5V2.5C5.22917 2.64167 4.39583 3.08958 3.7375 3.84375C3.07917 4.59792 2.75 5.47917 2.75 6.4875C2.75 7.49583 3.07917 8.37708 3.7375 9.13125C4.39583 9.88542 5.225 10.3333 6.225 10.475V11.475ZM7.225 11.475V10.475C7.58333 10.425 7.92708 10.3292 8.25625 10.1875C8.58542 10.0458 8.89167 9.86667 9.175 9.65L9.9 10.375C9.50833 10.6833 9.0875 10.9313 8.6375 11.1188C8.1875 11.3063 7.71667 11.425 7.225 11.475ZM9.2 3.325C8.90833 3.10833 8.59792 2.92917 8.26875 2.7875C7.93958 2.64583 7.59583 2.55 7.2375 2.5V1.5C7.72917 1.55 8.2 1.66875 8.65 1.85625C9.1 2.04375 9.51667 2.29167 9.9 2.6L9.2 3.325ZM10.6 9.65L9.9 8.9375C10.1167 8.65417 10.2917 8.34792 10.425 8.01875C10.5583 7.68958 10.65 7.34583 10.7 6.9875H11.725C11.6583 7.47917 11.5333 7.95208 11.35 8.40625C11.1667 8.86042 10.9167 9.275 10.6 9.65ZM10.7 5.9875C10.65 5.62917 10.5583 5.28542 10.425 4.95625C10.2917 4.62708 10.1167 4.32083 9.9 4.0375L10.6 3.325C10.9167 3.7 11.1708 4.11458 11.3625 4.56875C11.5542 5.02292 11.675 5.49583 11.725 5.9875H10.7Z"
fill={fill}
/>
</svg>
);
}

View file

@ -0,0 +1,18 @@
import React from 'react';
export default function PageIcon({ width = 15, height = 15, fill = '#ACB2B9' }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 15 15" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.66797 3.15625C1.66797 2.18975 2.45147 1.40625 3.41797 1.40625H9.2513C10.2178 1.40625 11.0013 2.18975 11.0013 3.15625V9.86458H5.9263C5.66857 9.86458 5.45964 10.0735 5.45964 10.3313C5.45964 11.3622 4.60943 12.1979 3.5785 12.1979C2.53146 12.1979 1.66797 11.3491 1.66797 10.3021V3.15625ZM4.0013 3.88542C3.75968 3.88542 3.5638 4.08129 3.5638 4.32292C3.5638 4.56454 3.75968 4.76042 4.0013 4.76042H8.66797C8.90959 4.76042 9.10547 4.56454 9.10547 4.32292C9.10547 4.08129 8.90959 3.88542 8.66797 3.88542H4.0013ZM3.5638 7.23958C3.5638 6.99796 3.75968 6.80208 4.0013 6.80208H6.33464C6.57626 6.80208 6.77214 6.99796 6.77214 7.23958C6.77214 7.48121 6.57626 7.67708 6.33464 7.67708H4.0013C3.75968 7.67708 3.5638 7.48121 3.5638 7.23958Z"
fill={fill}
/>
<path
d="M13.2626 11.317C13.0057 12.3264 12.0907 13.0729 11.0013 13.0729H4.0013C5.09067 13.0729 6.00567 12.3264 6.26264 11.317C6.34212 11.0048 6.5958 10.7396 6.91797 10.7396H12.7513C13.0735 10.7396 13.3421 11.0048 13.2626 11.317Z"
fill={fill}
/>
</svg>
);
}

View file

@ -1,13 +1,13 @@
import React from 'react';
const Retry = () => {
const Retry = ({ fill = '#ACB2B9' }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={12} height={13} viewBox="0 0 12 13" fill="none">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6.53571 4.37905C6.53571 4.47378 6.49809 4.56462 6.43111 4.63159L4.46682 6.59588C4.36468 6.69803 4.21106 6.72858 4.07761 6.6733C3.94416 6.61802 3.85714 6.4878 3.85714 6.34334V5.09334H3.14286C2.74837 5.09334 2.42857 5.41314 2.42857 5.80763V8.48619C2.42857 8.88069 2.74837 9.20048 3.14286 9.20048H8.85714C9.25164 9.20048 9.57143 8.88069 9.57143 8.48619V5.80763C9.57143 5.41314 9.25164 5.09334 8.85714 5.09334H8.32143C7.92694 5.09334 7.60714 4.77355 7.60714 4.37905C7.60714 3.98457 7.92694 3.66477 8.32143 3.66477H8.85714C10.0406 3.66477 11 4.62416 11 5.80763V8.48619C11 9.66969 10.0406 10.629 8.85714 10.629H3.14286C1.95939 10.629 1 9.66969 1 8.48619V5.80763C1 4.62416 1.95939 3.66478 3.14286 3.66478H3.85714V2.41477C3.85714 2.27032 3.94416 2.1401 4.07761 2.08481C4.21106 2.02954 4.36468 2.06009 4.46682 2.16223L6.43111 4.12652C6.49809 4.1935 6.53571 4.28433 6.53571 4.37905Z"
fill="#ACB2B9"
fill={fill}
/>
</svg>
);

View file

@ -0,0 +1,12 @@
import React from 'react';
export default function SolidArrow({ width = '37', height = '8', fill = 'black' }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 37 8" fill="none">
<path
d="M0.5 3.5C0.223858 3.5 0 3.72386 0 4C0 4.27614 0.223858 4.5 0.5 4.5V3.5ZM36.8536 4.35355C37.0488 4.15829 37.0488 3.84171 36.8536 3.64645L33.6716 0.464466C33.4763 0.269204 33.1597 0.269204 32.9645 0.464466C32.7692 0.659728 32.7692 0.976311 32.9645 1.17157L35.7929 4L32.9645 6.82843C32.7692 7.02369 32.7692 7.34027 32.9645 7.53553C33.1597 7.7308 33.4763 7.7308 33.6716 7.53553L36.8536 4.35355ZM0.5 4.5H36.5V3.5H0.5V4.5Z"
fill={fill}
/>
</svg>
);
}

View file

@ -1,5 +1,6 @@
import React from 'react';
import Apps from './Apps.jsx';
import AddAppIcon from './AddApp.jsx';
import Archive from './Archive.jsx';
import ArrowBack from './ArrowBack.jsx';
import ArrowDown from './ArrowDown.jsx';
@ -11,8 +12,10 @@ import ArrowSortRectangle from './ArrowSortRectangle.jsx';
import AddNavItemURL from './AddNavItemURL.jsx';
import ArrowTransfer from './ArrowTransfer.jsx';
import ArrowUp from './ArrowUp.jsx';
import BulbIcon from './Bulb.jsx';
import BookSearch from './BookSearch.jsx';
import Branch from './Branch.jsx';
import Bug from './Bug.jsx';
import Debugger from './Debugger.jsx';
import Calender from './Calender.jsx';
import CheckRectangle from './CheckRectangle.jsx';
@ -104,6 +107,7 @@ import RemoveRectangle from './RemoveRectangle.jsx';
import RightArrow from './RightArrow.jsx';
import RightOuterJoin from './RightOuterJoin.jsx';
import Row from './Row.jsx';
import Retry from './Retry.jsx';
import SadRectangle from './SadRectangle.jsx';
import Search from './Search.jsx';
import SearchMinus from './SearchMinus.jsx';
@ -219,6 +223,7 @@ import SectionExpand from './SectionExpand.jsx';
import Reset from './Reset.jsx';
import Outbound from './Outbound.jsx';
import AddPageGroupIcon from './AddPageGroup.jsx';
import PageIcon from './PageIcon.jsx';
import EnterpriseNew from './EnterpriseNew.jsx';
import ArrowReturn01 from './ArrowReturn01.jsx';
import ArrowUp01 from './ArrowUp01.jsx';
@ -286,6 +291,8 @@ const Icon = (props) => {
return <AlignRight {...props} />;
case 'apps':
return <Apps {...props} />;
case 'add-app':
return <AddAppIcon {...props} />;
case 'archive':
return <Archive {...props} />;
case 'arrowback':
@ -316,10 +323,14 @@ const Icon = (props) => {
return <Asterix {...props} />;
case 'auditlogs':
return <AuditLogs {...props} />;
case 'bulb':
return <BulbIcon {...props} />;
case 'booksearch':
return <BookSearch {...props} />;
case 'branch':
return <Branch {...props} />;
case 'bug':
return <Bug {...props} />;
case 'debugger':
return <Debugger {...props} />;
case 'calender':
@ -516,6 +527,8 @@ const Icon = (props) => {
return <Page {...props} />;
case 'pageAdd':
return <PageAdd {...props} />;
case 'page-icon':
return <PageIcon {...props} />;
case 'pageUpload':
return <PageUpload {...props} />;
case 'pin':
@ -556,6 +569,8 @@ const Icon = (props) => {
return <Row {...props} />;
case 'reset':
return <Reset {...props} />;
case 'retry':
return <Retry {...props} />;
case 'sadrectangle':
return <SadRectangle {...props} />;
case 'search':

View file

@ -1,5 +1,9 @@
import cx from 'classnames';
import { twMerge } from 'tailwind-merge';
import { extendTailwindMerge } from 'tailwind-merge';
const twMerge = extendTailwindMerge({
prefix: 'tw-',
});
export function cn(...inputs) {
return twMerge(cx(inputs));

View file

@ -1,7 +1,13 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class'],
content: ['./pages/**/*.{js,jsx}', './components/**/*.{js,jsx}', './app/**/*.{js,jsx}', './src/**/*.{js,jsx}'],
content: [
'./pages/**/*.{js,jsx}',
'./components/**/*.{js,jsx}',
'./app/**/*.{js,jsx}',
'./src/**/*.{js,jsx}',
'./ee/**/*.{js,jsx}',
],
prefix: 'tw-',
corePlugins: {
preflight: false,
@ -22,7 +28,7 @@ module.exports = {
'background-error-weak': 'var(--background-error-weak)',
'background-warning-stong': 'var(--background-warning-stong)',
'background-warning-weak': 'var(--background-warning-weak)',
'background-inverse': 'var(--background-Inverse)',
'background-inverse': 'var(--background-inverse)',
'text-default': 'var(--text-default)',
'text-medium': 'var(--text-medium)',
'text-placeholder': 'var(--text-placeholder)',

View file

@ -9,9 +9,11 @@ const { sentryWebpackPlugin } = require('@sentry/webpack-plugin');
const fs = require('fs');
const versionPath = path.resolve(__dirname, '.version');
const version = fs.readFileSync(versionPath, 'utf-8').trim();
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const environment = process.env.NODE_ENV === 'production' ? 'production' : 'development';
const edition = process.env.TOOLJET_EDITION;
const isDevEnv = process.env.NODE_ENV === 'development';
// Create path to empty module
const emptyModulePath = path.resolve(__dirname, 'src/modules/emptyModule');
@ -76,6 +78,10 @@ if (process.env.APM_VENDOR === 'sentry') {
);
}
if (isDevEnv) {
plugins.push(new ReactRefreshWebpackPlugin({ overlay: false }));
}
module.exports = {
mode: environment,
optimization: {
@ -122,7 +128,7 @@ module.exports = {
'@cloud/modules': emptyModulePath,
},
},
devtool: environment === 'development' ? 'source-map' : 'hidden-source-map',
devtool: environment === 'development' ? 'eval-source-map' : 'hidden-source-map',
module: {
rules: [
{
@ -197,8 +203,9 @@ module.exports = {
loader: 'babel-loader',
options: {
plugins: [
isDevEnv && require.resolve('react-refresh/babel'),
['import', { libraryName: 'lodash', libraryDirectory: '', camel2DashComponentName: false }, 'lodash'],
],
].filter(Boolean),
},
},
},

View file

@ -0,0 +1,62 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
export class AddAiGenerationFlagsInApp1750927057649 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Add is_initialised_from_prompt column
await queryRunner.addColumn(
'apps',
new TableColumn({
name: 'is_initialised_from_prompt',
type: 'boolean',
default: false,
isNullable: false,
})
);
// Add app_generated_from_prompt column
await queryRunner.addColumn(
'apps',
new TableColumn({
name: 'app_generated_from_prompt',
type: 'boolean',
default: false,
isNullable: false,
})
);
// Add ai_generation_metadata column
await queryRunner.addColumn(
'apps',
new TableColumn({
name: 'ai_generation_metadata',
type: 'jsonb',
isNullable: true,
})
);
// Create app_builder_mode enum type
await queryRunner.query(`CREATE TYPE "app_builder_mode" AS ENUM ('ai', 'visual')`);
// Add app_builder_mode column
await queryRunner.addColumn(
'apps',
new TableColumn({
name: 'app_builder_mode',
type: 'app_builder_mode',
default: "'visual'",
isNullable: false,
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Remove columns in reverse order
await queryRunner.dropColumn('apps', 'app_builder_mode');
await queryRunner.query('DROP TYPE "app_builder_mode"');
await queryRunner.dropColumn('apps', 'ai_generation_metadata');
await queryRunner.dropColumn('apps', 'app_generated_from_prompt');
await queryRunner.dropColumn('apps', 'is_initialised_from_prompt');
}
}

View file

@ -0,0 +1,81 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
export class AddArtifactsTable1750927083207 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'artifacts',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
isGenerated: true,
generationStrategy: 'uuid',
},
{
name: 'conversation_id',
type: 'uuid',
isNullable: false,
},
{
name: 'message_id',
type: 'uuid',
isNullable: false,
},
{
name: 'content',
type: 'jsonb',
isNullable: false,
},
{
name: 'identifier',
type: 'varchar',
isNullable: false,
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'updated_at',
type: 'timestamp',
default: 'now()',
},
],
}),
true,
);
await queryRunner.createForeignKey(
'artifacts',
new TableForeignKey({
columnNames: ['conversation_id'],
referencedColumnNames: ['id'],
referencedTableName: 'ai_conversations',
onDelete: 'CASCADE',
}),
);
await queryRunner.createForeignKey(
'artifacts',
new TableForeignKey({
columnNames: ['message_id'],
referencedColumnNames: ['id'],
referencedTableName: 'ai_conversation_messages',
onDelete: 'CASCADE',
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable('artifacts');
if (table) {
const foreignKeys = table.foreignKeys;
await Promise.all(foreignKeys.map(foreignKey => queryRunner.dropForeignKey('artifacts', foreignKey)));
}
await queryRunner.dropTable('artifacts');
}
}

@ -1 +1 @@
Subproject commit ace9a23a0f3075355d709943d9b7a1f7179f6dfd
Subproject commit 19fa2f86c408c63b5015af41d9f360aaf68c3a38

View file

@ -62,11 +62,13 @@
"isolated-vm": "^5.0.4",
"joi": "^17.4.1",
"js-base64": "^3.7.2",
"jsonrepair": "^3.12.0",
"jszip": "^3.10.1",
"ldapjs": "^3.0.7",
"lodash": "^4.17.21",
"module-from-string": "^3.3.1",
"moment": "^2.29.4",
"neo4j-driver": "^5.28.1",
"nest-winston": "^1.9.4",
"nestjs-pino": "^1.4.0",
"node-sql-parser": "^5.3.1",
@ -14281,6 +14283,15 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonrepair": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.12.0.tgz",
"integrity": "sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w==",
"license": "ISC",
"bin": {
"jsonrepair": "bin/cli.js"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@ -15566,6 +15577,67 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"license": "MIT"
},
"node_modules/neo4j-driver": {
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/neo4j-driver/-/neo4j-driver-5.28.1.tgz",
"integrity": "sha512-jbyBwyM0a3RLGcP43q3hIxPUPxA+1bE04RovOKdNAS42EtBMVCKcPSeOvWiHxgXp1ZFd0a8XqK+7LtguInOLUg==",
"license": "Apache-2.0",
"dependencies": {
"neo4j-driver-bolt-connection": "5.28.1",
"neo4j-driver-core": "5.28.1",
"rxjs": "^7.8.1"
}
},
"node_modules/neo4j-driver-bolt-connection": {
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-5.28.1.tgz",
"integrity": "sha512-nY8GBhjOW7J0rDtpiyJn6kFdk2OiNVZZhZrO8//mwNXnf5VQJ6HqZQTDthH/9pEaX0Jvbastz1xU7ZL8xzqY0w==",
"license": "Apache-2.0",
"dependencies": {
"buffer": "^6.0.3",
"neo4j-driver-core": "5.28.1",
"string_decoder": "^1.3.0"
}
},
"node_modules/neo4j-driver-bolt-connection/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/neo4j-driver-bolt-connection/node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/neo4j-driver-core": {
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/neo4j-driver-core/-/neo4j-driver-core-5.28.1.tgz",
"integrity": "sha512-14vN8TlxC0JvJYfjWic5PwjsZ38loQLOKFTXwk4fWLTbCk6VhrhubB2Jsy9Rz+gM6PtTor4+6ClBEFDp1q/c8g==",
"license": "Apache-2.0"
},
"node_modules/nest-winston": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/nest-winston/-/nest-winston-1.10.2.tgz",

View file

@ -100,11 +100,13 @@
"isolated-vm": "^5.0.4",
"joi": "^17.4.1",
"js-base64": "^3.7.2",
"jsonrepair": "^3.12.0",
"jszip": "^3.10.1",
"ldapjs": "^3.0.7",
"lodash": "^4.17.21",
"module-from-string": "^3.3.1",
"moment": "^2.29.4",
"neo4j-driver": "^5.28.1",
"nest-winston": "^1.9.4",
"nestjs-pino": "^1.4.0",
"node-sql-parser": "^5.3.1",

View file

@ -7,9 +7,11 @@ import {
ManyToOne,
JoinColumn,
OneToOne,
OneToMany,
} from 'typeorm';
import { AiConversation } from '@entities/ai_conversation.entity';
import { AiResponseVote } from '@entities/ai_response_vote.entity';
import { Artifact } from './artifact.entity';
@Entity('ai_conversation_messages')
export class AiConversationMessage {
@ -64,4 +66,7 @@ export class AiConversationMessage {
@OneToOne(() => AiResponseVote, (aiResponseVote) => aiResponseVote.aiConversationMessage)
aiResponseVote: AiResponseVote;
@OneToMany(() => Artifact, (artifact) => artifact.message)
artifacts: Artifact[];
}

View file

@ -69,6 +69,36 @@ export class App extends BaseEntity {
@Column({ name: 'workflow_enabled', default: false })
workflowEnabled: boolean;
@Column({ name: 'is_initialised_from_prompt', default: false })
isInitialisedFromPrompt: boolean;
@Column({ name: 'app_generated_from_prompt', default: false })
appGeneratedFromPrompt: boolean;
@Column({
type: 'enum',
enumName: 'app_builder_mode',
name: 'app_builder_mode',
enum: ['ai', 'visual'],
default: 'visual',
})
appBuilderMode: string;
@Column({ name: 'ai_generation_metadata', type: 'jsonb', nullable: true })
aiGenerationMetadata: {
steps: {
name: string;
id: string;
loadingStates: string[];
appInitialisationPrompt?: string;
}[];
appName?: string;
completedSteps: string[];
activeStep: string;
version: string;
};
@CreateDateColumn({ default: () => 'now()', name: 'created_at' })
createdAt: Date;

View file

@ -0,0 +1,51 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { AiConversation } from './ai_conversation.entity';
import { AiConversationMessage } from './ai_conversation_message.entity';
@Entity('artifacts')
export class Artifact {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({
name: 'conversation_id',
type: 'uuid',
nullable: false,
})
conversationId: string;
@Column({
name: 'message_id',
type: 'uuid',
nullable: false,
})
messageId: string;
@Column({ type: 'jsonb', nullable: false })
content: any;
@Column({ type: 'varchar', nullable: false })
identifier: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
@ManyToOne(() => AiConversation, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'conversation_id' })
conversation: AiConversation;
@ManyToOne(() => AiConversationMessage, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'message_id' })
message: AiConversationMessage;
}

View file

@ -1,16 +1,4 @@
export interface IAgentsService {
createComponent(prompt: string, organizationId: string): Promise<any>;
createQuery(prompt: string, tableName: string, columns: string, organizationId: string): Promise<any>;
createEvent(prompt: string, pageId: string[], organizationId: string): Promise<any>;
Agentic(prompt: string, organizationId: string): Promise<any>;
PromptEnrichment(prd_data: { content: string; metadata?: any }, organizationId: string): Promise<any>;
PromptEnrichmentChat(prompt: string, oldContext: any[], organizationId: string): Promise<any>;
CreateTable(organizationId: string, tables: any): Promise<any>;
docs(prompt: string, organizationId: string): Promise<any>;

View file

@ -1,4 +1,5 @@
export interface IAiUtilService {
getColorScheme(prd: any): any;
getAgentAssetPath(filename: string): any;
mergeSteps(componentsJson: any, newStepsJson: any): any;
@ -33,10 +34,6 @@ export interface IAiUtilService {
getQueriesfromsteps(steps: any): Promise<any>;
createQuerySteps(prd: string, lld: string, tableName: any, components: any, organizationId: any): Promise<any>;
createEventSteps(prd: string, Query: any, components: any, organizationId: any): Promise<any>;
convertToSteps(jsonData: any): Promise<any>;
getColorScheme(prd: any): any;

View file

@ -1,38 +1,55 @@
import { DynamicModule } from '@nestjs/common';
import { getImportPath } from '@modules/app/constants';
import { AiConversationRepository } from './repositories/ai-conversation.repository';
import { AiConversationMessageRepository } from './repositories/ai-conversation-message.repository';
import { AiResponseVoteRepository } from './repositories/ai-response-vote.repository';
import { AppsRepository } from '@modules/apps/repository';
import { FeatureAbilityFactory } from './ability';
import { TooljetDbModule } from '@modules/tooljet-db/module';
import { DataQueriesModule } from '@modules/data-queries/module';
import { LicenseModule } from '@modules/licensing/module';
import { AppPermissionsModule } from '@modules/app-permissions/module';
import { ImportExportResourcesModule } from '@modules/import-export-resources/module';
import { ArtifactRepository } from './repositories/artifact.repository';
import { SubModule } from '@modules/app/sub-module';
import { DataQueryRepository } from '@modules/data-queries/repository';
export class AiModule extends SubModule {
static async register(configs: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
const { AiController, AiService, AiUtilService, AgentsService } = await this.getProviders(configs, 'ai', [
'controller',
'service',
'util.service',
'services/agents.service',
]);
const { ComponentsService, EventsService } = await this.getProviders(configs, 'apps', [
'services/component.service',
'services/event.service',
]);
const importPath = await getImportPath(configs?.IS_GET_CONTEXT);
const { AiController } = await import(`${importPath}/ai/controller`);
const { AiService } = await import(`${importPath}/ai/service`);
const { AiUtilService } = await import(`${importPath}/ai/util.service`);
const { AgentsService } = await import(`${importPath}/ai/services/agents.service`);
const { ComponentsService } = await import(`${importPath}/apps/services/component.service`);
const { GraphService } = await import(`${importPath}/ai/services/graph.service`);
const { EventsService } = await import(`${importPath}/apps/services/event.service`);
return {
module: AiModule,
imports: [await TooljetDbModule.register(configs)],
imports: [
await TooljetDbModule.register(configs),
await DataQueriesModule.register(configs),
await LicenseModule.forRoot(configs),
await AppPermissionsModule.register(configs),
await ImportExportResourcesModule.register(configs),
],
controllers: [AiController],
providers: [
AiService,
AiUtilService,
GraphService,
AgentsService,
ComponentsService,
// ImportExportResourcesService,
AiConversationRepository,
AiConversationMessageRepository,
AppsRepository,
AiResponseVoteRepository,
FeatureAbilityFactory,
ArtifactRepository,
DataQueryRepository,
EventsService,
],
exports: [AiUtilService],

View file

@ -18,7 +18,7 @@ export class AiConversationMessageRepository extends Repository<AiConversationMe
order: {
createdAt: 'ASC',
},
relations: ['aiResponseVote'],
relations: ['aiResponseVote', 'artifacts'],
});
}

View file

@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { DataSource, EntityManager, Repository, UpdateResult } from 'typeorm';
import { dbTransactionWrap } from '@helpers/database.helper';
import { Artifact } from '@entities/artifact.entity';
@Injectable()
export class ArtifactRepository extends Repository<Artifact> {
constructor(private dataSource: DataSource) {
super(Artifact, dataSource.createEntityManager());
}
}

View file

@ -4,30 +4,6 @@ import { IAgentsService } from '../interfaces/IAgentsService';
@Injectable()
export class AgentsService implements IAgentsService {
constructor() {}
// Agents methods
async createComponent(prompt: string, organizationId): Promise<any> {
throw new Error('Method not implemented.');
}
async createQuery(prompt: string, tableName: string, columns: string, organizationId): Promise<any> {
throw new Error('Method not implemented.');
}
async createEvent(prompt: string, pageId: string[], organizationId): Promise<any> {
throw new Error('Method not implemented.');
}
async Agentic(prompt: string, organizationId): Promise<any> {
throw new Error('Method not implemented.');
}
async PromptEnrichment(prd_data: { content: string; metadata?: any }, organizationId: string): Promise<any> {
throw new Error('Method not implemented.');
}
async PromptEnrichmentChat(prompt: string, oldContext: any[], organizationId): Promise<any> {
throw new Error('Method not implemented.');
}
async CreateTable(organizationId: string, tables): Promise<any> {
throw new Error('Method not implemented.');

View file

@ -0,0 +1,27 @@
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
@Injectable()
export class GraphService implements OnModuleInit, OnModuleDestroy {
constructor() {}
async onModuleInit() {
// No-op for CE version
}
async onModuleDestroy() {
// No-op for CE version
}
// Placeholder methods that would be implemented in EE version
async getRelatedComponents(): Promise<any> {
throw new Error('GraphService is not available in Community Edition');
}
async analyzeDependencies(): Promise<any> {
throw new Error('GraphService is not available in Community Edition');
}
async getComponentGraph(): Promise<any> {
throw new Error('GraphService is not available in Community Edition');
}
}

View file

@ -2,6 +2,7 @@ import { IAiUtilService } from './interfaces/IUtilService';
export class AiUtilService implements IAiUtilService {
constructor() {}
public getAgentAssetPath(filename) {
throw new Error('Method not implemented.');
}
@ -34,14 +35,6 @@ export class AiUtilService implements IAiUtilService {
throw new Error('Method not implemented.');
}
async createQuerySteps(prd: string, lld: string, tableName, components, organizationId) {
throw new Error('Method not implemented.');
}
async createEventSteps(prd: string, Query: any, components: any, organizationId: any): Promise<any> {
throw new Error('Method not implemented.');
}
async convertToSteps(jsonData: any): Promise<any> {
throw new Error('Method not implemented.');
}

View file

@ -3,6 +3,11 @@ import { IsString, IsOptional, IsNotEmpty, MaxLength, IsBoolean, IsUUID, IsEnum
import { Exclude, Expose, Transform } from 'class-transformer';
import { APP_TYPES } from '../constants';
export enum AppBuilderMode {
AI = 'ai',
VISUAL = 'visual',
}
export class AppCreateDto {
@IsNotEmpty()
@IsString()
@ -18,6 +23,10 @@ export class AppCreateDto {
@IsString()
@IsEnum(APP_TYPES, { message: 'Invalid app type.' })
type: string;
@IsOptional()
@IsString()
prompt?: string;
}
export class AppUpdateDto {
@ -52,6 +61,10 @@ export class AppUpdateDto {
@IsOptional()
@Transform(({ value }) => sanitizeInput(value))
icon: string;
@IsOptional()
@IsEnum(AppBuilderMode, { message: 'app_builder_mode must be either "ai" or "visual"' })
app_builder_mode?: AppBuilderMode;
}
export class ValidateAppAccessDto {

View file

@ -6,7 +6,7 @@ import { AppUpdateDto } from '../dto';
import { AppEnvironment } from '@entities/app_environments.entity';
import { AppBase } from '@entities/app_base.entity';
export interface IAppsUtilService {
create(name: string, user: User, type: string, manager: EntityManager): Promise<App>;
create(name: string, user: User, type: string, isInitialisedFromPrompt: boolean, manager: EntityManager): Promise<App>;
findAppWithIdOrSlug(slug: string, organizationId: string): Promise<App>;
validateVersionEnvironment(
environmentName: string,

View file

@ -62,9 +62,9 @@ export class AppsService implements IAppsService {
protected readonly appGitRepository: AppGitRepository
) { }
async create(user: User, appCreateDto: AppCreateDto) {
const { name, icon, type } = appCreateDto;
const { name, icon, type, prompt } = appCreateDto;
return await dbTransactionWrap(async (manager: EntityManager) => {
const app = await this.appsUtilService.create(name, user, type as APP_TYPES, manager);
const app = await this.appsUtilService.create(name, user, type as APP_TYPES, !!prompt, manager);
const appUpdateDto = new AppUpdateDto();
appUpdateDto.name = name;

View file

@ -20,6 +20,23 @@ export class ComponentsService implements IComponentsService {
});
}
async findOneWithLayouts(id: string): Promise<Component> {
return dbTransactionWrap(async (manager: EntityManager) => {
const component = await manager
.createQueryBuilder(Component, 'component')
.leftJoinAndSelect('component.layouts', 'layout')
.where('component.id = :id', { id })
.getOne();
if (!component) {
throw new Error(`Component with id ${id} not found`);
}
return component;
});
}
async create(componentDiff: object, pageId: string, appVersionId: string) {
return dbTransactionForAppVersionAssociationsUpdate(async (manager: EntityManager) => {
await this.createComponentsAndLayouts(componentDiff, pageId, appVersionId, manager);

View file

@ -8,6 +8,15 @@ import { IEventsService } from '../interfaces/services/IEventService';
@Injectable()
export class EventsService implements IEventsService {
async findEventById(eventId: string): Promise<EventHandler> {
return dbTransactionWrap(async (manager: EntityManager) => {
const event = await manager.findOne(EventHandler, {
where: { id: eventId },
});
return event;
});
}
async findEventsForVersion(appVersionId: string, manager?: EntityManager): Promise<EventHandler[]> {
return dbTransactionWrap(async (manager: EntityManager) => {
const allEvents = await manager.find(EventHandler, {

View file

@ -52,7 +52,7 @@ export class AppsUtilService implements IAppsUtilService {
protected readonly organizationRepository: OrganizationRepository,
protected readonly abilityService: AbilityService
) {}
async create(name: string, user: User, type: APP_TYPES, manager: EntityManager): Promise<App> {
async create(name: string, user: User, type: APP_TYPES, isInitialisedFromPrompt: boolean = false, manager: EntityManager): Promise<App> {
return await dbTransactionWrap(async (manager: EntityManager) => {
const app = await catchDbException(() => {
return manager.save(
@ -64,6 +64,37 @@ export class AppsUtilService implements IAppsUtilService {
organizationId: user.organizationId,
userId: user.id,
isMaintenanceOn: type === APP_TYPES.WORKFLOW ? true : false,
...(isInitialisedFromPrompt && {
aiGenerationMetadata: {
steps: [
{
name: 'Describe app',
id: 'describe_app',
loadingStates: ['Generating PRD', 'PRD generated successfully'],
},
{
name: 'Define specs',
id: 'define_specs',
loadingStates: ['Generating app', 'App generated successfully'],
},
{
name: 'Setup database',
id: 'setup_database',
loadingStates: ['Generating app', 'App generated successfully'],
},
{
name: 'Generate app',
id: 'generate_app',
loadingStates: ['Generating app', 'App generated successfully'],
},
],
activeStep: 'describe_app',
completedSteps: [],
version: 'v1',
},
}),
isInitialisedFromPrompt: isInitialisedFromPrompt,
appBuilderMode: isInitialisedFromPrompt ? 'ai' : 'visual',
...(type === APP_TYPES.WORKFLOW && { workflowApiToken: uuidv4() }),
})
);

View file

@ -3,7 +3,6 @@ import { EntityManager, In } from 'typeorm';
import { User } from 'src/entities/user.entity';
import { DataSource } from 'src/entities/data_source.entity';
import { dbTransactionWrap } from 'src/helpers/database.helper';
import { DataSourceTypes } from '@modules/data-sources/constants';
import { Response } from 'express';
import { DataQueryRepository } from './repository';
import { decode } from 'js-base64';
@ -22,7 +21,7 @@ export class DataQueriesService implements IDataQueriesService {
protected readonly dataQueryRepository: DataQueryRepository,
protected readonly dataQueryUtilService: DataQueriesUtilService,
protected readonly dataSourceRepository: DataSourcesRepository
) { }
) {}
async getAll(user: User, versionId: string, mode?: string) {
const queries = await this.dataQueryRepository.getAll(versionId);

View file

@ -24,7 +24,7 @@ import { IComponentsController } from '../interfaces/controllers/IComponentsCont
version: '2',
})
export class ComponentsController implements IComponentsController {
constructor(protected readonly componentsService: ComponentsService) {}
constructor(protected readonly componentsService: ComponentsService) { }
@InitFeature(FEATURE_KEY.CREATE_COMPONENTS)
@UseGuards(JwtAuthGuard, ValidAppGuard, FeatureAbilityGuard)