:lightning: smarter checking for custom feedback

This commit is contained in:
2026-03-02 18:57:24 +10:00
parent 918b32c80d
commit 84377c82af
4 changed files with 29 additions and 5 deletions

View File

@@ -381,6 +381,15 @@
padding: 10px 12px; padding: 10px 12px;
} }
.chat-info-notice {
background: rgba(180, 140, 60, 0.12);
border: 1px solid rgba(180, 140, 60, 0.35);
border-radius: 6px;
color: #c9a94a;
font-size: 12px;
padding: 10px 12px;
}
.chat-input-area { .chat-input-area {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -48,6 +48,7 @@ export function ChatPanel(): JSX.Element {
const setTab = setRightPanelTab const setTab = setRightPanelTab
const [showHistory, setShowHistory] = useState(false) const [showHistory, setShowHistory] = useState(false)
const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0) const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0)
const [feedbackNotice, setFeedbackNotice] = useState<string | null>(null)
const scrollRef = useRef<HTMLDivElement>(null) const scrollRef = useRef<HTMLDivElement>(null)
const prevAnnotationCountRef = useRef(annotations.length) const prevAnnotationCountRef = useRef(annotations.length)
@@ -68,6 +69,7 @@ export function ChatPanel(): JSX.Element {
if (!activeFilePath || isAILoading) return if (!activeFilePath || isAILoading) return
setAIError(null) setAIError(null)
setFeedbackNotice(null)
addUserMessage(text, attachments.map(({ name, mimeType }) => ({ name, mimeType }))) addUserMessage(text, attachments.map(({ name, mimeType }) => ({ name, mimeType })))
startAssistantMessage() startAssistantMessage()
setAILoading(true) setAILoading(true)
@@ -97,11 +99,17 @@ export function ChatPanel(): JSX.Element {
const lastMsg = currentHistory[currentHistory.length - 1] const lastMsg = currentHistory[currentHistory.length - 1]
if (lastMsg?.role === 'assistant' && lastMsg.content.length > 0) { if (lastMsg?.role === 'assistant' && lastMsg.content.length > 0) {
const overrideType = attachments.length > 0 ? 'custom' as const : undefined const overrideType = attachments.length > 0 ? 'custom' as const : undefined
const parsed = parseAnnotationsFromAIResponse(lastMsg.content, activeFileContent, overrideType) const latestContent = useEditorStore.getState().activeFileContent
const { annotations: parsed, droppedCount } = parseAnnotationsFromAIResponse(lastMsg.content, latestContent, overrideType)
if (parsed.length > 0) { if (parsed.length > 0) {
setAnnotations(parsed) setAnnotations(parsed)
linkAnnotationsToMessage(lastMsg.id, parsed.map(a => a.id)) linkAnnotationsToMessage(lastMsg.id, parsed.map(a => a.id))
} }
if (droppedCount > 0) {
setFeedbackNotice(
`${droppedCount} feedback item${droppedCount === 1 ? '' : 's'} couldn't be applied — the referenced text has been edited.`
)
}
} }
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : 'An error occurred' const message = err instanceof Error ? err.message : 'An error occurred'
@@ -234,6 +242,9 @@ export function ChatPanel(): JSX.Element {
{aiError && ( {aiError && (
<div className="chat-error">{aiError}</div> <div className="chat-error">{aiError}</div>
)} )}
{feedbackNotice && (
<div className="chat-info-notice">{feedbackNotice}</div>
)}
</div> </div>
<ChatInput <ChatInput

View File

@@ -101,7 +101,7 @@ export function AnalysisToolbar(): JSX.Element {
// For show_tell mode, force all annotations to the show_tell type so the // 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'. // classifier doesn't accidentally mis-label them as 'style' or 'consistency'.
const overrideType = mode === 'show_tell' ? 'show_tell' : undefined const overrideType = mode === 'show_tell' ? 'show_tell' : undefined
const newAnnotations = parseAnnotationsFromAIResponse(lastMsg.content, activeFileContent, overrideType) const { annotations: newAnnotations } = parseAnnotationsFromAIResponse(lastMsg.content, activeFileContent, overrideType)
if (newAnnotations.length > 0) { if (newAnnotations.length > 0) {
const existing = useEditorStore.getState().annotations.filter((a) => a.type !== mode) const existing = useEditorStore.getState().annotations.filter((a) => a.type !== mode)
setAnnotations([...existing, ...newAnnotations]) setAnnotations([...existing, ...newAnnotations])

View File

@@ -7,10 +7,11 @@ export function parseAnnotationsFromAIResponse(
aiResponse: string, aiResponse: string,
documentContent: string, documentContent: string,
overrideType?: AnnotationType overrideType?: AnnotationType
): TextAnnotation[] { ): { annotations: TextAnnotation[]; droppedCount: number } {
const annotations: TextAnnotation[] = [] const annotations: TextAnnotation[] = []
let id = 0 let id = 0
const runId = Date.now() const runId = Date.now()
let droppedCount = 0
// Match quoted strings — handles "straight", "curly", and 'single' quotes // Match quoted strings — handles "straight", "curly", and 'single' quotes
// Minimum 10 chars to avoid matching short words // Minimum 10 chars to avoid matching short words
@@ -29,7 +30,10 @@ export function parseAnnotationsFromAIResponse(
docIndex = findNormalized(documentContent, normalized) docIndex = findNormalized(documentContent, normalized)
} }
if (docIndex === -1) continue if (docIndex === -1) {
droppedCount++
continue
}
// Don't annotate the same range twice // Don't annotate the same range twice
const alreadyAnnotated = annotations.some( const alreadyAnnotated = annotations.some(
@@ -61,7 +65,7 @@ export function parseAnnotationsFromAIResponse(
}) })
} }
return deduplicateOverlapping(annotations) return { annotations: deduplicateOverlapping(annotations), droppedCount }
} }
function deduplicateOverlapping(annotations: TextAnnotation[]): TextAnnotation[] { function deduplicateOverlapping(annotations: TextAnnotation[]): TextAnnotation[] {