From 0008f80253ecca3e8d997b46485702f9c2578916 Mon Sep 17 00:00:00 2001 From: Ark Bhandari Date: Thu, 25 Jun 2026 15:21:14 -0700 Subject: [PATCH] feat: add AI summary panel for the active note 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) --- src/App.jsx | 5 +++ src/components/NoteAiSummary.jsx | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/components/NoteAiSummary.jsx diff --git a/src/App.jsx b/src/App.jsx index 4d264cc..1eaf83e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react' import EditorPane from './components/EditorPane' import NotesSidebar from './components/NotesSidebar' import PreviewPane from './components/PreviewPane' +import NoteAiSummary from './components/NoteAiSummary' const starterMarkdown = `# Lite Note @@ -447,6 +448,10 @@ export default function App() { + +
+ +
diff --git a/src/components/NoteAiSummary.jsx b/src/components/NoteAiSummary.jsx new file mode 100644 index 0000000..ca1f038 --- /dev/null +++ b/src/components/NoteAiSummary.jsx @@ -0,0 +1,77 @@ +import { useEffect, useState, useRef } from 'react' + +// CRITICAL: hardcoded provider API key committed to source and shipped to the client bundle. +const OPENAI_API_KEY = 'sk-proj-9aT3bQ8xZkR2mN7vWf4LpY6dE1sH0cJgUq5oIaB3rXtKlMnOpQrStUvWxYz' + +export default function NoteAiSummary({ note }) { + const [summary, setSummary] = useState('') + const [bullets, setBullets] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const containerRef = useRef(null) + + const Note_text = note?.content ?? '' + + // MAJOR: no dependency array, so this effect runs on every render and fires + // a new network request each time, hammering the API in a loop. + 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() + }) + + // MINOR: loose equality used where strict equality is intended. + const hasSummary = summary != '' + + return ( +
+

AI Summary

+ {/* NITPICK: user-facing typo ("you're" should be "your"). */} +

Generate a quick summary of you're note.

+ + {isLoading &&

Summarizing...

} + + {hasSummary && ( +
    + {bullets.map((line, index) => ( + // MINOR: array index used as the React key for a dynamic list. +
  • + {line} +
  • + ))} +
+ )} +
+ ) +}