Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
2b31d36
fix(embedding): prevent ants worker panic and mutex deadlock on empty…
ljling90 Aug 4, 2026
fa67dfd
feat(knowledge): store folder path for folder uploads and expose fold…
Aug 4, 2026
f747d39
feat(knowledge): show uploaded folder structure as a sidebar tree
Aug 4, 2026
27efebb
refactor(knowledge): extract folder tree helpers and unit-test them
Aug 4, 2026
d6083c6
fix(knowledge): make the folder tree root a real parent node
Aug 4, 2026
b3e31a2
fix(knowledge): give individually uploaded documents a visible home i…
Aug 4, 2026
5fea7a1
feat(knowledge): browse folders like a file manager and allow re-filing
Aug 4, 2026
192a721
fix(knowledge): stop the root row rendering a rename input, and demot…
Aug 4, 2026
7316d95
feat(knowledge): enhance folder management and upload confirmation di…
lyingbug Aug 4, 2026
bb23444
fix(knowledge): sync folder APIs and address review follow-ups
lyingbug Aug 4, 2026
2f2be9f
refactor(knowledge): improve folder path handling and add tests for u…
lyingbug Aug 4, 2026
d37e289
fix(frontend): restore runtime queue i18n and guard against prune
lyingbug Aug 4, 2026
22af9b2
fix(chat): preserve retrieval fallback errors
KKtwo Aug 4, 2026
5723e83
fix(chat): cover web partial success and wiki-only embed fallback
lyingbug Aug 4, 2026
350d9e4
perf(container): batch legacy storage alias lookup at startup
lyingbug Aug 4, 2026
3507156
feat(storage): support AWS S3 NoAk default credentials (#2008)
qilifan Aug 5, 2026
51b88bb
fix(summary): preserve rows across table chunks / 修复表格分块摘要丢行
BigFishDreamWater Aug 5, 2026
a6926fc
refactor(storage): extract storage reference URL rewriting into inter…
Aug 4, 2026
877a6eb
feat(api): add resource_urls=public to return directly loadable file …
Aug 4, 2026
96e28b2
test(api): cover resource_urls=public across the rewriter, handlers a…
Aug 4, 2026
ef1f0b3
docs(api): document resource_urls and RESOURCE_URL_MODE
Aug 4, 2026
0613195
refactor(chat): build SSE payload after the liveness check
Aug 4, 2026
8b00cb7
perf(storage): reuse a live resource access grant per resource
lyingbug Aug 4, 2026
eb6ac01
fix(api): close the resource_urls=public gaps found in review
lyingbug Aug 4, 2026
e71c2dc
fix(api): address resource_urls review follow-ups
lyingbug Aug 5, 2026
b191201
style(client): gofmt message.go
lyingbug Aug 5, 2026
d2888e9
fix(chunking): align preview with parent-child splitting / 修复预览与父子分块不一致
BigFishDreamWater Aug 5, 2026
39f4220
fix(wiki): ensure prompt language is always resolved
lyingbug Aug 5, 2026
1985a59
fix(chunking): sync preview swagger and normalize splitter config
lyingbug Aug 5, 2026
03d6f90
fix(faq): prevent duplicate entries when a create is retried
lyingbug Aug 5, 2026
63b5f35
Improve Wiki reader layout and revision history UX.
lyingbug Aug 5, 2026
520be78
fix(chunking): normalize line endings before splitting / 在分块前统一换行符
BigFishDreamWater Aug 5, 2026
54c4309
feat(mcp-server): add create_knowledge_from_text tool
01luyicheng Aug 4, 2026
1737221
docs(mcp-server): document create_knowledge_from_file/text tools
01luyicheng Aug 4, 2026
0827cd6
fix: retry document summaries before fallback
ttommybot Aug 3, 2026
b39f006
fix: replace all exact wiki text matches
quan020406 Aug 4, 2026
cb5e59b
feat(im): add markdown replies for QQBot
YSs-ZDCK Aug 1, 2026
611b9a8
chore(deps): bump the cli-deps group in /cli with 3 updates
dependabot[bot] Aug 1, 2026
439af21
feat(feishu-driver): 新增飞书云盘数据源 (#2466)
Greenplumwine Aug 5, 2026
f16a4ed
fix(wiki): exclude archived pages from stats queries
pengpengyi92 Aug 5, 2026
6b0d8d1
fix(im/wecom): resolve longconn deadlock between closeConn and heartb…
Aug 6, 2026
e2a75db
chore(types): add internal only ContentRevision to types.SearchResult
mdrkrg Aug 6, 2026
3abeba3
test(chunks): add tests to pin contracts for position-aware merge
mdrkrg Aug 6, 2026
b7b8562
feat(chunks): restore position-aware merge for trusted pairs
mdrkrg Aug 6, 2026
c8d6e21
fix(im/wecom): scope connection close to its own generation
lyingbug Aug 6, 2026
c9dd5a5
chore(types): pin the gorm column for SearchResult.ContentRevision
lyingbug Aug 6, 2026
dfe1438
test(chunks): pin exact-overlap contracts for trusted merges
lyingbug Aug 6, 2026
e0ea453
fix(chunks): trim trusted overlaps by exact position, not text search
lyingbug Aug 6, 2026
e8882fa
fix(docs): correct swagger @Router path annotations for agent-chat an…
qfzc Aug 6, 2026
0a3f6b0
fix(chunks): 流水线重写后的检索结果不用位置匹配 (#2558)
mdrkrg Aug 6, 2026
a88c165
docs(website-docs): add comprehensive product documentation site
lyingbug Aug 6, 2026
08825e0
feat(website-docs): add quickstart sample data and local MCP demo
lyingbug Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
31 changes: 30 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ DOCREADER_TRANSPORT=grpc
# APP_EXTERNAL_URL=
# 前端外部 origin,用于邀请链接等绝对 URL(留空走 host-relative)。
# FRONTEND_BASE_URL=
# API 响应里文件引用的默认形式:handle(默认)/ public。
# handle — 返回内部 resource://<handle>,客户端需再调用带鉴权的 /files 代理取图。
# public — 直接返回可加载的 http(s) 链接,第三方 App 拿到即可渲染,无需二次请求。
# 单次请求可用 ?resource_urls=handle|public 覆盖本变量,覆盖范围见 README「API 参考」。
# public 依赖上面的 APP_EXTERNAL_URL(或存储后端本身公网可达)才能生成外链;
# 无法生成时该引用保持 resource:// 原样,客户端仍可回退到 /files。
# 注意:public 会为每个被引用文件签发限时(WeKnora 2 小时 / MinIO 24 小时)的匿名可读链接。
# 匿名的 embed 渠道与限定知识库的 API Key 不受本变量影响,始终返回 handle。
# 建议同时配置 SYSTEM_AES_KEY,以便复用 grant 行、稳定直链 URL 并降低读接口的写入压力。
# RESOURCE_URL_MODE=handle
# 对外暴露的 MCP Server 端口(默认 8082)。
# MCP_PORT=8082

Expand Down Expand Up @@ -200,11 +210,16 @@ LOCAL_STORAGE_BASE_DIR=/data/files
# TOS_PATH_PREFIX=your_tos_path_prefix
# TOS_TEMP_BUCKET_NAME=your_tos_temp_bucket_name
# TOS_TEMP_REGION=your_tos_temp_region
# 如果使用AWS S3作为文件存储,需要配置以下参数
# AWS S3的访问端点(可选;留空使用 Region 对应的 AWS 标准端点,例如 https://s3.amazonaws.com)
# ----- AWS S3(STORAGE_TYPE=s3)-----
# S3_ENDPOINT=https://s3.amazonaws.com
# S3_REGION=us-east-1
# S3_ACCESS_KEY=your_s3_access_key
# AWS S3访问密钥 Access Key(可选;AK/SK 同时留空时使用 AWS SDK 默认凭证链,
# 支持 EC2/ECS/EKS IAM Role、IRSA/Web Identity、环境变量和共享配置文件)
# AWS S3访问密钥 Secret Key(必须与 Access Key 同时填写或同时留空)
# S3_SECRET_KEY=your_s3_secret_key
# S3_ACCESS_KEY=your_s3_access_key
# S3_BUCKET_NAME=your_s3_bucket_name
# S3_PATH_PREFIX=weknora/
# S3_USE_SSL=true
Expand Down Expand Up @@ -479,6 +494,20 @@ OLLAMA_BASE_URL=http://host.docker.internal:11434
# WEKNORA_CHAT_ATTACHMENT_OCR_CONCURRENCY=8
# WEKNORA_CHAT_ATTACHMENT_OCR_MAX_PAGES=8

# ========== E9. 飞书云文档解析模式 ==========
# 控制飞书云盘 / 飞书知识库同步新版云文档(docx)时的解析路径:
# export(默认,留空即可):走异步导出 API 下载 .docx 二进制,交给 docreader
# 解析。图片 inline 进父文档(parent_chunk_id 同 knowledge 关联),检索 /
# wiki / 智能体三个场景都能关联图片内容。代价:同步变慢;飞书 docx 内的
# file block 附件不会随导出下载(会丢,需单独同步);若云文档中存在电子表格、
# 多维表格等,导出后会变成内嵌在 docx 中的表格,weknora 会无法解析这些表格。
# blocks:走 blocks API 转 Markdown。快,保留 docx 内附件;但图片渲染成空
# `![图片]()` 占位符、并作为独立知识条目入库,与父文档无内容关联(检索 /
# wiki / 智能体均无法把图片关联回文档);电子表格会解析成 markdown 表格,
# 存在丢失合并单元格等样式风险。
# 不需要图片关联、追求速度、要保留附件或需保留电子表格样式时设为 blocks。
# 此方案为临时方案,若后续有更好的解析方案,会移除此环境变量并替换。
# FEISHU_DOCX_PARSE_MODE=export

# #####################################################################
# F. 认证与空间隔离
Expand Down
10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@
# acceptance/contract envelope tests.
*.go text eol=lf
cli/acceptance/testdata/**/*.json text eol=lf

# Trellis: append-only developer journals should merge cleanly across
# parallel sessions/worktrees — each session only appends a new block, so
# there is nothing to actually conflict on.
#
# Do NOT add a rule for workspace/*/index.md here — it is fully regenerated
# every session, so a real conflict there is expected and safe to resolve by
# picking either side (task state lives in task.json, not index.md). See
# .trellis/spec/cli/backend/directory-structure.md for details.
.trellis/workspace/*/journal-*.md merge=union
9 changes: 5 additions & 4 deletions cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ require (
github.com/Tencent/WeKnora/client v0.0.0-00010101000000-000000000000
github.com/charmbracelet/huh v1.0.0
github.com/itchyny/gojq v0.12.19
github.com/mattn/go-isatty v0.0.22
github.com/mattn/go-runewidth v0.0.24
github.com/modelcontextprotocol/go-sdk v1.6.1
github.com/mattn/go-isatty v0.0.24
github.com/mattn/go-runewidth v0.0.27
github.com/modelcontextprotocol/go-sdk v1.7.0
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
Expand Down Expand Up @@ -51,9 +51,10 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.15.0 // indirect
)

// In-repo SDK module: cli pulls the local client/ until both modules ship
Expand Down
18 changes: 10 additions & 8 deletions cli/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,16 @@ github.com/itchyny/timefmt-go v0.1.8 h1:1YEo1JvfXeAHKdjelbYr/uCuhkybaHCeTkH8Bo79
github.com/itchyny/timefmt-go v0.1.8/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=
github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU=
github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ=
github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44=
github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
Expand Down Expand Up @@ -112,13 +112,15 @@ golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
9 changes: 8 additions & 1 deletion client/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)

Expand Down Expand Up @@ -77,8 +78,10 @@ func (c *Client) AgentQAStream(ctx context.Context, sessionID string, query stri
}

// AgentQAStreamWithRequest performs agent-based Q&A with SSE streaming using the full request payload.
// Pass ResourceURLOptions to receive public HTTP(S) file URLs in the stream.
func (c *Client) AgentQAStreamWithRequest(ctx context.Context,
sessionID string, request *AgentQARequest, callback AgentEventCallback,
opts ...ResourceURLOptions,
) error {
if request == nil {
return fmt.Errorf("agent QA request cannot be nil")
Expand All @@ -88,7 +91,11 @@ func (c *Client) AgentQAStreamWithRequest(ctx context.Context,
}

path := fmt.Sprintf("/api/v1/agent-chat/%s", sessionID)
resp, err := c.doRequestStream(ctx, http.MethodPost, path, request, nil)
queryParams := url.Values{}
if len(opts) > 0 {
applyResourceURLQuery(queryParams, &opts[0])
}
resp, err := c.doRequestStream(ctx, http.MethodPost, path, request, queryParams)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
Expand Down
134 changes: 122 additions & 12 deletions client/knowledge.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Knowledge struct {
EnableStatus string `json:"enable_status"`
EmbeddingModelID string `json:"embedding_model_id"`
FileName string `json:"file_name"`
FolderPath string `json:"folder_path"`
FileType string `json:"file_type"`
FileSize int64 `json:"file_size"`
FileHash string `json:"file_hash"`
Expand Down Expand Up @@ -86,14 +87,14 @@ var ErrDuplicateURL = errors.New("URL already exists")
// KnowledgeProcessOverrides stores per-upload parse config overrides sent as process_config.
// When nil, the server uses the knowledge base defaults only.
type KnowledgeProcessOverrides struct {
ParserEngineRules []ParserEngineRule `json:"parser_engine_rules,omitempty"`
ChunkingConfig *ChunkingConfig `json:"chunking_config,omitempty"`
EnableMultimodel *bool `json:"enable_multimodel,omitempty"`
VLMConfig *VLMConfig `json:"vlm_config,omitempty"`
ASRConfig *ASRConfig `json:"asr_config,omitempty"`
QuestionGenerationConfig *QuestionGenerationConfig `json:"question_generation_config,omitempty"`
GraphEnabled *bool `json:"graph_enabled,omitempty"`
ExtractConfig *ExtractConfig `json:"extract_config,omitempty"`
ParserEngineRules []ParserEngineRule `json:"parser_engine_rules,omitempty"`
ChunkingConfig *ChunkingConfig `json:"chunking_config,omitempty"`
EnableMultimodel *bool `json:"enable_multimodel,omitempty"`
VLMConfig *VLMConfig `json:"vlm_config,omitempty"`
ASRConfig *ASRConfig `json:"asr_config,omitempty"`
QuestionGenerationConfig *QuestionGenerationConfig `json:"question_generation_config,omitempty"`
GraphEnabled *bool `json:"graph_enabled,omitempty"`
ExtractConfig *ExtractConfig `json:"extract_config,omitempty"`
}

// CreateKnowledgeFromFile creates a knowledge entry from a local file path
Expand Down Expand Up @@ -320,7 +321,9 @@ func (c *Client) ListKnowledge(ctx context.Context,

// KnowledgeListFilter mirrors the server-side filters accepted by GET
// /api/v1/knowledge-bases/{id}/knowledge. Empty / zero fields are omitted from
// the request.
// the request. FolderPath, when non-nil, selects a folder scope; the empty
// string means the knowledge base root. Omit FolderPath entirely for a flat
// listing across every folder.
type KnowledgeListFilter struct {
TagID string
Keyword string
Expand All @@ -329,8 +332,10 @@ type KnowledgeListFilter struct {
Source string
// StartTime / EndTime filter on knowledge updated_at. Zero values are skipped.
// They are serialized in RFC3339 format.
StartTime time.Time
EndTime time.Time
StartTime time.Time
EndTime time.Time
FolderPath *string
FolderRecursive bool
}

// ListKnowledgeWithFilter lists knowledge entries with the full filter surface.
Expand Down Expand Up @@ -366,6 +371,12 @@ func (c *Client) ListKnowledgeWithFilter(ctx context.Context,
if !filter.EndTime.IsZero() {
queryParams.Add("end_time", filter.EndTime.Format(time.RFC3339))
}
if filter.FolderPath != nil {
queryParams.Add("folder_path", *filter.FolderPath)
if filter.FolderRecursive {
queryParams.Add("folder_recursive", "true")
}
}

resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams)
if err != nil {
Expand All @@ -380,6 +391,105 @@ func (c *Client) ListKnowledgeWithFilter(ctx context.Context,
return response.Data, response.Total, nil
}

// KnowledgeFolderNode is one node of the knowledge base folder tree.
type KnowledgeFolderNode struct {
Path string `json:"path"`
Name string `json:"name"`
DocumentCount int64 `json:"document_count"`
TotalCount int64 `json:"total_count"`
Children []*KnowledgeFolderNode `json:"children,omitempty"`
}

// KnowledgeFolderTree is returned by ListKnowledgeFolders.
type KnowledgeFolderTree struct {
RootDocumentCount int64 `json:"root_document_count"`
TotalDocumentCount int64 `json:"total_document_count"`
Folders []*KnowledgeFolderNode `json:"folders"`
}

// ListKnowledgeFolders returns the folder hierarchy of a knowledge base.
func (c *Client) ListKnowledgeFolders(ctx context.Context, knowledgeBaseID string) (*KnowledgeFolderTree, error) {
path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge/folders", knowledgeBaseID)
resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil)
if err != nil {
return nil, err
}

var response struct {
Success bool `json:"success"`
Data *KnowledgeFolderTree `json:"data"`
}
if err := parseResponse(resp, &response); err != nil {
return nil, err
}
return response.Data, nil
}

// MoveKnowledgeToFolderRequest re-files knowledge entries under a folder path.
type MoveKnowledgeToFolderRequest struct {
KBID string `json:"kb_id"`
IDs []string `json:"knowledge_ids"`
FolderPath string `json:"folder_path"`
}

// MoveKnowledgeToFolderResponse is the payload of POST /knowledge/folder.
type MoveKnowledgeToFolderResponse struct {
MovedCount int64 `json:"moved_count"`
FolderPath string `json:"folder_path"`
}

// MoveKnowledgeToFolder updates folder_path for the given knowledge entries.
// An empty FolderPath moves documents back to the knowledge base root.
func (c *Client) MoveKnowledgeToFolder(ctx context.Context, req *MoveKnowledgeToFolderRequest) (*MoveKnowledgeToFolderResponse, error) {
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/knowledge/folder", req, nil)
if err != nil {
return nil, err
}

var response struct {
Success bool `json:"success"`
Data *MoveKnowledgeToFolderResponse `json:"data"`
}
if err := parseResponse(resp, &response); err != nil {
return nil, err
}
return response.Data, nil
}

// RenameKnowledgeFolderRequest moves a folder and its subtree to a new path.
type RenameKnowledgeFolderRequest struct {
From string `json:"from"`
To string `json:"to"`
}

// RenameKnowledgeFolderResponse is the payload of PUT .../knowledge/folders.
type RenameKnowledgeFolderResponse struct {
MovedCount int64 `json:"moved_count"`
FolderPath string `json:"folder_path"`
}

// RenameKnowledgeFolder rewrites folder_path for a folder and every descendant.
func (c *Client) RenameKnowledgeFolder(
ctx context.Context,
knowledgeBaseID string,
req *RenameKnowledgeFolderRequest,
) (*RenameKnowledgeFolderResponse, error) {
path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge/folders", knowledgeBaseID)
resp, err := c.doRequest(ctx, http.MethodPut, path, req, nil)
if err != nil {
return nil, err
}

var response struct {
Success bool `json:"success"`
Data *RenameKnowledgeFolderResponse `json:"data"`
}
if err := parseResponse(resp, &response); err != nil {
return nil, err
}
return response.Data, nil
}

// DeleteKnowledge enqueues an asynchronous delete for the given knowledge entry.
// The server returns 200 once the task has been submitted; the actual deletion is
// performed by a background worker (same pipeline as BatchDeleteKnowledge).
Expand Down Expand Up @@ -527,7 +637,7 @@ func (c *Client) ReparseKnowledge(ctx context.Context, knowledgeID string) (*Kno
// - pending — task has not started
// - processing — DocReader / chunking / embedding stage
// - finalizing — primary parse done, enrichment subtasks (summary,
// question generation, graph extract) still running
// question generation, graph extract) still running
//
// Returns an error when the knowledge is in a terminal state
// (completed, failed) or already being deleted.
Expand Down
Loading
Loading