-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat: ai chatbot #6907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: ai chatbot #6907
Conversation
WalkthroughThis PR introduces a comprehensive AI Assistant panel to Bruno, featuring UI components for chat-based code generation, file selection, multi-file diff review, streaming responses, and Redux-backed state management. It includes Electron backend integration with OpenAI API support via the AI SDK, IPC handlers for streaming AI responses, and hotkey support for panel toggling. Changes
Sequence DiagramsequenceDiagram
participant User
participant Renderer as Renderer Process<br/>(AIAssistantPanel)
participant IPC as IPC Bridge
participant Backend as Main Process<br/>(AI Handler)
participant AI as OpenAI API
User->>Renderer: Types message & sends
Renderer->>Renderer: Build prompt with context
Renderer->>IPC: ai:generate (action, context)
IPC->>Backend: Receive request
Backend->>Backend: Validate & prepare prompt
Backend->>AI: streamText() with prompt
AI-->>Backend: Stream chunk
Backend->>IPC: ai:stream-chunk (content)
IPC->>Renderer: Receive chunk
Renderer->>Renderer: Accumulate & display
AI-->>Backend: Stream end
Backend->>Backend: Infer responseType (text/code/multi-file)
Backend->>IPC: ai:stream-end (type, full content)
IPC->>Renderer: Receive end event
Renderer->>Renderer: Parse & prepare diff or message
Renderer->>User: Display result (chat/diff/review)
alt Multi-file mode
Renderer->>User: Show per-file accept/reject UI
User->>Renderer: Accept/reject files
Renderer->>Renderer: Apply changes via Redux
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
🤖 Fix all issues with AI agents
In `@packages/bruno-app/src/components/AIAssistantPanel/ChatMessage/index.js`:
- Around line 23-33: The current regex line.match(/^(`{3,})(\w*)?$/) in the
code-fence detection only captures word characters for the info string and fails
for languages like "c++" or "objective-c"; update the match to allow any
non-empty info string (e.g. use a second capture like (.*) or (\S.*)?), then
assign codeBlockLang = (captureGroup2 || '').trim() so you accept and trim
arbitrary info strings; ensure the logic that compares closing backticks still
uses openingBackticks unchanged and that codeBlockLang handling in the
surrounding code (variables tripleBacktickMatch, inCodeBlock, openingBackticks,
codeBlockContent) uses the trimmed value.
- Around line 129-131: Add a targeted Biome ignore comment immediately above the
div using dangerouslySetInnerHTML to suppress the specific lint rule that flags
direct HTML insertion (while keeping the existing DOMPurify sanitization).
Locate the JSX element with className "markdown-content" that sets
dangerouslySetInnerHTML with sanitizedHtml and insert a single-line or block
Biome ignore for the specific lint rule (e.g., the react/no-danger-equivalent
rule) so only this occurrence is suppressed rather than disabling linting
broadly.
In `@packages/bruno-app/src/components/AIAssistantPanel/CodeBlock/index.js`:
- Around line 148-162: The CodeBlock component currently calls code.split('\n')
which will throw if code is null/undefined; update the component to guard the
input by defaulting code to an empty string (e.g., change the prop default or
create a local safeCode = code ?? ''), use safeCode for splitting and for
navigator.clipboard.writeText in handleCopy, and ensure any references (lines,
handleCopy) use that safe value so runtime errors are avoided.
In `@packages/bruno-app/src/components/AIAssistantPanel/FileSelector/index.js`:
- Around line 112-120: The IconX clear control uses onKeyDown handling that only
triggers on Enter; update the keyboard handler for the IconX instance(s) (the
one using onClick={handleClearSearch} and the similar control at the second
occurrence) to also respond to Spacebar by checking for e.key === 'Enter' ||
e.key === ' ' || e.key === 'Spacebar' (and call e.preventDefault() before
invoking handleClearSearch), ensuring the role="button" + tabIndex remains;
apply the same change to the other occurrence (lines near the second IconX block
referenced) so both keyboard users can toggle via Space as well as Enter.
- Around line 1-47: The debouncedSetSearch created with useMemo can fire after
clearing or after unmount; call debouncedSetSearch.cancel() inside
handleClearSearch to avoid re-applying the old search text, and add a useEffect
cleanup that calls debouncedSetSearch.cancel() on unmount to prevent state
updates after unmount. Update references to debouncedSetSearch (from useMemo)
and handleClearSearch accordingly so the debounce is cancelled both when
clearing and when the component unmounts.
In
`@packages/bruno-app/src/components/AIAssistantPanel/FileSelector/StyledWrapper.js`:
- Around line 86-97: The checkmark color in StyledWrapper.js is hard-coded to
'white' inside the .checked &::after rule; replace that literal with the theme
token (e.g., use (props) => props.theme.textWhite or your theme's equivalent
token) so the border color uses the theme's white text color instead of the
literal 'white' (update the border: solid white to border: solid ${(props) =>
props.theme.<textWhiteToken>} in the .checked ::after rule).
In `@packages/bruno-app/src/components/AIAssistantPanel/index.js`:
- Around line 181-198: The useEffect that conditionally clears and adds the
current item reads selectedFilesForAI but doesn't include it in the dependency
array, causing stale-checks; update the effect's dependencies to include
selectedFilesForAI (along with item, collection, dispatch) so the
isAlreadySelected check uses fresh state, ensuring clearAIFileSelection() and
addFileToAISelection(...) logic runs correctly when selectedFilesForAI changes.
- Around line 651-656: handleStopGeneration currently only clears frontend state
(currentStreamId, status, messages, resetStreamState) but doesn't abort the
backend stream; modify handleStopGeneration to also send an IPC "ai:cancel"
message with the active currentStreamId (and guard if null) before clearing
state, and update its dependency array to include currentStreamId and the ipc
sender (e.g., ipcRenderer). On the backend, implement an "ai:cancel" IPC handler
that looks up the stored AbortController for the given stream id used by the
"ai:generate" handler and calls controller.abort(), and ensure "ai:generate"
stores its AbortController keyed by the stream id so cancellation will stop the
server-side streaming/token consumption.
In `@packages/bruno-app/src/components/AIAssistantPanel/ModeSelector/index.js`:
- Around line 27-48: The dropdown's selectedItemId uses the possibly-invalid
currentMode while the displayed label falls back to AI_MODES[2]; update
ModeSelector to use the resolved id from currentModeConfig so the highlighted
item matches the fallback label—i.e., compute currentModeConfig (as already
done) and pass currentModeConfig.id (or the fallback id) to the MenuDropdown
selectedItemId prop instead of currentMode.
In
`@packages/bruno-app/src/components/AIAssistantPanel/MultiFileDiffView/index.js`:
- Around line 1-16: MultiFileDiffView can leave activeIndex out of bounds when
the changes array shrinks; add a useEffect that watches the changes array (the
changes reference itself) and clamps or resets activeIndex via setActiveIndex:
if the current activeIndex >= changes.length or the active change is no longer
valid, set activeIndex to the index of the first pending change (from
pendingChanges) or 0; ensure the effect references activeIndex, changes and
pendingChanges and only updates state when a change is needed to avoid extra
renders.
In `@packages/bruno-app/src/components/RequestPane/Script/index.js`:
- Around line 102-112: The effect that updates AI panel context (useEffect)
references activeTab and calls getCurrentScript(activeTab) but does not include
activeTab in the dependency array; update the dependency array for that
useEffect to include activeTab so the effect re-runs when activeTab changes
(retain showAIPanel, item, collection, dispatch and any other referenced
variables like getCurrentScript if it's not stable).
In `@packages/bruno-app/src/components/RequestTabPanel/index.js`:
- Around line 56-61: The hooks (useSelector calls for forceVerticalLayoutForAI,
isConsoleOpen, showAIPanel, responsePaneHiddenByAI and the computed
isVerticalLayout) are being invoked after an early return guarded by if (typeof
window === 'undefined'), violating Rules of Hooks; fix by extracting the
hook-using logic into a new child component (e.g., InnerRequestTabPanel) which
contains all useSelector calls and the isVerticalLayout computation, and have
the original component conditionally render that child (or null) based on typeof
window === 'undefined' so hooks always run at the top level of the child
component.
- Around line 360-375: When shouldHideResponsePane is true the request pane
keeps split dimensions from requestPaneStyle and leaves empty space; update the
layout logic that computes/assigns requestPaneStyle so that when
shouldHideResponsePane === true you remove/override the fixed split size and let
the request pane flex to fill available area (e.g. set style to
flex-grow/height:100% in vertical or width:100% in horizontal). Locate where
requestPaneStyle is computed/used in RequestTabPanel (and keep existing symbols
like shouldHideResponsePane, requestPaneStyle, resetPaneBoundaries,
handleDragbarMouseDown, renderResponsePane) and apply the override only when the
response pane is hidden so the dragbar/drag handlers still work when visible.
In `@packages/bruno-electron/src/ai/prompts/index.js`:
- Around line 145-155: Add a redaction helper and apply it before interpolating
request/response data into prompts: implement a function (e.g., redactForAi)
that clones the passed object and replaces sensitive header fields
(authorization, cookie, set-cookie, x-api-key, etc.) and any obvious PII in
bodies with a placeholder like "[REDACTED]"; then call redactForAi(request) and
redactForAi(response) inside generateTestsPrompt, chatPrompt, and
multiFileChatPrompt and use the sanitized objects when building the template
strings so headers/bodies sent to the external AI provider never include raw
credentials or cookies.
In `@packages/bruno-electron/src/ipc/ai.js`:
- Around line 34-90: The handler for ipcMain.handle('ai:generate') can receive
an undefined payload and currently uses context directly, causing TypeError;
ensure the second argument defaults to an object and that context defaults to {}
before use (either by changing the handler signature to default the param to {}
and/or adding an early line like: if (!context) context = {}), so all downstream
uses (generateTestsPrompt, improveScriptPrompt, chatPrompt, multiFileChatPrompt
and any context.* checks) operate on a safe object and return a descriptive
error when required fields are missing.
🧹 Nitpick comments (7)
packages/bruno-app/src/components/AIAssistantPanel/MultiFileDiffView/StyledWrapper.js (1)
77-95: Replace hard-coded colors with theme tokens.Use theme-provided colors for white text and error states to keep styling consistent across themes.
♻️ Suggested adjustment
- color: white; + color: ${(props) => props.theme.colors.text.primary}; ... - &.rejected { color: ${(props) => props.theme.colors.text.red || '#f85149'}; } + &.rejected { color: ${(props) => props.theme.colors.text.danger}; } ... - color: white; + color: ${(props) => props.theme.colors.text.primary};Note: For rejected states, prefer
theme.colors.text.danger(design system standard for errors) overtheme.colors.text.red, which aligns with the error-handling patterns used elsewhere in AIAssistantPanel.Also applies to: 121-126
packages/bruno-app/src/pages/Bruno/index.js (1)
66-68: Unused selector:aiPanelWidth.
aiPanelWidthis selected on line 68 but never used in this component. TheAIAssistantPanelreadspanelWidthdirectly from Redux internally.🔧 Suggested fix
const showAIPanel = useSelector((state) => state.app.showAIPanel); const aiPanelContext = useSelector((state) => state.app.aiPanelContext); - const aiPanelWidth = useSelector((state) => state.app.aiPanelWidth);packages/bruno-app/src/components/RequestPane/Script/index.js (1)
161-193: DuplicateTabscomponent with samevalueandonValueChange.There are two
<Tabs>components (lines 132 and 161) sharing the same state. This works but is unconventional. Consider wrapping bothTabsListandTabsContentelements under a single<Tabs>parent for clearer structure.packages/bruno-app/src/components/AIAssistantPanel/StyledWrapper.js (1)
415-436: Hardcodedwhitecolor should use theme.Per coding guidelines, colors should use the theme prop rather than hardcoded values. The
color: whiteon line 418 should reference a theme token.Based on learnings, use styled component's theme prop for colors.
♻️ Suggested fix
.send-btn { display: flex; align-items: center; justify-content: center; background: ${(props) => props.theme.brand}; border: none; cursor: pointer; - color: white; + color: ${(props) => props.theme.colors.text.white}; padding: 5px;packages/bruno-app/src/components/AIAssistantPanel/index.js (2)
266-444: Large dependency array causes frequent IPC listener re-subscription.The effect includes
selectedFilesForAIandcollectionsin deps. Every file selection change or collection update triggers listener teardown/setup. Consider moving the data lookups outside the handlers or using refs for values that change frequently but shouldn't trigger re-subscription.♻️ Suggested approach
Use refs for frequently changing values that are only needed inside handlers:
const selectedFilesRef = useRef(selectedFilesForAI); const collectionsRef = useRef(collections); useEffect(() => { selectedFilesRef.current = selectedFilesForAI; }, [selectedFilesForAI]); useEffect(() => { collectionsRef.current = collections; }, [collections]);Then reference
selectedFilesRef.currentandcollectionsRef.currentinside the IPC handlers, removing them from the effect's dependency array.
734-741: Using array index as key for messages.Array indices work for append-only lists, but if messages could be removed or reordered, this would cause React reconciliation issues. If the list is strictly append-only, this is acceptable.
packages/bruno-electron/src/ai/prompts/index.js (1)
7-128: Consider externalizing the Bruno API reference string.Keeping this large reference in a separate markdown/JSON module would make updates easier and reduce inline template noise.
| for (let i = 0; i < lines.length; i++) { | ||
| const line = lines[i]; | ||
| const tripleBacktickMatch = line.match(/^(`{3,})(\w*)?$/); | ||
|
|
||
| if (tripleBacktickMatch && !inCodeBlock) { | ||
| // Starting a code block | ||
| inCodeBlock = true; | ||
| openingBackticks = tripleBacktickMatch[1]; | ||
| codeBlockLang = tripleBacktickMatch[2] || ''; | ||
| codeBlockContent = []; | ||
| } else if (inCodeBlock && line.trim() === openingBackticks) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code-fence detection breaks for languages like c++ or objective-c.
(\w*) only matches word chars, so fences with non‑word info strings aren’t recognized and the parser can desync. Consider allowing any info string and trimming it.
🛠️ Suggested fix
- const tripleBacktickMatch = line.match(/^(`{3,})(\w*)?$/);
+ const tripleBacktickMatch = line.match(/^(`{3,})\s*(.*)?$/);
@@
- codeBlockLang = tripleBacktickMatch[2] || '';
+ codeBlockLang = (tripleBacktickMatch[2] || '').trim();📝 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.
| for (let i = 0; i < lines.length; i++) { | |
| const line = lines[i]; | |
| const tripleBacktickMatch = line.match(/^(`{3,})(\w*)?$/); | |
| if (tripleBacktickMatch && !inCodeBlock) { | |
| // Starting a code block | |
| inCodeBlock = true; | |
| openingBackticks = tripleBacktickMatch[1]; | |
| codeBlockLang = tripleBacktickMatch[2] || ''; | |
| codeBlockContent = []; | |
| } else if (inCodeBlock && line.trim() === openingBackticks) { | |
| for (let i = 0; i < lines.length; i++) { | |
| const line = lines[i]; | |
| const tripleBacktickMatch = line.match(/^(`{3,})\s*(.*)?$/); | |
| if (tripleBacktickMatch && !inCodeBlock) { | |
| // Starting a code block | |
| inCodeBlock = true; | |
| openingBackticks = tripleBacktickMatch[1]; | |
| codeBlockLang = (tripleBacktickMatch[2] || '').trim(); | |
| codeBlockContent = []; | |
| } else if (inCodeBlock && line.trim() === openingBackticks) { |
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/AIAssistantPanel/ChatMessage/index.js`
around lines 23 - 33, The current regex line.match(/^(`{3,})(\w*)?$/) in the
code-fence detection only captures word characters for the info string and fails
for languages like "c++" or "objective-c"; update the match to allow any
non-empty info string (e.g. use a second capture like (.*) or (\S.*)?), then
assign codeBlockLang = (captureGroup2 || '').trim() so you accept and trim
arbitrary info strings; ensure the logic that compares closing backticks still
uses openingBackticks unchanged and that codeBlockLang handling in the
surrounding code (variables tripleBacktickMatch, inCodeBlock, openingBackticks,
codeBlockContent) uses the trimmed value.
Sorry, something went wrong.
All reactions
| <div | ||
| className="markdown-content" | ||
| dangerouslySetInnerHTML={{ __html: sanitizedHtml }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find and examine the specific file
cat -n packages/bruno-app/src/components/AIAssistantPanel/ChatMessage/index.js | sed -n '120,140p'Repository: usebruno/bruno
Length of output: 725
🏁 Script executed:
# Search for DOMPurify usage and sanitization in the file
rg -A 5 -B 5 "dangerouslySetInnerHTML|sanitizedHtml|DOMPurify" packages/bruno-app/src/components/AIAssistantPanel/ChatMessage/index.jsRepository: usebruno/bruno
Length of output: 760
🏁 Script executed:
# Check for any existing biome-ignore comments near lines 129-131
rg "biome-ignore" packages/bruno-app/src/components/AIAssistantPanel/ChatMessage/index.jsRepository: usebruno/bruno
Length of output: 40
🏁 Script executed:
# Check for biome configuration files to understand the linting setup
fd -t f "biome.json|biome.jsonc" .Repository: usebruno/bruno
Length of output: 70
🏁 Script executed:
# Search the codebase for any existing dangerouslySetInnerHTML usages with or without suppressions
rg "dangerouslySetInnerHTML" --type jsx --type tsx -B 2 -A 2Repository: usebruno/bruno
Length of output: 85
🌐 Web query:
Biome lint rule dangerouslySetInnerHTML security rule name and syntax
💡 Result:
Biome’s React/XSS-related lint rule for dangerouslySetInnerHTML is:
- Rule name (grouped):
security/noDangerouslySetInnerHtml(biomejs.dev) - Diagnostic category (as shown in errors):
lint/security/noDangerouslySetInnerHtml(biomejs.dev)
Biome config syntax (biome.json):
{
"linter": {
"rules": {
"security": {
"noDangerouslySetInnerHtml": "error"
}
}
}
}Related rule (when both children and dangerouslySetInnerHTML are used):
security/noDangerouslySetInnerHtmlWithChildren(config key:noDangerouslySetInnerHtmlWithChildren) (biomejs.dev)
Citations:
- 1: https://biomejs.dev/es/linter/rules/no-dangerously-set-inner-html/?utm_source=openai
- 2: https://biomejs.dev/es/linter/rules/no-dangerously-set-inner-html/?utm_source=openai
- 3: https://biomejs.dev/es/linter/rules/no-dangerously-set-inner-html/?utm_source=openai
- 4: https://biomejs.dev/es/linter/rules/no-dangerously-set-inner-html-with-children/?utm_source=openai
🏁 Script executed:
# Check the biome.json configuration
cat biome.jsonRepository: usebruno/bruno
Length of output: 1832
🏁 Script executed:
# Search for dangerouslySetInnerHTML usages with proper file types
rg "dangerouslySetInnerHTML" -B 2 -A 2 --type js --type tsRepository: usebruno/bruno
Length of output: 1401
Add a biome-ignore comment to suppress the lint rule.
The dangerouslySetInnerHTML usage is guarded by DOMPurify sanitization, but Biome's recommended rules will flag it without an explicit suppression. Add a targeted ignore comment:
Suggested fix
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: sanitized via DOMPurify */}
<div
className="markdown-content"
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}📝 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.
| <div | |
| className="markdown-content" | |
| dangerouslySetInnerHTML={{ __html: sanitizedHtml }} | |
| {/* biome-ignore lint/security/noDangerouslySetInnerHtml: sanitized via DOMPurify */} | |
| <div | |
| className="markdown-content" | |
| dangerouslySetInnerHTML={{ __html: sanitizedHtml }} |
🧰 Tools
🪛 Biome (2.1.2)
[error] 131-131: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/AIAssistantPanel/ChatMessage/index.js`
around lines 129 - 131, Add a targeted Biome ignore comment immediately above
the div using dangerouslySetInnerHTML to suppress the specific lint rule that
flags direct HTML insertion (while keeping the existing DOMPurify sanitization).
Locate the JSX element with className "markdown-content" that sets
dangerouslySetInnerHTML with sanitizedHtml and insert a single-line or block
Biome ignore for the specific lint rule (e.g., the react/no-danger-equivalent
rule) so only this occurrence is suppressed rather than disabling linting
broadly.
Sorry, something went wrong.
All reactions
| const CodeBlock = ({ code, language = 'javascript' }) => { | ||
| const [copied, setCopied] = useState(false); | ||
|
|
||
| const handleCopy = async () => { | ||
| try { | ||
| await navigator.clipboard.writeText(code); | ||
| setCopied(true); | ||
| toast.success('Copied to clipboard'); | ||
| setTimeout(() => setCopied(false), 2000); | ||
| } catch (err) { | ||
| toast.error('Failed to copy'); | ||
| } | ||
| }; | ||
|
|
||
| const lines = code.split('\n'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against undefined code inputs.
code.split('\n') will throw if code is undefined or null. Defaulting to an empty string avoids runtime errors.
🐛 Proposed fix
-const CodeBlock = ({ code, language = 'javascript' }) => {
+const CodeBlock = ({ code = '', language = 'javascript' }) => {🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/AIAssistantPanel/CodeBlock/index.js` around
lines 148 - 162, The CodeBlock component currently calls code.split('\n') which
will throw if code is null/undefined; update the component to guard the input by
defaulting code to an empty string (e.g., change the prop default or create a
local safeCode = code ?? ''), use safeCode for splitting and for
navigator.clipboard.writeText in handleCopy, and ensure any references (lines,
handleCopy) use that safe value so runtime errors are avoided.
Sorry, something went wrong.
All reactions
| import React, { useState, useMemo, useCallback } from 'react'; | ||
| import { useSelector, useDispatch } from 'react-redux'; | ||
| import { IconSearch, IconX } from '@tabler/icons'; | ||
| import { addFileToAISelection, removeFileFromAISelection, clearAIFileSelection } from 'providers/ReduxStore/slices/app'; | ||
| import { isItemARequest, isItemAFolder } from 'utils/tabs'; | ||
| import { debounce } from 'lodash'; | ||
| import StyledWrapper from './StyledWrapper'; | ||
|
|
||
| // Flatten collection items recursively, including path info | ||
| const flattenItems = (items, parentPath = '') => { | ||
| const result = []; | ||
| for (const item of items || []) { | ||
| const currentPath = parentPath ? `${parentPath}/${item.name}` : item.name; | ||
|
|
||
| if (isItemARequest(item)) { | ||
| result.push({ | ||
| ...item, | ||
| path: parentPath | ||
| }); | ||
| } else if (isI CE3F temAFolder(item) && item.items) { | ||
| result.push(...flattenItems(item.items, currentPath)); | ||
| } | ||
| } | ||
| return result; | ||
| }; | ||
|
|
||
| const FileSelector = ({ collectionUid }) => { | ||
| const dispatch = useDispatch(); | ||
| const [searchText, setSearchText] = useState(''); | ||
| const [searchInput, setSearchInput] = useState(''); | ||
|
|
||
| // Debounced search to avoid filtering on every keystroke | ||
| const debouncedSetSearch = useMemo( | ||
| () => debounce((value) => setSearchText(value), 150), | ||
| [] | ||
| ); | ||
|
|
||
| const handleSearchChange = useCallback((e) => { | ||
| const value = e.target.value; | ||
| setSearchInput(value); | ||
| debouncedSetSearch(value); | ||
| }, [debouncedSetSearch]); | ||
|
|
||
| const handleClearSearch = useCallback(() => { | ||
| setSearchInput(''); | ||
| setSearchText(''); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd "FileSelector" --type f --extension js --extension jsxRepository: usebruno/bruno
Length of output: 172
🌐 Web query:
lodash debounce cancel method
💡 Result:
In Lodash, _.debounce(func, wait, options) returns a debounced function that has a .cancel() method.
debounced.cancel(): cancels any pending (scheduled) invocation offunc(i.e., clears the internal timer), so the trailing call won’t run. It’s commonly used for cleanup (e.g., when unmounting a component or navigating away). [1][2]
Related:
debounced.flush(): immediately invokes any pending invocation instead of waiting. [1][2]
Example
const debouncedSave = _.debounce(save, 500);
// schedule
debouncedSave();
// prevent the scheduled trailing call from running
debouncedSave.cancel();Sources: Lodash debounce docs. [1][2]
Cancel pending debounced updates on clear and unmount.
A pending debounce can re-apply the old search text after the user clicks "Clear", and can attempt to set state after component unmount. Cancel pending debounced calls on clear and add cleanup on unmount.
Suggested fix
-import React, { useState, useMemo, useCallback } from 'react';
+import React, { useState, useMemo, useCallback, useEffect } from 'react';
@@
const debouncedSetSearch = useMemo(
() => debounce((value) => setSearchText(value), 150),
[]
);
+ useEffect(() => {
+ return () => {
+ debouncedSetSearch.cancel();
+ };
+ }, [debouncedSetSearch]);
+
const handleClearSearch = useCallback(() => {
+ debouncedSetSearch.cancel();
setSearchInput('');
setSearchText('');
- }, []);
+ }, [debouncedSetSearch]);🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/AIAssistantPanel/FileSelector/index.js`
around lines 1 - 47, The debouncedSetSearch created with useMemo can fire after
clearing or after unmount; call debouncedSetSearch.cancel() inside
handleClearSearch to avoid re-applying the old search text, and add a useEffect
cleanup that calls debouncedSetSearch.cancel() on unmount to prevent state
updates after unmount. Update references to debouncedSetSearch (from useMemo)
and handleClearSearch accordingly so the debounce is cancelled both when
clearing and when the component unmounts.
Sorry, something went wrong.
All reactions
| <IconX | ||
| size={14} | ||
| style={{ cursor: 'pointer' }} | ||
| onClick={handleClearSearch} | ||
| role="button" | ||
| aria-label="Clear search" | ||
| tabIndex={0} | ||
| onKeyDown={(e) => e.key === 'Enter' && handleClearSearch()} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add Space key support for button/checkbox roles.
Keyboard users expect Space to toggle. Currently only Enter works.
🛠️ Proposed fix
- onKeyDown={(e) => e.key === 'Enter' && handleClearSearch()}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ handleClearSearch();
+ }
+ }}
@@
- onKeyDown={(e) => e.key === 'Enter' && handleToggleItem(item)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ handleToggleItem(item);
+ }
+ }}Also applies to: 144-152
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/AIAssistantPanel/FileSelector/index.js`
around lines 112 - 120, The IconX clear control uses onKeyDown handling that
only triggers on Enter; update the keyboard handler for the IconX instance(s)
(the one using onClick={handleClearSearch} and the similar control at the second
occurrence) to also respond to Spacebar by checking for e.key === 'Enter' ||
e.key === ' ' || e.key === 'Spacebar' (and call e.preventDefault() before
invoking handleClearSearch), ensuring the role="button" + tabIndex remains;
apply the same change to the other occurrence (lines near the second IconX block
referenced) so both keyboard users can toggle via Space as well as Enter.
Sorry, something went wrong.
All reactions
| // Update AI panel context when panel opens (e.g., via Cmd+L) | ||
| useEffect(() => { | ||
| if (showAIPanel) { | ||
| dispatch(updateAIPanelContext({ | ||
| scriptType: activeTab, | ||
| currentScript: getCurrentScript(activeTab), | ||
| item, 3D81 | ||
| collection | ||
| })); | ||
| } | ||
| }, [showAIPanel, item, collection, dispatch]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing activeTab in dependency array.
getCurrentScript(activeTab) is called inside the effect, but activeTab isn't listed in the deps. This could lead to stale context being dispatched if activeTab changes while other dependencies remain stable.
🐛 Suggested fix
useEffect(() => {
if (showAIPanel) {
dispatch(updateAIPanelContext({
scriptType: activeTab,
currentScript: getCurrentScript(activeTab),
item,
collection
}));
}
- }, [showAIPanel, item, collection, dispatch]);
+ }, [showAIPanel, activeTab, item, collection, dispatch]);📝 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.
| // Update AI panel context when panel opens (e.g., via Cmd+L) | |
| useEffect(() => { | |
| if (showAIPanel) { | |
| dispatch(updateAIPanelContext({ | |
| scriptType: activeTab, | |
| currentScript: getCurrentScript(activeTab), | |
| item, | |
| collection | |
| })); | |
| } | |
| }, [showAIPanel, item, collection, dispatch]); | |
| // Update AI panel context when panel opens (e.g., via Cmd+L) | |
| useEffect(() => { | |
| if (showAIPanel) { | |
| dispatch(updateAIPanelContext({ | |
| scriptType: activeTab, | |
| currentScript: getCurrentScript(activeTab), | |
| item, | |
| collection | |
| })); | |
| } | |
| }, [showAIPanel, activeTab, item, collection, dispatch]); |
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/Script/index.js` around lines
102 - 112, The effect that updates AI panel context (useEffect) references
activeTab and calls getCurrentScript(activeTab) but does not include activeTab
in the dependency array; update the dependency array for that useEffect to
include activeTab so the effect re-runs when activeTab changes (retain
showAIPanel, item, collection, dispatch and any other referenced variables like
getCurrentScript if it's not stable).
Sorry, something went wrong.
All reactions
| const forceVerticalLayoutForAI = useSelector((state) => state.app.forceVerticalLayoutForAI); | ||
| // Force vertical layout when AI panel is open, otherwise use user preference | ||
| const isVerticalLayout = forceVerticalLayoutForAI ? true : preferences?.layout?.responsePaneOrientation === 'vertical'; | ||
| const isConsoleOpen = useSelector((state) => state.logs.isConsoleOpen); | ||
| const showAIPanel = useSelector((state) => state.app.showAIPanel); | ||
| const responsePaneHiddenByAI = useSelector((state) => state.app.responsePaneHiddenByAI); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd "RequestTabPanel" --type fRepository: usebruno/bruno
Length of output: 40
🏁 Script executed:
cat -n packages/bruno-app/src/components/RequestTabPanel/index.js | head -100Repository: usebruno/bruno
Length of output: 6085
Hooks are called after an early return, violating React's Rules of Hooks.
Lines 49–61 invoke useSelector hooks after the if (typeof window === 'undefined') guard on lines 46–48. This conditional execution of hooks breaks the requirement that hooks run unconditionally on every render. Extract the inner logic into a separate component to ensure all hooks execute at the top level.
Suggested fix: wrap with component guard
-const RequestTabPanel = () => {
- if (typeof window === 'undefined') {
- return <div></div>;
- }
+const RequestTabPanelInner = () => {
const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs);
...
-};
-
-export default RequestTabPanel;
+};
+
+const RequestTabPanel = () => {
+ if (typeof window === 'undefined') {
+ return <div />;
+ }
+ return <RequestTabPanelInner />;
+};
+
+export default RequestTabPanel;🧰 Tools
🪛 Biome (2.1.2)
[error] 56-56: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 59-59: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 60-60: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 61-61: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestTabPanel/index.js` around lines 56 -
61, The hooks (useSelector calls for forceVerticalLayoutForAI, isConsoleOpen,
showAIPanel, responsePaneHiddenByAI and the computed isVerticalLayout) are being
invoked after an early return guarded by if (typeof window === 'undefined'),
violating Rules of Hooks; fix by extracting the hook-using logic into a new
child component (e.g., InnerRequestTabPanel) which contains all useSelector
calls and the isVerticalLayout computation, and have the original component
conditionally render that child (or null) based on typeof window === 'undefined'
so hooks always run at the top level of the child component.
Sorry, something went wrong.
All reactions
| {!shouldHideResponsePane && ( | ||
| <> | ||
| <div | ||
| className="dragbar-wrapper" | ||
| onDoubleClick={(e) => { | ||
| e.preventDefault(); | ||
| resetPaneBoundaries(); | ||
| }} | ||
| onMouseDown={handleDragbarMouseDown} | ||
| > | ||
| <div className="dragbar-handle" /> | ||
| </div> | ||
|
|
||
| <section className="response-pane flex-grow overflow-x-auto"> | ||
| {renderResponsePane()} | ||
| </section> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cd packages/bruno-app && find . -name "RequestTabPanel" -o -name "index.js" | grep -i requesttab | head -5Repository: usebruno/bruno
Length of output: 172
🏁 Script executed:
find packages/bruno-app/src/components -name "*RequestTab*" -type dRepository: usebruno/bruno
Length of output: 210
🏁 Script executed:
ls -la packages/bruno-app/src/components/ | grep -i requestRepository: usebruno/bruno
Length of output: 319
🏁 Script executed:
wc -l packages/bruno-app/src/components/RequestTabPanel/index.jsRepository: usebruno/bruno
Length of output: 120
🏁 Script executed:
cat -n packages/bruno-app/src/components/RequestTabPanel/index.js | head -100Repository: usebruno/bruno
Length of output: 6085
🏁 Script executed:
cat -n packages/bruno-app/src/components/RequestTabPanel/index.js | sed -n '350,385p'Repository: usebruno/bruno
Length of output: 1575
🏁 Script executed:
cat -n packages/bruno-app/src/components/RequestTabPanel/index.js | sed -n '150,250p'Repository: usebruno/bruno
Length of output: 3930
🏁 Script executed:
grep -n "requestPaneStyle" packages/bruno-app/src/components/RequestTabPanel/index.jsRepository: usebruno/bruno
Length of output: 146
🏁 Script executed:
cat -n packages/bruno-
3D81
app/src/components/RequestTabPanel/index.js | sed -n '320,345p'Repository: usebruno/bruno
Length of output: 1125
🏁 Script executed:
grep -n "request-pane\|requestPane" packages/bruno-app/src/components/RequestTabPanel/index.jsRepository: usebruno/bruno
Length of output: 193
🏁 Script executed:
cat packages/bruno-app/src/components/RequestTabPanel/StyledWrapper.jsRepository: usebruno/bruno
Length of output: 4660
🏁 Script executed:
cat -n packages/bruno-app/src/components/RequestTabPanel/index.js | sed -n '348,365p'Repository: usebruno/bruno
Length of output: 758
Request pane should expand when response pane is hidden.
When shouldHideResponsePane is true, the response pane is removed but requestPaneStyle retains split dimensions (fixed height in vertical layout, fixed width in horizontal layout), leaving unused space. The request pane should fill the available space when the response pane is hidden.
🛠️ Suggested adjustment
- const requestPaneStyle = isVerticalLayout
- ? {
- height: `${Math.max(topPaneHeight, MIN_TOP_PANE_HEIGHT)}px`,
- minHeight: `${MIN_TOP_PANE_HEIGHT}px`,
- width: '100%'
- }
- : {
- width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`
- };
+ const requestPaneStyle = shouldHideResponsePane
+ ? {
+ width: '100%',
+ height: '100%'
+ }
+ : isVerticalLayout
+ ? {
+ height: `${Math.max(topPaneHeight, MIN_TOP_PANE_HEIGHT)}px`,
+ minHeight: `${MIN_TOP_PANE_HEIGHT}px`,
+ width: '100%'
+ }
+ : {
+ width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`
+ };🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestTabPanel/index.js` around lines 360
- 375, When shouldHideResponsePane is true the request pane keeps split
dimensions from requestPaneStyle and leaves empty space; update the layout logic
that computes/assigns requestPaneStyle so that when shouldHideResponsePane ===
true you remove/override the fixed split size and let the request pane flex to
fill available area (e.g. set style to flex-grow/height:100% in vertical or
width:100% in horizontal). Locate where requestPaneStyle is computed/used in
RequestTabPanel (and keep existing symbols like shouldHideResponsePane,
requestPaneStyle, resetPaneBoundaries, handleDragbarMouseDown,
renderResponsePane) and apply the override only when the response pane is hidden
so the dragbar/drag handlers still work when visible.
Sorry, something went wrong.
All reactions
| - Method: ${request.method || 'GET'} | ||
| - URL: ${request.url || ''} | ||
| - Headers: ${JSON.stringify(request.headers || {}, null, 2)} | ||
| - Body: ${JSON.stringify(request.body || null, null, 2)} | ||
|
|
||
| ## Response Details | ||
| CE3F | - Status: ${response.status || 'N/A'} | |
| - Status Text: ${response.statusText || ''} | ||
| - Headers: ${JSON.stringify(response.headers || {}, null, 2)} | ||
| - Body: ${JSON.stringify(response.data || response.body || null, null, 2)} | ||
| - Response Time: ${response.responseTime || 'N/A'}ms |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Check if the file exists and get its size
wc -l packages/bruno-electron/src/ai/prompts/index.js 2>/dev/null || echo "File not found or error checking"Repository: usebruno/bruno
Length of output: 109
🏁 Script executed:
# Read the entire file to understand context
cat -n packages/bruno-electron/src/ai/prompts/index.jsRepository: usebruno/bruno
Length of output: 22336
🏁 Script executed:
# Search for functions that call these prompt generators
rg -n "generateTestsPrompt|improveScriptPrompt|chatPrompt|multiFileChatPrompt" packages/bruno-electron/src --type js --type ts -A 2Repository: usebruno/bruno
Length of output: 3158
🏁 Script executed:
# Look for AI service calls or API endpoints
rg -n "fetch|axios|http|api" packages/bruno-electron/src/ai --type js --type ts | head -50Repository: usebruno/bruno
Length of output: 822
🏁 Script executed:
# Read the AI service integration file
cat -n packages/bruno-electron/src/ipc/ai.jsRepository: usebruno/bruno
Length of output: 7245
🏁 Script executed:
# Search for any existing redaction or consent mechanisms
rg -n "redact|sanitize|consent|privacy|secret" packages/bruno-electron/src/ai --type js --type tsRepository: usebruno/bruno
Length of output: 40
🏁 Script executed:
# Check if there are any guard rails or warnings in the UI/frontend
find packages -name "*.jsx" -o -name "*.tsx" | xargs rg -l "ai:generate|generateTests|improveScript" | head -5Repository: usebruno/bruno
Length of output: 40
🏁 Script executed:
# Find frontend files that handle AI generation
find packages/bruno-web packages/bruno-electron -name "*.jsx" -o -name "*.tsx" -o -name "*.vue" 2>/dev/null | xargs grep -l "ai:generate\|AI\|generateTests" 2>/dev/null | head -10Repository: usebruno/bruno
Length of output: 40
🏁 Script executed:
# Check for any privacy disclaimers or consent flows in the codebase
rg -n "privacy\|disclaimer\|warning\|PII\|sensitive\|secret" packages/bruno-electron/src --type js --type ts | grep -i "ai\|prompt\|external" | head -20Repository: usebruno/bruno
Length of output: 40
🏁 Script executed:
# Find all files that reference the AI IPC handler
rg "ai:generate" packages --type js --type ts --type jsx --type tsx -lRepository: usebruno/bruno
Length of output: 85
🏁 Script executed:
# Look for any environment checks or configuration related to AI
rg -n "OPENAI_API_KEY\|AI_CONFIG\|ai:" packages/bruno-electron --type js --type ts -B 2 -A 2 | head -80Repository: usebruno/bruno
Length of output: 40
🏁 Script executed:
# Verify the exact lines where secrets are exposed in multiFileChatPrompt
sed -n '343,352p' packages/bruno-electron/src/ai/prompts/index.jsRepository: usebruno/bruno
Length of output: 786
🏁 Script executed:
# Check if there's any documentation about AI privacy/security
find packages -name "*.md" | xargs grep -l "AI\|OpenAI\|privacy\|security" 2>/dev/null | head -5Repository: usebruno/bruno
Length of output: 232
🏁 Script executed:
# Verify the exact sensitive data in chatPrompt function
sed -n '248,263p' packages/bruno-electron/src/ai/prompts/index.jsRepository: usebruno/bruno
Length of output: 1538
🏁 Script executed:
# Check improveScriptPrompt for any sensitive data exposure
sed -n '182,217p' packages/bruno-electron/src/ai/prompts/index.jsRepository: usebruno/bruno
Length of output: 1383
Redact secrets/PII before sending prompts to the external AI provider.
Headers, bodies, and scripts can contain credentials or sensitive data. Sending them verbatim to OpenAI is a privacy and compliance risk. Sanitize sensitive fields (authorization headers, API keys, cookies, etc.) before building these prompts.
Implement a redaction helper and apply it consistently across generateTestsPrompt, chatPrompt, and multiFileChatPrompt:
Suggested implementation
const redactForAi = (data = {}) => {
const safe = { ...data };
if (safe.headers && typeof safe.headers === 'object') {
const headers = { ...safe.headers };
const sensitive = ['authorization', 'cookie', 'set-cookie', 'x-api-key'];
Object.keys(headers).forEach((header) => {
if (sensitive.includes(header.toLowerCase())) {
headers[header] = '[REDACTED]';
}
});
safe.headers = headers;
}
return safe;
};Then apply to request/response objects before interpolating into prompts.
🤖 Prompt for AI Agents
In `@packages/bruno-electron/src/ai/prompts/index.js` around lines 145 - 155, Add
a redaction helper and apply it before interpolating request/response data into
prompts: implement a function (e.g., redactForAi) that clones the passed object
and replaces sensitive header fields (authorization, cookie, set-cookie,
x-api-key, etc.) and any obvious PII in bodies with a placeholder like
"[REDACTED]"; then call redactForAi(request) and redactForAi(response) inside
generateTestsPrompt, chatPrompt, and multiFileChatPrompt and use the sanitized
objects when building the template strings so headers/bodies sent to the
external AI provider never include raw credentials or cookies.
Sorry, something went wrong.
All reactions
| ipcMain.handle('ai:generate', async (event, { action, context }) => { | ||
| // Get API key from environment variable | ||
| const apiKey = process.env.OPENAI_API_KEY; | ||
| if (!apiKey) { | ||
| throw new Error('OPENAI_API_KEY environment variable is not set. Please add it to your .env file in bruno-electron.'); | ||
| } | ||
|
|
||
| // Create provider and get model | ||
| const provider = createProvider('openai', apiKey); | ||
| const modelId = AI_CONFIG.model; | ||
|
|
||
| // Build prompt based on action | ||
| let prompt; | ||
| switch (action) { | ||
| case 'generate-tests': | ||
| if (!context.response) { | ||
| throw new Error('No response data available. Please run the request first.'); | ||
| } | ||
| prompt = generateTestsPrompt( | ||
| context.request || {}, | ||
| context.response || {}, | ||
| context.currentScript || '' | ||
| ); | ||
| break; | ||
|
|
||
| case 'improve-script': | ||
| if (!context.script || !context.script.trim()) { | ||
| throw new Error('No script to improve. Please write some code first.'); | ||
| } | ||
| prompt = improveScriptPrompt( | ||
| context.script, | ||
| context.scriptType || 'post-response', | ||
| context.request || {} | ||
| ); | ||
| break; | ||
|
|
||
| case 'chat': | ||
| prompt = chatPrompt( | ||
| context.userMessage, | ||
| context.script || '', | ||
| context.scriptType || 'post-response', | ||
| context.request || {}, | ||
| context.response || null, | ||
| context.mode || 'ask-before-edit', | ||
| context.testsScript || '' | ||
| ); | ||
| break; | ||
|
|
||
| case 'multi-file-chat': | ||
| if (!context.files || context.files.length === 0) { | ||
| throw new Error('No files selected. Please select at least one file.'); | ||
| } | ||
| prompt = multiFileChatPrompt( | ||
| context.userMessage, | ||
| context.files, | ||
| context.mode || 'ask-before-edit' | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against missing context to avoid TypeError.
If the renderer sends an invalid payload (or future callers omit context), the handler will throw before a useful error message. Default to {} and use that object consistently.
🔧 Proposed fix
ipcMain.handle('ai:generate', async (event, { action, context }) => {
+ const ctx = context || {};
// Get API key from environment variable
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error('OPENAI_API_KEY environment variable is not set. Please add it to your .env file in bruno-electron.');
}
@@
switch (action) {
case 'generate-tests':
- if (!context.response) {
+ if (!ctx.response) {
throw new Error('No response data available. Please run the request first.');
}
prompt = generateTestsPrompt(
- context.request || {},
- context.response || {},
- context.currentScript || ''
+ ctx.request || {},
+ ctx.response || {},
+ ctx.currentScript || ''
);
break;
case 'improve-script':
- if (!context.script || !context.script.trim()) {
+ if (!ctx.script || !ctx.script.trim()) {
throw new Error('No script to improve. Please write some code first.');
}
prompt = improveScriptPrompt(
- context.script,
- context.scriptType || 'post-response',
- context.request || {}
+ ctx.script,
+ ctx.scriptType || 'post-response',
+ ctx.request || {}
);
break;
case 'chat':
prompt = chatPrompt(
- context.userMessage,
- context.script || '',
- context.scriptType || 'post-response',
- context.request || {},
- context.response || null,
- context.mode || 'ask-before-edit',
- context.testsScript || ''
+ ctx.userMessage,
+ ctx.script || '',
+ ctx.scriptType || 'post-response',
+ ctx.request || {},
+ ctx.response || null,
+ ctx.mode || 'ask-before-edit',
+ ctx.testsScript || ''
);
break;
case 'multi-file-chat':
- if (!context.files || context.files.length === 0) {
+ if (!ctx.files || ctx.files.length === 0) {
throw new Error('No files selected. Please select at least one file.');
}
prompt = multiFileChatPrompt(
- context.userMessage,
- context.files,
- context.mode || 'ask-before-edit'
+ ctx.userMessage,
+ ctx.files,
+ ctx.mode || 'ask-before-edit'
);
break;🤖 Prompt for AI Agents
In `@packages/bruno-electron/src/ipc/ai.js` around lines 34 - 90, The handler for
ipcMain.handle('ai:generate') can receive an undefined payload and currently
uses context directly, causing TypeError; ensure the second argument defaults to
an object and that context defaults to {} before use (either by changing the
handler signature to default the param to {} and/or adding an early line like:
if (!context) context = {}), so all downstream uses (generateTestsPrompt,
improveScriptPrompt, chatPrompt, multiFileChatPrompt and any context.* checks)
operate on a safe object and return a descriptive error when required fields are
missing.
Sorry, something went wrong.
All reactions
Reviewers
coderabbitai[bot]
helloanoop
bijin-bruno
lohit-bruno
naman-bruno
At least 3 approving reviews are required to merge this pull request.
Assignees
No one assignedLabels
Projects
Milestone
No milestoneDevelopment
Successfully merging this pull request may close these issues.
Description
Contribution Checklist:
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
Publishing to New Package Managers
Please see here for more information.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.