import { useEffect } from 'react' import { useEditorStore } from '../../store/editorStore' import { detectPassiveVoice } from '../../utils/passiveVoice' import { parseAnnotationsFromAIResponse } from '../../utils/annotationParser' import './Toolbar.css' function countWords(text: string): number { return text.trim() === '' ? 0 : text.trim().split(/\s+/).length } function formatWordCount(n: number): string { if (n >= 1000) return `${(n / 1000).toFixed(1)}k` return String(n) } export function AnalysisToolbar(): JSX.Element { const { activeFilePath, activeFileContent, isDirty, analysisMode, annotations, isAILoading, setAnnotations, clearAnnotations, setAnalysisMode, addUserMessage, startAssistantMessage, appendToLastAssistantMessage, setAILoading, setAIError, chatHistory, projectWordCount, setProjectWordCount, fontSize, setFontSize } = useEditorStore() useEffect(() => { window.api.getProjectWordCount().then(setProjectWordCount).catch(() => {}) }, []) // Refresh project count after a save (isDirty transitions from true → false) useEffect(() => { if (!isDirty) { window.api.getProjectWordCount().then(setProjectWordCount).catch(() => {}) } }, [isDirty]) const hasFile = Boolean(activeFilePath) const runPassiveVoice = (): void => { if (!hasFile) return const found = detectPassiveVoice(activeFileContent) setAnnotations(found) setAnalysisMode('passive_voice') } const runAIAnalysis = async (mode: 'consistency' | 'style' | 'critique'): Promise => { if (!activeFilePath || isAILoading) return setAnalysisMode(mode) setAIError(null) const prompt = mode === 'consistency' ? '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.' addUserMessage(prompt) startAssistantMessage() setAILoading(true) try { await window.api.streamAIMessage( { mode, documentContent: activeFileContent, documentPath: activeFilePath, conversationHistory: chatHistory .slice(-10) .map((m) => ({ role: m.role, content: m.content })), userMessage: prompt }, (chunk: string) => { appendToLastAssistantMessage(chunk) } ) // Parse annotations from response 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) if (newAnnotations.length > 0) setAnnotations(newAnnotations) } } catch (err) { setAIError(err instanceof Error ? err.message : 'Analysis failed') } finally { setAILoading(false) } } const passiveCount = annotations.filter((a) => a.type === 'passive_voice').length const otherCount = annotations.filter((a) => a.type !== 'passive_voice').length const docWordCount = countWords(activeFileContent) return (
{annotations.length > 0 && ( )}
{activeFilePath && ( {formatWordCount(docWordCount)} / {formatWordCount(projectWordCount)} )} {isDirty && ( )} {activeFilePath && ( {activeFilePath.split('/').pop()?.replace(/\.md$/, '')} )}
) }