Skip to content

feat: add AI summary panel for the active note - #23

Open
ark-commits wants to merge 1 commit into
control/no-noise-instructionfrom
test/cr-bugs-no-noise
Open

feat: add AI summary panel for the active note#23
ark-commits wants to merge 1 commit into
control/no-noise-instructionfrom
test/cr-bugs-no-noise

Conversation

@ark-commits

@ark-commits ark-commits commented Jun 25, 2026

Copy link
Copy Markdown
Owner

What

Adds a new NoteAiSummary component 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

  • New component src/components/NoteAiSummary.jsx
  • Rendered under the editor/preview grid in App.jsx

Testing

  • npm run build passes
  • Verified the panel renders for the active note

Summary by CodeRabbit

  • New Features
    • Added an AI Summary section to notes in the editor/preview area.
    • The summary updates for the currently selected note and shows a loading indicator while generating.
    • Summaries are displayed as a readable list of bullet points when available.

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>
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e72b743f-5f35-4dc2-9909-7428ed1d0e45

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/cr-bugs-no-noise

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 835cd89 and 11baa59.

📒 Files selected for processing (2)
  • src/App.jsx
  • src/components/NoteAiSummary.jsx

Comment thread src/App.jsx
</section>

<div className="mt-4">
<NoteAiSummary note={activeNote} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +3 to +4
// CRITICAL: hardcoded provider API key committed to source and shipped to the client bundle.
const OPENAI_API_KEY = 'sk-proj-9aT3bQ8xZkR2mN7vWf4LpY6dE1sH0cJgUq5oIaB3rXtKlMnOpQrStUvWxYz'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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

Comment on lines +16 to +52
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()
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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 -S

Repository: 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 -S

Repository: 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.

Suggested change
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.

Comment on lines +17 to +18
if (!Note_text) {
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Suggested change
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.

Comment on lines +41 to +48
// 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Suggested change
// 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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
<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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant