From c735eed1f3439d725ae46c78678c96d5aa282db2 Mon Sep 17 00:00:00 2001 From: Alex Hernandez Date: Mon, 2 Mar 2026 09:18:12 +1000 Subject: [PATCH] :sparkles: custom feedback highlights --- src/renderer/components/AIChat/ChatPanel.tsx | 9 +++--- .../components/Editor/MarkdownEditor.tsx | 31 +++++++++++++++++-- .../components/Feedback/FeedbackPanel.tsx | 7 +++-- src/renderer/store/editorStore.ts | 7 +++++ src/renderer/types/editor.ts | 1 + 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/renderer/components/AIChat/ChatPanel.tsx b/src/renderer/components/AIChat/ChatPanel.tsx index 47403c5..b561717 100644 --- a/src/renderer/components/AIChat/ChatPanel.tsx +++ b/src/renderer/components/AIChat/ChatPanel.tsx @@ -7,8 +7,6 @@ import { parseAnnotationsFromAIResponse } from '../../utils/annotationParser' import type { Attachment, TextAnnotation } from '../../types/editor' import './Chat.css' -type TabId = 'chat' | 'feedback' - function formatSessionTime(createdAt: number): string { const date = new Date(createdAt) const now = new Date() @@ -41,10 +39,13 @@ export function ChatPanel(): JSX.Element { setAnnotations, linkAnnotationsToMessage, newChat, - setActiveSession + setActiveSession, + rightPanelTab, + setRightPanelTab, } = useEditorStore() - const [tab, setTab] = useState('chat') + const tab = rightPanelTab + const setTab = setRightPanelTab const [showHistory, setShowHistory] = useState(false) const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0) const scrollRef = useRef(null) diff --git a/src/renderer/components/Editor/MarkdownEditor.tsx b/src/renderer/components/Editor/MarkdownEditor.tsx index ef02f47..9c6cbaf 100644 --- a/src/renderer/components/Editor/MarkdownEditor.tsx +++ b/src/renderer/components/Editor/MarkdownEditor.tsx @@ -116,7 +116,9 @@ export function analyseAnnotation( documentContent: activeFileContent, documentPath: activeFilePath ?? '', conversationHistory: [], - userMessage: `This passage was flagged for ${typeName}: "${ann.matchedText}"\n\nIn 1–2 sentences explain the specific issue, then provide a direct rewrite in a markdown blockquote like this:\n\n> Rewritten passage here.\n\nBe specific to this exact text—no generic advice.` + userMessage: ann.autoAnalyse + ? `Please provide editorial feedback on this selected passage: "${ann.matchedText}"\n\nIn 1–2 sentences, identify the most important issue or opportunity for improvement, then provide a suggested rewrite in a markdown blockquote:\n\n> Rewritten version here.\n\nBe specific to this exact text.` + : `This passage was flagged for ${typeName}: "${ann.matchedText}"\n\nIn 1–2 sentences explain the specific issue, then provide a direct rewrite in a markdown blockquote like this:\n\n> Rewritten passage here.\n\nBe specific to this exact text—no generic advice.` }, (chunk: string) => { if (cancelled) return @@ -607,7 +609,32 @@ export function MarkdownEditor(): JSX.Element { selectAll(view) view.focus() } - } + }, + ...(hasSelection ? [ + 'separator' as const, + { + label: 'Generate feedback', + action: () => { + const view = viewRef.current + if (!view) return + const { from, to } = view.state.selection.main + const text = view.state.sliceDoc(from, to) + if (!text.trim()) return + const annotation: TextAnnotation = { + id: `custom-${Date.now()}`, + type: 'custom', + from, + to, + matchedText: text, + message: 'Generating feedback...', + autoAnalyse: true, + } + const store = useEditorStore.getState() + store.setAnnotations([...store.annotations, annotation]) + store.setRightPanelTab('feedback') + } + } + ] : []) ] return ( diff --git a/src/renderer/components/Feedback/FeedbackPanel.tsx b/src/renderer/components/Feedback/FeedbackPanel.tsx index 4664d87..6d4fafa 100644 --- a/src/renderer/components/Feedback/FeedbackPanel.tsx +++ b/src/renderer/components/Feedback/FeedbackPanel.tsx @@ -44,7 +44,8 @@ function FeedbackCard({ ann, autoAnalyse, onDismiss }: FeedbackCardProps): JSX.E 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. - if (ann.type === 'custom') { + // Context-menu annotations (autoAnalyse: true) need AI analysis, so start idle. + if (ann.type === 'custom' && !ann.autoAnalyse) { return { status: 'done', text: ann.message, suggestion: ann.suggestion ?? null } } return { status: 'idle' } @@ -93,7 +94,7 @@ function FeedbackCard({ ann, autoAnalyse, onDismiss }: FeedbackCardProps): JSX.E
{typeName} - {ann.type === 'custom' && ( + {ann.type === 'custom' && !ann.autoAnalyse && ( from attachment )}
@@ -251,7 +252,7 @@ export function FeedbackPanel(): JSX.Element { { cancelPendingDismiss(ann.id); removeAnnotation(ann.id); tooltipAnalysisCache.delete(ann.id) }} /> ))} diff --git a/src/renderer/store/editorStore.ts b/src/renderer/store/editorStore.ts index a2b8d18..3f1e63e 100644 --- a/src/renderer/store/editorStore.ts +++ b/src/renderer/store/editorStore.ts @@ -76,6 +76,10 @@ interface EditorState { revisions: RevisionMeta[] setRevisions: (revisions: RevisionMeta[]) => void + // Right panel tab (chat or feedback) — shared so MarkdownEditor can switch it + rightPanelTab: 'chat' | 'feedback' + setRightPanelTab: (tab: 'chat' | 'feedback') => void + // Session persistence loadSession: () => Promise } @@ -483,6 +487,9 @@ export const useEditorStore = create((set, get) => ({ revisionPanelOpen: false, toggleRevisionPanel: () => set((s) => ({ revisionPanelOpen: !s.revisionPanelOpen })), + + rightPanelTab: 'chat', + setRightPanelTab: (tab) => set({ rightPanelTab: tab }), revisions: [], setRevisions: (revisions) => set({ revisions }), diff --git a/src/renderer/types/editor.ts b/src/renderer/types/editor.ts index be8d689..a4eefaa 100644 --- a/src/renderer/types/editor.ts +++ b/src/renderer/types/editor.ts @@ -40,6 +40,7 @@ export interface TextAnnotation { suggestion?: string applied?: boolean // true when the suggestion has been applied to the document dismissed?: boolean // true when the user dismissed this annotation (archived) + autoAnalyse?: boolean // true when created via context menu — FeedbackCard starts AI analysis immediately } export type AnalysisMode = 'none' | 'passive_voice' | 'consistency' | 'style' | 'critique'