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}
+
+ ))}
+
+ )}
+
+ )
+}