Fix: Search mind map not displaying (#16866)

This commit is contained in:
euvre
2026-07-14 17:04:47 +08:00
committed by GitHub
parent eb506221c3
commit 315e5725c2
2 changed files with 70 additions and 16 deletions

View File

@@ -17,10 +17,12 @@
package handler
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"ragflow/internal/common"
"ragflow/internal/entity"
@@ -61,20 +63,33 @@ func runMindMap(config mindMapRunConfig) (mindMapNode, error) {
return mindMapNode{ID: "root", Children: []mindMapNode{}}, nil
}
modelID, _ := config.SearchConfig["chat_id"].(string)
if modelID == "" && config.TenantSvc != nil {
defaultModel, err := config.TenantSvc.GetDefaultModelName(modelTenantID, entity.ModelTypeChat)
if err == nil {
modelID = defaultModel
messages := []modelModule.Message{{Role: "system", Content: mindMapPrompt(strings.Join(sections, "\n"))}, {Role: "user", Content: "Output:"}}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
// search_config chat_id can be a stale tenant_model ID that no longer
// exists. ResolveModelConfig tries ID lookup first, then falls back to
// composite-name parsing which fails for bare IDs. If the configured
// model can't be resolved, fall back to the tenant's default chat model
// (mirrors Python's gen_mindmap get_tenant_default_model_by_type).
ch, streamErr := config.LLM.ChatStream(ctx, modelTenantID, modelID, messages, &modelModule.ChatConfig{})
if streamErr != nil && config.TenantSvc != nil {
if defaultModel, err := config.TenantSvc.GetDefaultModelName(modelTenantID, entity.ModelTypeChat); err == nil && defaultModel != "" && defaultModel != modelID {
ch, streamErr = config.LLM.ChatStream(ctx, modelTenantID, defaultModel, messages, &modelModule.ChatConfig{})
}
}
response, err := config.LLM.Chat(modelTenantID, modelID, []modelModule.Message{{Role: "user", Content: mindMapPrompt(strings.Join(sections, "\n"))}, {Role: "user", Content: "Output:"}}, &modelModule.ChatConfig{})
if err != nil {
return mindMapNode{}, err
if streamErr != nil {
return mindMapNode{}, streamErr
}
if response == nil || response.Answer == nil {
var sb strings.Builder
for delta := range ch {
sb.WriteString(delta)
}
fullText := sb.String()
if fullText == "" {
return mindMapNode{ID: "root", Children: []mindMapNode{}}, nil
}
return parseMindMapMarkdown(*response.Answer), nil
return parseMindMapMarkdown(fullText), nil
}
func searchConfigFromDetail(detail map[string]interface{}) map[string]interface{} {
@@ -232,22 +247,20 @@ type mindMapNode struct {
var mindMapHeadingRe = regexp.MustCompile(`^(#{1,6})\s+(.+)$`)
var mindMapListRe = regexp.MustCompile(`^(\s*)(?:[-*+]|\d+\.)\s+(.+)$`)
var mindMapThinkRe = regexp.MustCompile(`(?s)<think>.*?(?:</think>|$)`)
var mindMapFenceRe = regexp.MustCompile("(?m)^```[^\n]*$")
func parseMindMapMarkdown(text string) mindMapNode {
text = mindMapThinkRe.ReplaceAllString(text, "")
text = mindMapFenceRe.ReplaceAllString(text, "")
lines := strings.Split(strings.ReplaceAll(text, "\r\n", "\n"), "\n")
root := mindMapNode{ID: "root", Children: []mindMapNode{}}
stack := []*mindMapNode{&root}
inFence := false
listBaseLevel := 1
lastWasList := false
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "```") {
inFence = !inFence
lastWasList = false
continue
}
if inFence || trimmed == "" {
if trimmed == "" {
lastWasList = false
continue
}

View File

@@ -584,6 +584,47 @@ func TestParseMindMapMarkdown_ListUnderHeading(t *testing.T) {
}
}
func TestParseMindMapMarkdown_CodeFence(t *testing.T) {
input := "```markdown\n# Title\n## Section\n- Item\n```"
got := parseMindMapMarkdown(input)
if got.ID != "Title" {
t.Fatalf("root = %q, want Title", got.ID)
}
if len(got.Children) != 1 || got.Children[0].ID != "Section" {
t.Fatalf("children = %+v, want Section", got.Children)
}
}
func TestParseMindMapMarkdown_ThinkTag(t *testing.T) {
input := "<think>reasoning here</think>\n# Title\n- Item"
got := parseMindMapMarkdown(input)
if got.ID != "Title" {
t.Fatalf("root = %q, want Title", got.ID)
}
if len(got.Children) != 1 || got.Children[0].ID != "Item" {
t.Fatalf("children = %+v, want Item", got.Children)
}
}
func TestParseMindMapMarkdown_ThinkTagMultiple(t *testing.T) {
input := "<think>first</think>\n# A\n- x\n<think>second</think>\n# B\n- y"
got := parseMindMapMarkdown(input)
if got.ID != "root" {
t.Fatalf("root = %q, want root (two top-level headings)", got.ID)
}
if len(got.Children) != 2 || got.Children[0].ID != "A" || got.Children[1].ID != "B" {
t.Fatalf("children = %+v, want [A, B]", got.Children)
}
}
func TestParseMindMapMarkdown_ThinkTagUnclosed(t *testing.T) {
input := "<think>reasoning that gets cut off by max tokens without a close tag"
got := parseMindMapMarkdown(input)
if len(got.Children) != 0 {
t.Fatalf("children = %+v, want empty root for unclosed think block", got.Children)
}
}
// ---- SSE helper direct tests ----
func TestSseAnswer_Final(t *testing.T) {