✨ custom feedback highlights
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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 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) => {
|
(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 (
|
||||||
|
|||||||
@@ -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) }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -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 }),
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user