fix: OCR keywords leaking into LLM prompt + streaming preview issues (#74)

- Strip consumed OCR option keywords (縦書き, figures, etc.) from message
  content after PDF processing so they don't get interpreted as LLM instructions
- Apply StripThinkBlocks to streaming preview to hide <think> tags
- Clear orphan draft on SendDraft failure to prevent stale preview bubbles

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-03-21 03:25:42 +09:00 committed by GitHub
parent fc6714b52b
commit fc6baf3402
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 2 deletions

View file

@ -256,7 +256,7 @@ func (al *AgentLoop) setupStreamingHook(opts processOptions, task *activeTask) (
go func() {
defer close(streamDone)
for up := range streamCh {
display := buildStreamingDisplay(up.accumulated, up.reasoning)
display := buildStreamingDisplay(utils.StripThinkBlocks(up.accumulated), up.reasoning)
outMsg := bus.OutboundMessage{
Channel: opts.Channel,
ChatID: opts.ChatID,

View file

@ -471,6 +471,38 @@ func detectReadingOrder(content string, cfgDefault string) string {
return "auto"
}
// stripConsumedOCRKeywords removes OCR option keywords from the message
// content after they have been consumed by PDF processing, so the LLM
// does not interpret them as user instructions.
func stripConsumedOCRKeywords(content string, withFigures bool, readingOrder string) string {
lower := strings.ToLower(content)
if withFigures {
for _, kw := range figureKeywords {
if idx := strings.Index(lower, kw); idx >= 0 {
content = content[:idx] + content[idx+len(kw):]
lower = lower[:idx] + lower[idx+len(kw):]
}
}
}
if readingOrder != "auto" {
for _, kw := range readingOrderKeywords {
if kw.order != readingOrder {
continue
}
if idx := strings.Index(lower, kw.keyword); idx >= 0 {
content = content[:idx] + content[idx+len(kw.keyword):]
lower = lower[:idx] + lower[idx+len(kw.keyword):]
}
}
}
// Normalize runs of whitespace left by removal.
for strings.Contains(content, " ") {
content = strings.ReplaceAll(content, " ", " ")
}
content = strings.TrimSpace(content)
return content
}
const pdfHintMessage = "PDF OCR in progress. " +
"Tip: include \"figures\" or \"\u56f3\u7248\" to extract images. " +
"Add \"\u7e26\u66f8\u304d\" or \"\u6a2a\u66f8\u304d\" to set reading order."
@ -495,9 +527,14 @@ func (al *AgentLoop) processPDFsInMessages(
cfgRO = ocrCfg.ReadingOrder
}
readingOrder := detectReadingOrder(m.Content, cfgRO)
result[i].Content = al.replacePDFTags(
newContent := al.replacePDFTags(
ctx, m.Content, ocrCfg, channel, chatID, withFigures, readingOrder,
)
// PDF was actually processed (content changed) → strip consumed keywords
if newContent != m.Content {
newContent = stripConsumedOCRKeywords(newContent, withFigures, readingOrder)
}
result[i].Content = newContent
}
return result

View file

@ -184,6 +184,8 @@ func (m *Manager) preSend(ctx context.Context, name string, msg bus.OutboundMess
}
return true
}
// Clear orphan draft on failure to prevent stale streaming preview.
_ = drafter.SendDraft(ctx, msg.ChatID, entry.draftID, "")
}
m.statusEditTimes.Delete(key)
// Draft update failed → fall through to placeholder path.