feat: add Ask AI CE stubs and shared file wiring#41692
feat: add Ask AI CE stubs and shared file wiring#41692
Conversation
Add minimal CE stubs and EE re-export shims so the Ask AI feature (implemented entirely in the EE repo) can integrate with shared files. New CE stub files: - ce/selectors/aiAssistantSelectors.ts (returns false/null/[]) - ce/actions/aiAssistantActions.ts (action creators + types) - ce/components/editorComponents/GPT/AISidePanel.tsx (returns null) - ce/components/editorComponents/GlobalAISidePanel/index.tsx (returns null) New EE re-export shims: - ee/selectors/aiAssistantSelectors.ts - ee/actions/aiAssistantActions.ts - ee/components/editorComponents/GPT/AISidePanel.tsx - ee/components/editorComponents/GlobalAISidePanel/index.tsx Updated existing CE files: - GPT/trigger.tsx: added isAISupportedMode, hasApiKey param to isAIEnabled - GPT/index.tsx: re-exports AISidePanel - ReduxActionConstants.tsx: added 9 new AI action types Updated shared files: - CodeEditor/index.tsx: AI selector/action imports, componentDidMount load - CodeEditor/generateQuickCommands.tsx: AI slash command updates - DynamicTextField.tsx: AIAssisted auto-detection for editor modes - StaticLayout.tsx, AnimatedLayout.tsx: render GlobalAISidePanel
WalkthroughThis change introduces a new AI Assistant feature through Redux-based state management, including action creators, selectors, and UI components. The CodeEditor is refactored to use Redux actions instead of local state, and new side panel components are integrated into the application layouts. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CodeEditor
participant Redux as Redux<br/>(Actions & Selectors)
participant GlobalAISidePanel as GlobalAISidePanel<br/>(UI)
User->>CodeEditor: Click AskAIButton
activate CodeEditor
CodeEditor->>CodeEditor: Extract editor context<br/>(cursor position, function name, etc.)
CodeEditor->>Redux: Dispatch openAIPanelWithContext(context)
deactivate CodeEditor
activate Redux
Redux->>Redux: Update AI state<br/>(isAIPanelOpen: true)
Redux->>GlobalAISidePanel: Trigger re-render with state
deactivate Redux
activate GlobalAISidePanel
GlobalAISidePanel->>User: Render AI side panel
deactivate GlobalAISidePanel
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/client/src/components/editorComponents/CodeEditor/index.tsx`:
- Around line 347-352: Replace the stale instance field AIEnabled with a derived
accessor: add a getAIEnabled() method that returns
isAIEnabled(this.props.featureFlags, this.props.mode, this.props.hasAIApiKey) &&
Boolean(this.props.AIAssisted), remove any assignments to this.AIEnabled
(including in constructor and componentDidUpdate), and update all call sites
(e.g., the lines referenced: 1213, 1437, 1744, 1813 and the other occurrences
around 589-598) to call this.getAIEnabled() so the value is always computed from
current props; also remove or update the incomplete componentDidUpdate logic
that attempted to recalc AIEnabled so it no longer mutates instance state.
- Around line 1749-1789: The onClick handler is using this.props.input.value
which can be stale — compute currentValue from the live editor before the
try/catch (use this.editor.getValue() when this.editor exists, falling back to
this.props.input.value or ""), then pass that currentValue into
openAIPanelWithContext and also reuse it in the catch fallback instead of an
empty string; update the onClick block around getAIContext, editor.getValue(),
and this.props.openAIPanelWithContext (and keep getEditorIdentifier(entity info)
and getAIContext usage intact).
- Around line 355-359: The component currently only calls loadAISettings() in
componentDidMount(), so when AIAssisted flips from false to true after mount the
config never loads; add a componentDidUpdate(prevProps) method that checks if
prevProps.AIAssisted was false and this.props.AIAssisted is true and
this.props.isAIConfigLoaded is false, and if so call
this.props.loadAISettings(); reuse the same guards as componentDidMount to avoid
duplicate loads and keep behavior consistent with existing load logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: bde01aee-ac44-4ff2-ab5b-474ea8d7e969
📒 Files selected for processing (16)
app/client/src/ce/actions/aiAssistantActions.tsapp/client/src/ce/components/editorComponents/GPT/AISidePanel.tsxapp/client/src/ce/components/editorComponents/GPT/index.tsxapp/client/src/ce/components/editorComponents/GPT/trigger.tsxapp/client/src/ce/components/editorComponents/GlobalAISidePanel/index.tsxapp/client/src/ce/constants/ReduxActionConstants.tsxapp/client/src/ce/selectors/aiAssistantSelectors.tsapp/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsxapp/client/src/components/editorComponents/CodeEditor/index.tsxapp/client/src/components/editorComponents/form/fields/DynamicTextField.tsxapp/client/src/ee/actions/aiAssistantActions.tsapp/client/src/ee/components/editorComponents/GPT/AISidePanel.tsxapp/client/src/ee/components/editorComponents/GlobalAISidePanel/index.tsxapp/client/src/ee/selectors/aiAssistantSelectors.tsapp/client/src/pages/AppIDE/layouts/AnimatedLayout.tsxapp/client/src/pages/AppIDE/layouts/StaticLayout.tsx
💤 Files with no reviewable changes (1)
- app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx
| this.AIEnabled = | ||
| isAIEnabled(this.props.featureFlags, this.props.mode) && | ||
| Boolean(this.props.AIAssisted); | ||
| isAIEnabled( | ||
| this.props.featureFlags, | ||
| this.props.mode, | ||
| this.props.hasAIApiKey, | ||
| ) && Boolean(this.props.AIAssisted); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the file to verify the AIEnabled initialization and usage
cat -n app/client/src/components/editorComponents/CodeEditor/index.tsx | sed -n '140,160p;345,360p;585,605p'Repository: appsmithorg/appsmith
Length of output: 2252
🏁 Script executed:
# Get more context around the componentDidUpdate method
cat -n app/client/src/components/editorComponents/CodeEditor/index.tsx | sed -n '585,610p'Repository: appsmithorg/appsmith
Length of output: 1053
🏁 Script executed:
# Search for all usages of AIEnabled in the file
rg "AIEnabled" app/client/src/components/editorComponents/CodeEditor/index.tsx -nRepository: appsmithorg/appsmith
Length of output: 481
🏁 Script executed:
# Check the AI payload construction around the ask AI button handler
cat -n app/client/src/components/editorComponents/CodeEditor/index.tsx | sed -n '1740,1800p'Repository: appsmithorg/appsmith
Length of output: 2647
Derive AIEnabled from current props instead of storing stale instance field.
render() executes before componentDidUpdate(), so AIEnabled is stale when hasAIApiKey updates. Additionally, the componentDidUpdate() dependency check is incomplete—changes to featureFlags, mode, or AIAssisted do not recalculate AIEnabled. Mutating an instance field also prevents React from scheduling a re-render for consistency.
🔧 Suggested direction
- AIEnabled = false;
+ private getAIEnabled = (props: Props = this.props) =>
+ isAIEnabled(props.featureFlags, props.mode, props.hasAIApiKey) &&
+ Boolean(props.AIAssisted);
constructor(props: Props) {
super(props);
@@
- this.AIEnabled =
- isAIEnabled(
- this.props.featureFlags,
- this.props.mode,
- this.props.hasAIApiKey,
- ) && Boolean(this.props.AIAssisted);
}
@@
- if (prevProps.hasAIApiKey !== this.props.hasAIApiKey) {
- this.AIEnabled =
- isAIEnabled(
- this.props.featureFlags,
- this.props.mode,
- this.props.hasAIApiKey,
- ) && Boolean(this.props.AIAssisted);
- }
@@
- {this.AIEnabled && (
+ {this.getAIEnabled() && (Update all call sites (lines 1213, 1437, 1744, 1813) to use this.getAIEnabled().
Also applies to: 589-598
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/client/src/components/editorComponents/CodeEditor/index.tsx` around lines
347 - 352, Replace the stale instance field AIEnabled with a derived accessor:
add a getAIEnabled() method that returns isAIEnabled(this.props.featureFlags,
this.props.mode, this.props.hasAIApiKey) && Boolean(this.props.AIAssisted),
remove any assignments to this.AIEnabled (including in constructor and
componentDidUpdate), and update all call sites (e.g., the lines referenced:
1213, 1437, 1744, 1813 and the other occurrences around 589-598) to call
this.getAIEnabled() so the value is always computed from current props; also
remove or update the incomplete componentDidUpdate logic that attempted to
recalc AIEnabled so it no longer mutates instance state.
| componentDidMount(): void { | ||
| // Load AI settings if this is an AI-assisted editor and settings aren't loaded yet | ||
| if (this.props.AIAssisted && !this.props.isAIConfigLoaded) { | ||
| this.props.loadAISettings(); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the file exists and get its basic structure
fd "index.tsx" app/client/src/components/editorComponents/CodeEditor/ | head -5Repository: appsmithorg/appsmith
Length of output: 127
🏁 Script executed:
# Get the file size to understand how much we can read
wc -l app/client/src/components/editorComponents/CodeEditor/index.tsxRepository: appsmithorg/appsmith
Length of output: 132
🏁 Script executed:
# Read the componentDidMount and surrounding code context (lines 350-380)
sed -n '350,380p' app/client/src/components/editorComponents/CodeEditor/index.tsxRepository: appsmithorg/appsmith
Length of output: 1122
🏁 Script executed:
# Search for componentDidUpdate in the file
rg "componentDidUpdate" app/client/src/components/editorComponents/CodeEditor/index.tsx -A 30Repository: appsmithorg/appsmith
Length of output: 1136
🏁 Script executed:
# Look for the Props type/interface definition
rg "interface Props|type Props" app/client/src/components/editorComponents/CodeEditor/index.tsx -A 20Repository: appsmithorg/appsmith
Length of output: 572
🏁 Script executed:
# Look for ReduxStateProps definition to see AIAssisted and isAIConfigLoaded types
rg "interface ReduxStateProps|type ReduxStateProps" app/client/src/components/editorComponents/CodeEditor/ -A 30Repository: appsmithorg/appsmith
Length of output: 2968
🏁 Script executed:
# Search for EditorProps to see if AIAssisted is defined there
rg "interface EditorProps|type EditorProps" app/client/src/components/editorComponents/CodeEditor/ -A 30Repository: appsmithorg/appsmith
Length of output: 3142
🏁 Script executed:
# Check where loadAISettings is dispatched from and what it does
rg "loadAISettings" app/client/src/components/editorComponents/CodeEditor/ -B 2 -A 2Repository: appsmithorg/appsmith
Length of output: 1636
🏁 Script executed:
# Look for any other places in the codebase where AIAssisted prop changes are handled
rg "AIAssisted" app/client/src/components/editorComponents/CodeEditor/index.tsx -B 2 -A 2Repository: appsmithorg/appsmith
Length of output: 675
🏁 Script executed:
# Check if isAIConfigLoaded is in the redux state and could change after mount
rg "isAIConfigLoaded" app/client/src/components/editorComponents/CodeEditor/index.tsx -B 2 -A 2Repository: appsmithorg/appsmith
Length of output: 488
🏁 Script executed:
# Search for mapStateToProps to understand how AIAssisted and isAIConfigLoaded are passed
sed -n '1900,1942p' app/client/src/components/editorComponents/CodeEditor/index.tsxRepository: appsmithorg/appsmith
Length of output: 1851
Add AI settings loading when AIAssisted prop transitions from false to true.
Currently, loadAISettings() only fires in componentDidMount(). If an editor instance becomes AI-assisted after initial mount, the config never loads and Ask AI stays unavailable until the component remounts.
🔧 Suggested fix
componentDidUpdate(prevProps: Props): void {
+ if (
+ !prevProps.AIAssisted &&
+ this.props.AIAssisted &&
+ !this.props.isAIConfigLoaded
+ ) {
+ this.props.loadAISettings();
+ }
+
// Recalculate AIEnabled when hasAIApiKey changes (e.g., after saga loads settings)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/client/src/components/editorComponents/CodeEditor/index.tsx` around lines
355 - 359, The component currently only calls loadAISettings() in
componentDidMount(), so when AIAssisted flips from false to true after mount the
config never loads; add a componentDidUpdate(prevProps) method that checks if
prevProps.AIAssisted was false and this.props.AIAssisted is true and
this.props.isAIConfigLoaded is false, and if so call
this.props.loadAISettings(); reuse the same guards as componentDidMount to avoid
duplicate loads and keep behavior consistent with existing load logic.
| onClick={() => { | ||
| try { | ||
| const currentValue = | ||
| typeof this.props.input.value === "string" | ||
| ? this.props.input.value | ||
| : ""; | ||
|
|
||
| let aiContext = { | ||
| functionName: "", | ||
| cursorLineNumber: 0, | ||
| functionString: "", | ||
| }; | ||
|
|
||
| if (this.editor) { | ||
| const cursorPosition = this.editor.getCursor(); | ||
|
|
||
| aiContext = getAIContext({ | ||
| cursorPosition, | ||
| editor: this.editor, | ||
| }); | ||
| } | ||
|
|
||
| this.props.openAIPanelWithContext({ | ||
| functionName: aiContext.functionName, | ||
| cursorLineNumber: aiContext.cursorLineNumber, | ||
| functionString: aiContext.functionString, | ||
| mode: this.props.mode, | ||
| currentValue, | ||
| editorId: getEditorIdentifier(this.props), | ||
| entityName: entityInformation?.entityName, | ||
| entityId: entityInformation?.entityId, | ||
| propertyPath: entityInformation?.propertyPath, | ||
| }); | ||
| } catch (error) { | ||
| // eslint-disable-next-line no-console | ||
| console.error("Error opening AI panel:", error); | ||
| this.props.openAIPanelWithContext({ | ||
| mode: this.props.mode, | ||
| currentValue: "", | ||
| }); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -name "index.tsx" -path "*CodeEditor*" 2>/dev/null | head -5Repository: appsmithorg/appsmith
Length of output: 46
🏁 Script executed:
# First, let me check if the file exists and examine the context around those lines
head -100 app/client/src/components/editorComponents/CodeEditor/index.tsx | tail -50Repository: appsmithorg/appsmith
Length of output: 2002
🏁 Script executed:
# Get the full line count to understand file size
wc -l app/client/src/components/editorComponents/CodeEditor/index.tsxRepository: appsmithorg/appsmith
Length of output: 132
🏁 Script executed:
# Examine the specific lines mentioned in the review (1749-1789) and surrounding context
sed -n '1740,1800p' app/client/src/components/editorComponents/CodeEditor/index.tsxRepository: appsmithorg/appsmith
Length of output: 2220
🏁 Script executed:
# Look for editor initialization and type information
sed -n '1,100p' app/client/src/components/editorComponents/CodeEditor/index.tsxRepository: appsmithorg/appsmith
Length of output: 3975
🏁 Script executed:
# Search for handleDebouncedChange to understand its behavior
rg "handleDebouncedChange" app/client/src/components/editorComponents/CodeEditor/index.tsx -A 5 -B 2Repository: appsmithorg/appsmith
Length of output: 513
🏁 Script executed:
# Search for editor.getValue to see if it's used elsewhere in the file
rg "getValue" app/client/src/components/editorComponents/CodeEditor/index.tsxRepository: appsmithorg/appsmith
Length of output: 388
🏁 Script executed:
# Look for editor initialization (this.editor =)
rg "this\.editor\s*=" app/client/src/components/editorComponents/CodeEditor/index.tsx -A 3 -B 2Repository: appsmithorg/appsmith
Length of output: 277
🏁 Script executed:
# Check what type this.editor is - look for editor library imports and setup
sed -n '1,150p' app/client/src/components/editorComponents/CodeEditor/index.tsx | grep -E "import.*[Ee]ditor|CodeMirror|Ace|Monaco"Repository: appsmithorg/appsmith
Length of output: 759
Use the live editor value instead of the potentially stale input prop.
this.props.input.value lags behind visible text because handleDebouncedChange() updates asynchronously. Move the currentValue calculation outside the try block to use this.editor.getValue() when available, and reuse that value in the error fallback instead of dropping to "".
🔧 Suggested fix
onClick={() => {
+ const currentValue = this.editor
+ ? this.editor.getValue()
+ : typeof this.props.input.value === "string"
+ ? this.props.input.value
+ : "";
try {
- const currentValue =
- typeof this.props.input.value === "string"
- ? this.props.input.value
- : "";
-
let aiContext = {
functionName: "",
cursorLineNumber: 0,
functionString: "",
};
@@
this.props.openAIPanelWithContext({
mode: this.props.mode,
- currentValue: "",
+ currentValue,
});
}
}}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onClick={() => { | |
| try { | |
| const currentValue = | |
| typeof this.props.input.value === "string" | |
| ? this.props.input.value | |
| : ""; | |
| let aiContext = { | |
| functionName: "", | |
| cursorLineNumber: 0, | |
| functionString: "", | |
| }; | |
| if (this.editor) { | |
| const cursorPosition = this.editor.getCursor(); | |
| aiContext = getAIContext({ | |
| cursorPosition, | |
| editor: this.editor, | |
| }); | |
| } | |
| this.props.openAIPanelWithContext({ | |
| functionName: aiContext.functionName, | |
| cursorLineNumber: aiContext.cursorLineNumber, | |
| functionString: aiContext.functionString, | |
| mode: this.props.mode, | |
| currentValue, | |
| editorId: getEditorIdentifier(this.props), | |
| entityName: entityInformation?.entityName, | |
| entityId: entityInformation?.entityId, | |
| propertyPath: entityInformation?.propertyPath, | |
| }); | |
| } catch (error) { | |
| // eslint-disable-next-line no-console | |
| console.error("Error opening AI panel:", error); | |
| this.props.openAIPanelWithContext({ | |
| mode: this.props.mode, | |
| currentValue: "", | |
| }); | |
| } | |
| onClick={() => { | |
| const currentValue = this.editor | |
| ? this.editor.getValue() | |
| : typeof this.props.input.value === "string" | |
| ? this.props.input.value | |
| : ""; | |
| try { | |
| let aiContext = { | |
| functionName: "", | |
| cursorLineNumber: 0, | |
| functionString: "", | |
| }; | |
| if (this.editor) { | |
| const cursorPosition = this.editor.getCursor(); | |
| aiContext = getAIContext({ | |
| cursorPosition, | |
| editor: this.editor, | |
| }); | |
| } | |
| this.props.openAIPanelWithContext({ | |
| functionName: aiContext.functionName, | |
| cursorLineNumber: aiContext.cursorLineNumber, | |
| functionString: aiContext.functionString, | |
| mode: this.props.mode, | |
| currentValue, | |
| editorId: getEditorIdentifier(this.props), | |
| entityName: entityInformation?.entityName, | |
| entityId: entityInformation?.entityId, | |
| propertyPath: entityInformation?.propertyPath, | |
| }); | |
| } catch (error) { | |
| // eslint-disable-next-line no-console | |
| console.error("Error opening AI panel:", error); | |
| this.props.openAIPanelWithContext({ | |
| mode: this.props.mode, | |
| currentValue, | |
| }); | |
| } | |
| }} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/client/src/components/editorComponents/CodeEditor/index.tsx` around lines
1749 - 1789, The onClick handler is using this.props.input.value which can be
stale — compute currentValue from the live editor before the try/catch (use
this.editor.getValue() when this.editor exists, falling back to
this.props.input.value or ""), then pass that currentValue into
openAIPanelWithContext and also reuse it in the catch fallback instead of an
empty string; update the onClick block around getAIContext, editor.getValue(),
and this.props.openAIPanelWithContext (and keep getEditorIdentifier(entity info)
and getAIContext usage intact).
Summary
trigger.tsx,GPT/index.tsx) to match the new API surface expected by shared filesReduxActionConstants.tsxCodeEditor,DynamicTextField, IDE layouts) with the imports and conditional rendering hooks for Ask AIWhat this PR does NOT do
This PR does not add any AI functionality to the community edition. All CE stubs return
null,false, or[]. The actual Ask AI implementation lives entirely in the EE repo and will be enabled via thelicense_ask_ai_config_enabledfeature flag.Changes
New CE stub files (all return null/false)
ce/selectors/aiAssistantSelectors.tsce/actions/aiAssistantActions.tsce/components/editorComponents/GPT/AISidePanel.tsxce/components/editorComponents/GlobalAISidePanel/index.tsxNew EE re-export shims
ee/selectors/aiAssistantSelectors.tsee/actions/aiAssistantActions.tsee/components/editorComponents/GPT/AISidePanel.tsxee/components/editorComponents/GlobalAISidePanel/index.tsxUpdated existing CE files
GPT/trigger.tsx— addedisAISupportedMode()export andhasApiKeyparameter toisAIEnabled()GPT/index.tsx— re-exportsAISidePanelReduxActionConstants.tsx— 9 new AI action type constantsUpdated shared files
CodeEditor/index.tsx— AI selector/action imports, AI settings loading incomponentDidMountCodeEditor/generateQuickCommands.tsx— slash command updatesDynamicTextField.tsx—AIAssistedauto-detection for editor modesStaticLayout.tsx,AnimatedLayout.tsx— render<GlobalAISidePanel />Test plan
ee/imports correctly through the re-export shimsSummary by CodeRabbit
Release Notes
Automation
/ok-to-test tags="@tag.All"
Tip
🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/23943740853
Commit: ac661e1
Cypress dashboard.
Tags:
@tag.AllSpec:
Fri, 03 Apr 2026 12:00:29 UTC