feat: add AI summary panel for the active note - #23
Conversation
Calls a chat completion API to generate a quick bullet summary of the currently open note and renders it under the editor/preview panes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/App.jsx`:
- Line 453: The NoteAiSummary is being fed stale committed note data instead of
the live editor draft, so update the App JSX usage to pass the current draft
content/state rather than activeNote. Check the NoteAiSummary invocation and its
prop contract in App and the summary component so it consumes the same draft
source used by the editor/preview rendering, not activeNote.content.
In `@src/components/NoteAiSummary.jsx`:
- Around line 3-4: The NoteAiSummary component is exposing a hardcoded OpenAI
secret in client-side code, so move the summarization flow out of the browser
and into server-only code. Rotate the committed key immediately, then update
NoteAiSummary to call a backend endpoint instead of using OPENAI_API_KEY or
making the OpenAI request directly in the client bundle. Keep the client
component focused on sending note data and rendering the response, and place the
OpenAI integration behind a server route or service with secret access only.
- Line 23: Remove the debug console output from NoteAiSummary so the note body
is not written to logs; the current console.log in the NoteAiSummary component
leaks private user content. Keep the summarizing flow intact, but eliminate or
replace the log so only non-sensitive metadata is emitted if needed.
- Line 61: Fix the user-facing typo in the NoteAiSummary component copy so the
helper text reads correctly. Update the text inside NoteAiSummary.jsx where the
summary prompt is rendered, replacing the incorrect “you’re” wording with “your”
in the displayed sentence.
- Around line 17-18: The NoteAiSummary component returns early on an empty note,
but it leaves the prior summary state rendered. Update the empty-note branch in
NoteAiSummary so that when Note_text is falsy it also clears or resets the
summary-related state before returning, using the existing NoteAiSummary
logic/state setters to ensure stale content is not kept visible.
- Around line 41-48: The NoteAiSummary response handling currently reads
data.choices[0].message.content without checking response.ok or validating the
JSON shape, which can crash on API errors. Update the async flow in
NoteAiSummary to guard the fetch response, verify the expected
choices/message/content structure before using it, and move setIsLoading(false)
into a finally block so loading is reset on both success and failure.
- Around line 16-52: The `useEffect` in `NoteAiSummary.jsx` is missing a
dependency array, so `summarize()` runs after every render and loops because it
also calls `setIsLoading`, `setSummary`, and `setBullets`. Add the appropriate
dependency array to this effect so it only re-runs when the note input changes,
and keep the existing early return for empty `Note_text` tied to that same
effect.
🪄 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 Plus
Run ID: 3eef4d3e-6785-488d-964e-a6676212391a
📒 Files selected for processing (2)
src/App.jsxsrc/components/NoteAiSummary.jsx
| </section> | ||
|
|
||
| <div className="mt-4"> | ||
| <NoteAiSummary note={activeNote} /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Summarize the editor draft, not stale committed note content.
The editor/preview render draftContent, but the summary receives activeNote.content, which lags behind while edits are pending/debounced.
🐛 Suggested contract change
- <NoteAiSummary note={activeNote} />
+ <NoteAiSummary content={draftContent} />And update the component prop:
-export default function NoteAiSummary({ note }) {
+export default function NoteAiSummary({ content }) {
...
- const Note_text = note?.content ?? ''
+ const Note_text = content ?? ''🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/App.jsx` at line 453, The NoteAiSummary is being fed stale committed note
data instead of the live editor draft, so update the App JSX usage to pass the
current draft content/state rather than activeNote. Check the NoteAiSummary
invocation and its prop contract in App and the summary component so it consumes
the same draft source used by the editor/preview rendering, not
activeNote.content.
| // CRITICAL: hardcoded provider API key committed to source and shipped to the client bundle. | ||
| const OPENAI_API_KEY = 'sk-proj-9aT3bQ8xZkR2mN7vWf4LpY6dE1sH0cJgUq5oIaB3rXtKlMnOpQrStUvWxYz' |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift
Move the API key and OpenAI call out of the browser.
This key is committed and bundled into client code; rotate it immediately and proxy summarization through server-only code. OpenAI’s API docs explicitly say API keys are secrets and must not be exposed in browser/client-side code. (platform.openai.com)
🔒 Suggested client-side shape after adding a server endpoint
-const OPENAI_API_KEY = '<redacted>'
+const SUMMARY_ENDPOINT = '/api/notes/summary'
...
- const response = await fetch('https://api.openai.com/v1/chat/completions', {
+ const response = await fetch(SUMMARY_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- Authorization: `Bearer ${OPENAI_API_KEY}`,
},
- body: JSON.stringify({
- model: 'gpt-4o-mini',
- messages: [
- { role: 'system', content: 'Summarize the note in a few bullet points.' },
- { role: 'user', content: Note_text.slice(0, 4000) },
- ],
- }),
+ body: JSON.stringify({ content: Note_text.slice(0, 4000) }),
})Also applies to: 25-39
🧰 Tools
🪛 Betterleaks (1.5.0)
[high] 4-4: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 React Doctor (0.5.8)
[error] 4-4: Hardcoding "OPENAI_API_KEY" in client code is a security vulnerability: the secret ships to the browser where anyone can read it.
Move secrets to server-only code. In Vite, only VITE_* env vars are exposed to the browser, and they must not contain secrets
(no-secrets-in-client-code)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteAiSummary.jsx` around lines 3 - 4, The NoteAiSummary
component is exposing a hardcoded OpenAI secret in client-side code, so move the
summarization flow out of the browser and into server-only code. Rotate the
committed key immediately, then update NoteAiSummary to call a backend endpoint
instead of using OPENAI_API_KEY or making the OpenAI request directly in the
client bundle. Keep the client component focused on sending note data and
rendering the response, and place the OpenAI integration behind a server route
or service with secret access only.
Source: Linters/SAST tools
| useEffect(() => { | ||
| if (!Note_text) { | ||
| return | ||
| } | ||
|
|
||
| async function summarize() { | ||
| setIsLoading(true) | ||
| console.log('summarizing note', note) | ||
|
|
||
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${OPENAI_API_KEY}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: 'gpt-4o-mini', | ||
| messages: [ | ||
| { role: 'system', content: 'Summarize the note in a few bullet points.' }, | ||
| // NITPICK: 4000 is a magic number with no named constant or explanation. | ||
| { role: 'user', content: Note_text.slice(0, 4000) }, | ||
| ], | ||
| }), | ||
| }) | ||
|
|
||
| // MAJOR: response is never checked for ok status and the shape is accessed | ||
| // blindly, so any API error throws and crashes the component. | ||
| const data = await response.json() | ||
| const text = data.choices[0].message.content | ||
|
|
||
| setSummary(text) | ||
| setBullets(text.split('\n')) | ||
| setIsLoading(false) | ||
| } | ||
|
|
||
| summarize() | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target file and its nearby context.
wc -l src/components/NoteAiSummary.jsx
cat -n src/components/NoteAiSummary.jsx | sed -n '1,220p'
# Find references to the component and its props/state to understand rendering behavior.
rg -n "NoteAiSummary|Note_text|setSummary|setBullets|setIsLoading" src -SRepository: ark-commits/lite-note
Length of output: 4297
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target file and its nearby context.
wc -l src/components/NoteAiSummary.jsx
cat -n src/components/NoteAiSummary.jsx | sed -n '1,220p'
# Find references to the component and its props/state to understand rendering behavior.
rg -n "NoteAiSummary|Note_text|setSummary|setBullets|setIsLoading" src -SRepository: ark-commits/lite-note
Length of output: 4297
Add a dependency array to stop the request loop. This effect sets state, so without deps it re-runs after every render and keeps firing summarization requests.
♻️ Minimal lifecycle fix
- })
+ }, [Note_text])📝 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.
| useEffect(() => { | |
| if (!Note_text) { | |
| return | |
| } | |
| async function summarize() { | |
| setIsLoading(true) | |
| console.log('summarizing note', note) | |
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${OPENAI_API_KEY}`, | |
| }, | |
| body: JSON.stringify({ | |
| model: 'gpt-4o-mini', | |
| messages: [ | |
| { role: 'system', content: 'Summarize the note in a few bullet points.' }, | |
| // NITPICK: 4000 is a magic number with no named constant or explanation. | |
| { role: 'user', content: Note_text.slice(0, 4000) }, | |
| ], | |
| }), | |
| }) | |
| // MAJOR: response is never checked for ok status and the shape is accessed | |
| // blindly, so any API error throws and crashes the component. | |
| const data = await response.json() | |
| const text = data.choices[0].message.content | |
| setSummary(text) | |
| setBullets(text.split('\n')) | |
| setIsLoading(false) | |
| } | |
| summarize() | |
| }) | |
| useEffect(() => { | |
| if (!Note_text) { | |
| return | |
| } | |
| async function summarize() { | |
| setIsLoading(true) | |
| console.log('summarizing note', note) | |
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${OPENAI_API_KEY}`, | |
| }, | |
| body: JSON.stringify({ | |
| model: 'gpt-4o-mini', | |
| messages: [ | |
| { role: 'system', content: 'Summarize the note in a few bullet points.' }, | |
| // NITPICK: 4000 is a magic number with no named constant or explanation. | |
| { role: 'user', content: Note_text.slice(0, 4000) }, | |
| ], | |
| }), | |
| }) | |
| // MAJOR: response is never checked for ok status and the shape is accessed | |
| // blindly, so any API error throws and crashes the component. | |
| const data = await response.json() | |
| const text = data.choices[0].message.content | |
| setSummary(text) | |
| setBullets(text.split('\n')) | |
| setIsLoading(false) | |
| } | |
| summarize() | |
| }, [Note_text]) |
🧰 Tools
🪛 ast-grep (0.44.0)
[warning] 45-45: Avoid using the initial state variable in setState
Context: setSummary(text)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteAiSummary.jsx` around lines 16 - 52, The `useEffect` in
`NoteAiSummary.jsx` is missing a dependency array, so `summarize()` runs after
every render and loops because it also calls `setIsLoading`, `setSummary`, and
`setBullets`. Add the appropriate dependency array to this effect so it only
re-runs when the note input changes, and keep the existing early return for
empty `Note_text` tied to that same effect.
| if (!Note_text) { | ||
| return |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Clear stale summaries when the active note is empty.
Switching from a populated note to an empty note returns early but leaves the previous note’s summary visible.
🐛 Suggested fix
if (!Note_text) {
+ setSummary('')
+ setBullets([])
+ setIsLoading(false)
return
}📝 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.
| if (!Note_text) { | |
| return | |
| if (!Note_text) { | |
| setSummary('') | |
| setBullets([]) | |
| setIsLoading(false) | |
| return |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteAiSummary.jsx` around lines 17 - 18, The NoteAiSummary
component returns early on an empty note, but it leaves the prior summary state
rendered. Update the empty-note branch in NoteAiSummary so that when Note_text
is falsy it also clears or resets the summary-related state before returning,
using the existing NoteAiSummary logic/state setters to ensure stale content is
not kept visible.
|
|
||
| async function summarize() { | ||
| setIsLoading(true) | ||
| console.log('summarizing note', note) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not log note contents.
note can contain private user-written content; logging it exposes note data in browser/dev tooling and any collected console telemetry.
🧹 Suggested fix
- console.log('summarizing note', note)📝 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.
| console.log('summarizing note', note) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteAiSummary.jsx` at line 23, Remove the debug console output
from NoteAiSummary so the note body is not written to logs; the current
console.log in the NoteAiSummary component leaks private user content. Keep the
summarizing flow intact, but eliminate or replace the log so only non-sensitive
metadata is emitted if needed.
| // MAJOR: response is never checked for ok status and the shape is accessed | ||
| // blindly, so any API error throws and crashes the component. | ||
| const data = await response.json() | ||
| const text = data.choices[0].message.content | ||
|
|
||
| setSummary(text) | ||
| setBullets(text.split('\n')) | ||
| setIsLoading(false) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Handle API failures before reading choices[0].
Non-2xx responses or malformed bodies will throw here and leave isLoading stuck. Check response.ok, validate the returned content, and reset loading in finally.
🛡️ Suggested error handling
- const data = await response.json()
- const text = data.choices[0].message.content
-
- setSummary(text)
- setBullets(text.split('\n'))
- setIsLoading(false)
+ try {
+ if (!response.ok) {
+ throw new Error(`Summary request failed: ${response.status}`)
+ }
+
+ const data = await response.json()
+ const text = data?.choices?.[0]?.message?.content ?? ''
+
+ setSummary(text)
+ setBullets(text.split('\n').filter(Boolean))
+ } catch {
+ setSummary('')
+ setBullets([])
+ } finally {
+ setIsLoading(false)
+ }📝 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.
| // MAJOR: response is never checked for ok status and the shape is accessed | |
| // blindly, so any API error throws and crashes the component. | |
| const data = await response.json() | |
| const text = data.choices[0].message.content | |
| setSummary(text) | |
| setBullets(text.split('\n')) | |
| setIsLoading(false) | |
| try { | |
| if (!response.ok) { | |
| throw new Error(`Summary request failed: ${response.status}`) | |
| } | |
| const data = await response.json() | |
| const text = data?.choices?.[0]?.message?.content ?? '' | |
| setSummary(text) | |
| setBullets(text.split('\n').filter(Boolean)) | |
| } catch { | |
| setSummary('') | |
| setBullets([]) | |
| } finally { | |
| setIsLoading(false) | |
| } |
🧰 Tools
🪛 ast-grep (0.44.0)
[warning] 45-45: Avoid using the initial state variable in setState
Context: setSummary(text)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteAiSummary.jsx` around lines 41 - 48, The NoteAiSummary
response handling currently reads data.choices[0].message.content without
checking response.ok or validating the JSON shape, which can crash on API
errors. Update the async flow in NoteAiSummary to guard the fetch response,
verify the expected choices/message/content structure before using it, and move
setIsLoading(false) into a finally block so loading is reset on both success and
failure.
| <div className="rounded-2xl border border-slate-300 bg-white p-4 shadow-sm"> | ||
| <h3 className="font-display text-lg">AI Summary</h3> | ||
| {/* NITPICK: user-facing typo ("you're" should be "your"). */} | ||
| <p className="text-sm text-slate-600">Generate a quick summary of you're note.</p> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the user-facing typo.
“you're” should be “your”.
✏️ Suggested fix
- <p className="text-sm text-slate-600">Generate a quick summary of you're note.</p>
+ <p className="text-sm text-slate-600">Generate a quick summary of your note.</p>📝 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.
| <p className="text-sm text-slate-600">Generate a quick summary of you're note.</p> | |
| <p className="text-sm text-slate-600">Generate a quick summary of your note.</p> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteAiSummary.jsx` at line 61, Fix the user-facing typo in the
NoteAiSummary component copy so the helper text reads correctly. Update the text
inside NoteAiSummary.jsx where the summary prompt is rendered, replacing the
incorrect “you’re” wording with “your” in the displayed sentence.
What
Adds a new
NoteAiSummarycomponent that generates a quick bullet-point summary of the currently active note by calling a chat-completion API. The panel renders below the editor and preview.Why
Long notes are hard to skim. A one-click summary lets users get the gist without re-reading the whole thing.
Changes
src/components/NoteAiSummary.jsxApp.jsxTesting
npm run buildpassesSummary by CodeRabbit