custom feedback highlights

This commit is contained in:
2026-03-02 09:18:12 +10:00
parent 338eaf5c5f
commit c735eed1f3
5 changed files with 46 additions and 9 deletions

View File

@@ -7,8 +7,6 @@ import { parseAnnotationsFromAIResponse } from '../../utils/annotationParser'
import type { Attachment, TextAnnotation } from '../../types/editor' import type { Attachment, TextAnnotation } from '../../types/editor'
import './Chat.css' import './Chat.css'
type TabId = 'chat' | 'feedback'
function formatSessionTime(createdAt: number): string { function formatSessionTime(createdAt: number): string {
const date = new Date(createdAt) const date = new Date(createdAt)
const now = new Date() const now = new Date()
@@ -41,10 +39,13 @@ export function ChatPanel(): JSX.Element {
setAnnotations, setAnnotations,
linkAnnotationsToMessage, linkAnnotationsToMessage,
newChat, newChat,
setActiveSession setActiveSession,
rightPanelTab,
setRightPanelTab,
} = useEditorStore() } = useEditorStore()
const [tab, setTab] = useState<TabId>('chat') const tab = rightPanelTab
const setTab = setRightPanelTab
const [showHistory, setShowHistory] = useState(false) const [showHistory, setShowHistory] = useState(false)
const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0) const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0)
const scrollRef = useRef<HTMLDivElement>(null) const scrollRef = useRef<HTMLDivElement>(null)

View File

@@ -116,7 +116,9 @@ export function analyseAnnotation(
documentContent: activeFileContent, documentContent: activeFileContent,
documentPath: activeFilePath ?? '', documentPath: activeFilePath ?? '',
conversationHistory: [], conversationHistory: [],
userMessage: `This passage was flagged for ${typeName}: "${ann.matchedText}"\n\nIn 12 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 12 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 12 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) => { (chunk: string) => {
if (cancelled) return if (cancelled) return
@@ -607,7 +609,32 @@ export function MarkdownEditor(): JSX.Element {
selectAll(view) selectAll(view)
view.focus() 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 ( return (

View File

@@ -44,7 +44,8 @@ function FeedbackCard({ ann, autoAnalyse, onDismiss }: FeedbackCardProps): JSX.E
if (cached) return { status: 'done', text: cached.text, suggestion: cached.suggestion } if (cached) return { status: 'done', text: cached.text, suggestion: cached.suggestion }
// Custom (attachment-driven) annotations already carry their analysis — show // Custom (attachment-driven) annotations already carry their analysis — show
// the problem description and suggestion immediately without an extra AI call. // 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: 'done', text: ann.message, suggestion: ann.suggestion ?? null }
} }
return { status: 'idle' } return { status: 'idle' }
@@ -93,7 +94,7 @@ function FeedbackCard({ ann, autoAnalyse, onDismiss }: FeedbackCardProps): JSX.E
<div className="fb-card-header-top"> <div className="fb-card-header-top">
<div className="fb-card-header-badges"> <div className="fb-card-header-badges">
<span className="fb-card-badge">{typeName}</span> <span className="fb-card-badge">{typeName}</span>
{ann.type === 'custom' && ( {ann.type === 'custom' && !ann.autoAnalyse && (
<span className="fb-card-source-tag">from attachment</span> <span className="fb-card-source-tag">from attachment</span>
)} )}
</div> </div>
@@ -251,7 +252,7 @@ export function FeedbackPanel(): JSX.Element {
<FeedbackCard <FeedbackCard
key={ann.id} key={ann.id}
ann={ann} ann={ann}
autoAnalyse={analyseAll} autoAnalyse={analyseAll || ann.autoAnalyse === true}
onDismiss={() => { cancelPendingDismiss(ann.id); removeAnnotation(ann.id); tooltipAnalysisCache.delete(ann.id) }} onDismiss={() => { cancelPendingDismiss(ann.id); removeAnnotation(ann.id); tooltipAnalysisCache.delete(ann.id) }}
/> />
))} ))}

View File

@@ -76,6 +76,10 @@ interface EditorState {
revisions: RevisionMeta[] revisions: RevisionMeta[]
setRevisions: (revisions: RevisionMeta[]) => void 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 // Session persistence
loadSession: () => Promise<void> loadSession: () => Promise<void>
} }
@@ -483,6 +487,9 @@ export const useEditorStore = create<EditorState>((set, get) => ({
revisionPanelOpen: false, revisionPanelOpen: false,
toggleRevisionPanel: () => set((s) => ({ revisionPanelOpen: !s.revisionPanelOpen })), toggleRevisionPanel: () => set((s) => ({ revisionPanelOpen: !s.revisionPanelOpen })),
rightPanelTab: 'chat',
setRightPanelTab: (tab) => set({ rightPanelTab: tab }),
revisions: [], revisions: [],
setRevisions: (revisions) => set({ revisions }), setRevisions: (revisions) => set({ revisions }),

View File

@@ -40,6 +40,7 @@ export interface TextAnnotation {
suggestion?: string suggestion?: string
applied?: boolean // true when the suggestion has been applied to the document applied?: boolean // true when the suggestion has been applied to the document
dismissed?: boolean // true when the user dismissed this annotation (archived) 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' export type AnalysisMode = 'none' | 'passive_voice' | 'consistency' | 'style' | 'critique'