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
69 lines
3.4 KiB
Go
69 lines
3.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/Tencent/WeKnora/internal/types"
|
|
)
|
|
|
|
// Reuse the same access-checked scope resolver as retrieval. Descriptions are
|
|
// persisted document summaries, not a claim that every raw chunk was read.
|
|
func (s *sessionService) KnowledgeReadingOverview(ctx context.Context, kbs, files []string, scopes []types.TagScope) (*types.KnowledgeReadingOverview, error) {
|
|
tenant, ok := types.TenantIDFromContext(ctx)
|
|
if !ok { return nil, fmt.Errorf("workspace ID not found in context") }
|
|
targets, err := s.buildSearchTargets(ctx, tenant, kbs, files, scopes)
|
|
if err != nil { return nil, err }
|
|
result := &types.KnowledgeReadingOverview{Entries: []types.KnowledgeReadingEntry{}}
|
|
scopeParts := []string{}
|
|
seen := map[string]bool{}
|
|
for _, target := range targets {
|
|
ids, tags := append([]string(nil), target.KnowledgeIDs...), append([]string(nil), target.TagIDs...)
|
|
sort.Strings(ids)
|
|
sort.Strings(tags)
|
|
scopeParts = append(scopeParts, fmt.Sprintf("%s|%s|%v|%v", target.KnowledgeBaseID, target.Type, ids, tags))
|
|
base, checkErr := s.knowledgeBaseService.GetKnowledgeBaseByID(ctx, target.KnowledgeBaseID)
|
|
if checkErr != nil { return nil, checkErr }
|
|
if base == nil { return nil, fmt.Errorf("知识库不存在或无权使用") }
|
|
listCtx := ctx
|
|
if base.TenantID != tenant {
|
|
if s.kbShareService == nil { return nil, fmt.Errorf("知识库不存在或无权使用") }
|
|
allowed, shareErr := s.kbShareService.HasTenantKBPermission(ctx, base.ID, tenant, types.TenantRoleFromContext(ctx), types.OrgRoleViewer)
|
|
if shareErr != nil || !allowed { return nil, fmt.Errorf("知识库不存在或无权使用") }
|
|
listCtx = context.WithValue(ctx, types.TenantIDContextKey, base.TenantID)
|
|
}
|
|
var rows []*types.Knowledge
|
|
if target.Type == types.SearchTargetTypeKnowledge {
|
|
rows, err = s.knowledgeService.GetKnowledgeBatchWithSharedAccess(ctx, tenant, target.KnowledgeIDs)
|
|
} else {
|
|
rows, err = s.knowledgeService.ListKnowledgeByKnowledgeBaseID(listCtx, target.KnowledgeBaseID)
|
|
}
|
|
if err != nil { return nil, err }
|
|
for _, row := range rows {
|
|
if row == nil || row.KnowledgeBaseID != target.KnowledgeBaseID || seen[row.ID] || row.EnableStatus == "disabled" || row.ParseStatus != types.ParseStatusCompleted { continue }
|
|
seen[row.ID] = true
|
|
title := row.Title
|
|
if title == "" { title = row.FileName }
|
|
summary := ""
|
|
// FAQ tags select chunks within one document; its whole-document
|
|
// summary could expose content outside the chosen category.
|
|
if row.SummaryStatus == types.SummaryStatusCompleted && len(target.TagIDs) == 0 { summary = strings.TrimSpace(row.Description) }
|
|
entry := types.KnowledgeReadingEntry{ID: row.ID, KnowledgeBaseID: row.KnowledgeBaseID, Title: title, Folder: row.FolderPath, Summary: summary}
|
|
entry.Fingerprint = fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%s|%s|%s|%s|%s", row.ID, row.FileHash, row.UpdatedAt.UTC().Format("2006-01-02T15:04:05.999999999Z"), title, summary))))
|
|
result.Entries = append(result.Entries, entry)
|
|
result.DocumentCount++
|
|
if summary != "" { result.SummaryCount++ }
|
|
}
|
|
}
|
|
sort.Slice(result.Entries, func(i,j int) bool { return result.Entries[i].ID < result.Entries[j].ID })
|
|
hash := sha256.New()
|
|
sort.Strings(scopeParts)
|
|
for _, part := range scopeParts { fmt.Fprintln(hash, part) }
|
|
for _, entry := range result.Entries { fmt.Fprintln(hash, entry.Fingerprint) }
|
|
result.Fingerprint = fmt.Sprintf("%x", hash.Sum(nil))
|
|
return result, nil
|
|
}
|