diff --git a/src/main/aiService.ts b/src/main/aiService.ts index 34c742e..8c84b7c 100644 --- a/src/main/aiService.ts +++ b/src/main/aiService.ts @@ -58,16 +58,29 @@ SUGGESTION: [how to fix it]`, style: `Analyze the writing style and provide specific improvement suggestions: - Pacing: identify slow passages or rushed moments - Sentence variety: flag runs of similar length or structure -- Show don't tell: identify passages that tell emotion/state rather than showing it - Gothic atmosphere: passages where the atmospheric tone is inconsistent - Dialogue: any dialogue that feels stilted or unnatural For each suggestion: -ISSUE: [type: Pacing / Sentence Variety / Show-Don't-Tell / Atmosphere / Dialogue] +ISSUE: [type: Pacing / Sentence Variety / Atmosphere / Dialogue] PASSAGE: "[exact quoted text]" PROBLEM: [specific explanation] SUGGESTION: [concrete rewrite or approach]`, + show_tell: `Identify every passage in this chapter where the author tells the reader something that should instead be shown through action, dialogue, sensation, or concrete detail. + +Focus on: +- Emotions or inner states stated directly ("she felt afraid", "he was relieved") instead of expressed through physical reaction or behaviour +- Character motivations or intentions explained by the narrator rather than revealed through what characters do or say +- Abstract or evaluative description ("the room was oppressive", "it was a beautiful day") instead of specific sensory detail that lets the reader feel it +- Dialogue tags or beats that tell the reader how something was said rather than letting word choice and action carry it + +For each instance: +ISSUE: Show Don't Tell +PASSAGE: "[exact quoted text from the chapter]" +PROBLEM: [one sentence explaining what is being told that should be shown] +SUGGESTION: "[a concrete rewrite that shows the same moment through action, sensation, or dialogue]"`, + critique: `Give an honest, detailed critique of this chapter as a whole. Structure your response as follows: **Overall impression** (2–3 sentences on what the chapter achieves and its most significant weakness) diff --git a/src/renderer/components/Editor/MarkdownEditor.tsx b/src/renderer/components/Editor/MarkdownEditor.tsx index 8a7c974..5a0d2bf 100644 --- a/src/renderer/components/Editor/MarkdownEditor.tsx +++ b/src/renderer/components/Editor/MarkdownEditor.tsx @@ -98,8 +98,10 @@ export function analyseAnnotation( ann: TextAnnotation, onUpdate: (text: string, streaming: boolean, suggestion: string | null) => void ): () => void { - const cached = tooltipAnalysisCache.get(ann.id) + const cached = tooltipAnalysisCache.get(ann.id) ?? ann.analysisCache ?? null if (cached) { + // Warm the in-memory cache so subsequent calls this session are instant + if (!tooltipAnalysisCache.has(ann.id)) tooltipAnalysisCache.set(ann.id, cached) onUpdate(cached.text, false, cached.suggestion) return () => {} } @@ -131,6 +133,7 @@ export function analyseAnnotation( const text = accumulated || ann.message const suggestion = extractBlockquote(text) ?? ann.suggestion ?? null tooltipAnalysisCache.set(ann.id, { text, suggestion }) + useEditorStore.getState().setAnnotationAnalysis(ann.id, { text, suggestion }) onUpdate(text, false, suggestion) }).catch(() => { if (!cancelled) onUpdate(ann.message, false, ann.suggestion ?? null) @@ -363,6 +366,11 @@ function buildTheme(fontSize: number, dark: boolean): ReturnType(() => { - const cached = tooltipAnalysisCache.get(ann.id) + const cached = tooltipAnalysisCache.get(ann.id) ?? ann.analysisCache ?? null if (cached) return { status: 'done', text: cached.text, suggestion: cached.suggestion } // Custom (attachment-driven) annotations already carry their analysis — show // the problem description and suggestion immediately without an extra AI call. diff --git a/src/renderer/components/Toolbar/AnalysisToolbar.tsx b/src/renderer/components/Toolbar/AnalysisToolbar.tsx index e3abb2c..1e8a798 100644 --- a/src/renderer/components/Toolbar/AnalysisToolbar.tsx +++ b/src/renderer/components/Toolbar/AnalysisToolbar.tsx @@ -59,7 +59,7 @@ export function AnalysisToolbar(): JSX.Element { setAnalysisMode('passive_voice') } - const runAIAnalysis = async (mode: 'consistency' | 'style' | 'critique'): Promise => { + const runAIAnalysis = async (mode: 'consistency' | 'style' | 'show_tell' | 'critique'): Promise => { if (!activeFilePath || isAILoading) return setAnalysisMode(mode) @@ -70,7 +70,9 @@ export function AnalysisToolbar(): JSX.Element { ? 'Please check this chapter for consistency issues (character names, timeline, repeated phrases).' : mode === 'style' ? 'Please analyze the style and pacing of this chapter and suggest improvements.' - : 'Please give me an honest critique of this chapter.' + : mode === 'show_tell' + ? 'Please identify every passage in this chapter where I am telling rather than showing.' + : 'Please give me an honest critique of this chapter.' addUserMessage(prompt) startAssistantMessage() @@ -96,7 +98,10 @@ export function AnalysisToolbar(): JSX.Element { const currentHistory = useEditorStore.getState().chatHistory const lastMsg = currentHistory[currentHistory.length - 1] if (lastMsg?.role === 'assistant' && lastMsg.content.length > 0) { - const newAnnotations = parseAnnotationsFromAIResponse(lastMsg.content, activeFileContent) + // For show_tell mode, force all annotations to the show_tell type so the + // classifier doesn't accidentally mis-label them as 'style' or 'consistency'. + const overrideType = mode === 'show_tell' ? 'show_tell' : undefined + const newAnnotations = parseAnnotationsFromAIResponse(lastMsg.content, activeFileContent, overrideType) if (newAnnotations.length > 0) { const existing = useEditorStore.getState().annotations.filter((a) => a.type !== mode) setAnnotations([...existing, ...newAnnotations]) @@ -113,6 +118,7 @@ export function AnalysisToolbar(): JSX.Element { const passiveCount = annotations.filter((a) => a.type === 'passive_voice').length const consistencyCount = annotations.filter((a) => a.type === 'consistency').length const styleCount = annotations.filter((a) => a.type === 'style').length + const showTellCount = annotations.filter((a) => a.type === 'show_tell').length const critiqueCount = annotations.filter((a) => a.type === 'critique').length const docWordCount = countWords(activeFileContent) @@ -155,6 +161,18 @@ export function AnalysisToolbar(): JSX.Element { )} + +