:lightning: smarter checking for custom feedback
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user