Add "Prepare for Claude" feature for study guide generation
Adds a buildClaudePrompt() utility that formats the completed study project (scripture, notes, Greek word research) into a structured prompt ready to paste into Claude. A "Prepare for Claude" button in the header copies it to the clipboard and shows a brief confirmation. Button is disabled until at least one chunk exists. https://claude.ai/code/session_01C4T3bYHR2svLq7JVcxKJ6Y
This commit is contained in:
+80
@@ -198,6 +198,69 @@ export function renderVerseContent(content) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildClaudePrompt(project) {
|
||||||
|
const header = `I've prepared a Bible study on ${project.book} ${project.chapter} (${project.translation}) and need your help turning my notes into a polished study guide.
|
||||||
|
|
||||||
|
Below is my work organised by passage chunk, including my study notes and Greek word research. Please create a clear, structured study guide that:
|
||||||
|
- Synthesises my notes into coherent teaching points
|
||||||
|
- Naturally integrates the Greek word insights
|
||||||
|
- Includes 2–3 reflection questions per chunk
|
||||||
|
- Preserves the passage-by-passage structure
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
PROJECT: ${project.title}
|
||||||
|
TRANSLATION: ${project.translation}
|
||||||
|
PASSAGE: ${project.book} ${project.chapter}
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const chunks = project.chunks.map((chunk, index) => {
|
||||||
|
const ref = chunk.startVerse === chunk.endVerse
|
||||||
|
? `${project.book} ${project.chapter}:${chunk.startVerse}`
|
||||||
|
: `${project.book} ${project.chapter}:${chunk.startVerse}–${chunk.endVerse}`;
|
||||||
|
|
||||||
|
const verses = project.verses
|
||||||
|
.filter((v) => v.number >= chunk.startVerse && v.number <= chunk.endVerse)
|
||||||
|
.map((v) => `${v.number} ${v.text}`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const notes = chunk.notes.trim() || 'No notes.';
|
||||||
|
|
||||||
|
const greekWords = chunk.greekWords.length === 0
|
||||||
|
? 'None.'
|
||||||
|
: chunk.greekWords.map((word) => {
|
||||||
|
const summary = [
|
||||||
|
word.strongNumber,
|
||||||
|
word.lexeme,
|
||||||
|
word.transliteration && `(${word.transliteration})`,
|
||||||
|
word.partOfSpeech,
|
||||||
|
word.shortDefinition,
|
||||||
|
].filter(Boolean).join(' | ');
|
||||||
|
const definition = word.definitionHtml
|
||||||
|
? `\n ${htmlToPlainText(word.definitionHtml)}`
|
||||||
|
: '';
|
||||||
|
return `• ${summary}${definition}`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return `===
|
||||||
|
|
||||||
|
CHUNK ${index + 1} — ${ref}
|
||||||
|
|
||||||
|
Scripture:
|
||||||
|
${verses}
|
||||||
|
|
||||||
|
My Notes:
|
||||||
|
${notes}
|
||||||
|
|
||||||
|
Greek Words:
|
||||||
|
${greekWords}
|
||||||
|
`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return header + chunks;
|
||||||
|
}
|
||||||
|
|
||||||
export function parseBibleChapter(data) {
|
export function parseBibleChapter(data) {
|
||||||
if (data?.verses && Array.isArray(data.verses)) {
|
if (data?.verses && Array.isArray(data.verses)) {
|
||||||
return data.verses.map((verse) => ({
|
return data.verses.map((verse) => ({
|
||||||
@@ -671,6 +734,15 @@ const App = () => {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyForClaude = () => {
|
||||||
|
if (!project) return;
|
||||||
|
const prompt = buildClaudePrompt(project);
|
||||||
|
navigator.clipboard.writeText(prompt).then(() => {
|
||||||
|
setSaveStatus('Copied for Claude!');
|
||||||
|
window.setTimeout(() => setSaveStatus(''), 2000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const verseLabel = (start, end) => (start === end ? `${start}` : `${start}-${end}`);
|
const verseLabel = (start, end) => (start === end ? `${start}` : `${start}-${end}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -710,6 +782,14 @@ const App = () => {
|
|||||||
)}
|
)}
|
||||||
{project && (
|
{project && (
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={copyForClaude}
|
||||||
|
disabled={project.chunks.length === 0}
|
||||||
|
className="rounded-md bg-violet-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-violet-400 disabled:cursor-not-allowed disabled:bg-slate-500"
|
||||||
|
>
|
||||||
|
Prepare for Claude
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={exportChapter}
|
onClick={exportChapter}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
parseBibleChapter,
|
parseBibleChapter,
|
||||||
wordTableHtml,
|
wordTableHtml,
|
||||||
buildExportHtml,
|
buildExportHtml,
|
||||||
|
buildClaudePrompt,
|
||||||
createParagraphsFromText,
|
createParagraphsFromText,
|
||||||
} from './App.jsx';
|
} from './App.jsx';
|
||||||
|
|
||||||
@@ -388,3 +389,87 @@ describe('createParagraphsFromText', () => {
|
|||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// buildClaudePrompt
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
describe('buildClaudePrompt', () => {
|
||||||
|
test('includes project title, translation, and passage', () => {
|
||||||
|
const prompt = buildClaudePrompt(baseProject);
|
||||||
|
expect(prompt).toContain('Titus 1 Study');
|
||||||
|
expect(prompt).toContain('BSB');
|
||||||
|
expect(prompt).toContain('Titus 1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('includes scripture verse text for the chunk range', () => {
|
||||||
|
const prompt = buildClaudePrompt(baseProject);
|
||||||
|
expect(prompt).toContain('Paul, a servant of God.');
|
||||||
|
expect(prompt).toContain('In hope of eternal life.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('includes study notes', () => {
|
||||||
|
expect(buildClaudePrompt(baseProject)).toContain('Key observations.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('includes Greek word data', () => {
|
||||||
|
const prompt = buildClaudePrompt(baseProject);
|
||||||
|
expect(prompt).toContain('G1401');
|
||||||
|
expect(prompt).toContain('δοῦλος');
|
||||||
|
expect(prompt).toContain('doulos');
|
||||||
|
expect(prompt).toContain('a slave');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('numbers each chunk sequentially', () => {
|
||||||
|
const multiChunkProject = {
|
||||||
|
...baseProject,
|
||||||
|
chunks: [
|
||||||
|
{ ...baseProject.chunks[0], id: 'c1', startVerse: 1, endVerse: 1 },
|
||||||
|
{ ...baseProject.chunks[0], id: 'c2', startVerse: 2, endVerse: 2, notes: 'Second chunk notes.', greekWords: [] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const prompt = buildClaudePrompt(multiChunkProject);
|
||||||
|
expect(prompt).toContain('CHUNK 1');
|
||||||
|
expect(prompt).toContain('CHUNK 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats a single-verse reference without a dash', () => {
|
||||||
|
const project = {
|
||||||
|
...baseProject,
|
||||||
|
chunks: [{ ...baseProject.chunks[0], startVerse: 1, endVerse: 1 }],
|
||||||
|
};
|
||||||
|
const prompt = buildClaudePrompt(project);
|
||||||
|
expect(prompt).toContain('Titus 1:1');
|
||||||
|
expect(prompt).not.toContain('Titus 1:1–1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats a multi-verse reference with an en-dash', () => {
|
||||||
|
const prompt = buildClaudePrompt(baseProject);
|
||||||
|
expect(prompt).toContain('Titus 1:1–2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows "No notes." when chunk notes are empty', () => {
|
||||||
|
const project = { ...baseProject, chunks: [{ ...baseProject.chunks[0], notes: '' }] };
|
||||||
|
expect(buildClaudePrompt(project)).toContain('No notes.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows "None." when chunk has no Greek words', () => {
|
||||||
|
const project = { ...baseProject, chunks: [{ ...baseProject.chunks[0], greekWords: [] }] };
|
||||||
|
expect(buildClaudePrompt(project)).toContain('None.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('only includes verses within the chunk range', () => {
|
||||||
|
const project = {
|
||||||
|
...baseProject,
|
||||||
|
verses: [
|
||||||
|
{ number: 1, text: 'Verse one.' },
|
||||||
|
{ number: 2, text: 'Verse two.' },
|
||||||
|
{ number: 3, text: 'Verse three.' },
|
||||||
|
],
|
||||||
|
chunks: [{ ...baseProject.chunks[0], startVerse: 2, endVerse: 2, greekWords: [] }],
|
||||||
|
};
|
||||||
|
const prompt = buildClaudePrompt(project);
|
||||||
|
expect(prompt).toContain('Verse two.');
|
||||||
|
expect(prompt).not.toContain('Verse one.');
|
||||||
|
expect(prompt).not.toContain('Verse three.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user