WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
htmltomd "github.com/JohannesKaufmann/html-to-markdown/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
htmlTagPattern = regexp.MustCompile(`<[^>]+>`)
|
||||
codeBlockPattern = regexp.MustCompile("(?s)^\\s*```[a-zA-Z]*\\s*\n(.*?)\n\\s*```\\s*$")
|
||||
htmlDocPattern = regexp.MustCompile(`(?i)^\s*(<\!DOCTYPE|<html|<body|<div|<p[\s>]|<table|<h[1-6][\s>])`)
|
||||
multipleNewlines = regexp.MustCompile(`\n{3,}`)
|
||||
knownEmptyReplies = []string{
|
||||
"无文字内容",
|
||||
"无法识别",
|
||||
"no text",
|
||||
"no text content",
|
||||
"no content",
|
||||
"empty",
|
||||
"图片中没有文字",
|
||||
"图片中没有可识别的文字",
|
||||
}
|
||||
)
|
||||
|
||||
// sanitizeOCRText cleans up VLM OCR output by stripping HTML wrappers,
|
||||
// converting HTML to markdown, and filtering out useless responses.
|
||||
func sanitizeOCRText(raw string) string {
|
||||
text := strings.TrimSpace(raw)
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
text = stripMarkdownCodeBlock(text)
|
||||
|
||||
// If stripping HTML tags leaves almost no text, the response is useless
|
||||
// (e.g. "<html><body><div class="image"><img/></div></body></html>").
|
||||
plainText := strings.TrimSpace(htmlTagPattern.ReplaceAllString(text, ""))
|
||||
if len(plainText) < 10 && htmlTagPattern.MatchString(text) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if looksLikeHTML(text) {
|
||||
text = ocrHTMLToMarkdown(text)
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
if isKnownEmptyReply(text) {
|
||||
return ""
|
||||
}
|
||||
|
||||
text = multipleNewlines.ReplaceAllString(text, "\n\n")
|
||||
return strings.TrimSpace(text)
|
||||
}
|
||||
|
||||
// stripMarkdownCodeBlock removes a markdown code-fence wrapper that some
|
||||
// models add around their output (e.g. ```html\n...\n``` or ```markdown\n...\n```).
|
||||
func stripMarkdownCodeBlock(text string) string {
|
||||
if m := codeBlockPattern.FindStringSubmatch(text); len(m) == 2 {
|
||||
return strings.TrimSpace(m[1])
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// looksLikeHTML returns true when the text appears to be an HTML document
|
||||
// or contains a significant amount of HTML tags.
|
||||
func looksLikeHTML(text string) bool {
|
||||
if htmlDocPattern.MatchString(text) {
|
||||
return true
|
||||
}
|
||||
tags := htmlTagPattern.FindAllString(text, -1)
|
||||
if len(tags) == 0 {
|
||||
return false
|
||||
}
|
||||
tagChars := 0
|
||||
for _, t := range tags {
|
||||
tagChars += len(t)
|
||||
}
|
||||
return float64(tagChars)/float64(len(text)) > 0.3
|
||||
}
|
||||
|
||||
// ocrHTMLToMarkdown converts HTML content to markdown, falling back to the
|
||||
// original text on failure.
|
||||
func ocrHTMLToMarkdown(content string) string {
|
||||
md, err := htmltomd.ConvertString(content)
|
||||
if err != nil {
|
||||
return content
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// isKnownEmptyReply checks whether the text matches a known "no content"
|
||||
// reply pattern that VLM models produce when the image has no text.
|
||||
// Trailing punctuation (., !, ?) is stripped before comparison so that
|
||||
// responses like "No text content." still match "no text content".
|
||||
func isKnownEmptyReply(text string) bool {
|
||||
lower := strings.ToLower(strings.TrimSpace(text))
|
||||
lower = strings.TrimRight(lower, ".!?。!?")
|
||||
for _, phrase := range knownEmptyReplies {
|
||||
if lower == strings.ToLower(phrase) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user