feat(knowledge): 知识库文件夹上传的左侧树形目录 - #50
Draft
lyingbug wants to merge 11 commits into
Draft
Conversation
…/mismatched results If the embedding provider returns an empty slice of vectors (e.g. length 0) due to api mismatch or empty input, BatchEmbed will return length 0. Accessing embedding[i] directly in BatchEmbedWithPool results in an out of bounds panic. Because the panic occurs inside a mu.Lock() block, the mutex is never unlocked, leading to a permanent deadlock of the document processing queue. This fix checks the returned embeddings slice length and returns an error instead of panicking, releasing the mutex properly.
…er tree API Folder uploads used to encode their relative directory inside file_name, which made the document list render the whole path as the title and left no way to browse or query a single folder. The directory now lives in a dedicated folder_path column (with a backfill for existing rows), the document list accepts folder_path / folder_recursive filters, and a new GET /knowledge-bases/:id/knowledge/folders endpoint returns the folder tree with per-folder document counts.
The documents tab gains a left folder column built from the folder_path now stored on each knowledge entry. Selecting a folder filters the existing document list (so search, tags, type, status and date filters all keep working), a breadcrumb above the list shows where you are, and uploads made while browsing a folder land in that folder. A per-row folder chip appears only when the list can span folders.
The upload-path builder, breadcrumb/ancestor derivation, folder lookup and tree flattening were inline in the view and the sidebar component. Moving them into folderTree.ts follows the convention of kbListMerge.ts and wikiStatusRefresh.ts, removes the duplicated webkitRelativePath handling, and makes the folder navigation logic directly testable.
The sidebar listed "all documents" and "root" as two rows sitting next to the top-level folders, but those folders are inside the root, so the three rows contradicted each other and reaching the top level for an upload meant selecting a row that did not look like a parent. There is now a single root row with every folder nested beneath it, and what a row lists is decided by one uniform rule at every level: the direct documents, or the whole subtree when the scope toggle is on. "Documents not in any folder" is the root with the toggle off, which is also why the toggle now defaults to on. The upload destination is no longer implied by the sidebar selection either: the confirmation dialog shows which folder the batch lands in, lets it be switched back to the top level, and lists each file's own sub-directory.
…n the tree A folder-only tree cannot show a node for a document that is not in any folder, so single-file uploads were unreachable from the sidebar: a base with 5 documents and one 3-document folder just left two of them unaccounted for. Rather than add a second pseudo-folder row for them (the ambiguity the previous commit removed), the one existing mechanism is made visible: the scope switch is now a labeled segmented control instead of an icon behind a tooltip, and the root row is named after whatever the current scope makes it list - the whole base, or exactly the unfiled documents. The breadcrumb's leading crumb follows the same rule.
The scope switch is gone. It existed because "select a folder" was ambiguous in a list that could not show folders, and turning that ambiguity into a control just moved the problem onto the user. The list now shows what a folder contains: its sub-folders as entries first, then the documents directly inside it. So the mode follows what the user is already doing instead of a switch - browsing shows one level, and any active filter searches the whole subtree flat, with a breadcrumb note saying so. A document uploaded on its own needs no special row either: it sits at the top level next to the folders, exactly where it is. Folders can also be adjusted now, which they could not before - a mis-filed upload previously had to be deleted and re-uploaded. Documents move via the row menu or the batch bar into an existing or newly typed folder, and a folder can be renamed in place, carrying its subtree. Both are plain folder_path updates: nothing is re-parsed, re-chunked or re-embedded.
…e the folder picker to a popup Two things the folder UI got wrong. The rename state used the empty string to mean "nothing is being renamed", but the empty string is also the root folder's own path, so the root row matched and permanently rendered a stray rename input. The sentinel is now null, and rename is additionally gated on the row being a real folder, so no sentinel value can reach the root again. Moving documents also did not deserve a modal dialog: it is one small, reversible choice. In a row's menu the picker is now another level of the menu that is already open, mirroring the "move to knowledge base" sub-menu right next to it; in the batch bar it hangs off the button. Picking a folder performs the move, since there is nothing else to confirm. The upload destination line was also mangling folder names: truncating from the left with direction: rtl reorders CJK text. It now shows the destination's last segment with the full path in the title, reads as a quiet line instead of a grey banner, and only appears when the destination is not the top level.
…alog - Added folder options to the upload confirmation dialog, allowing users to select from existing folders as upload destinations. - Updated folder tree UI to improve folder browsing experience, displaying sub-folders and documents more intuitively. - Enhanced folder picker functionality to support creating new sub-folders directly from the picker. - Improved localization for folder-related strings across multiple languages. - Refactored related components and tests to accommodate new features and ensure proper functionality. This commit streamlines the user experience for managing folders and uploading documents, making it easier to navigate and organize content.
Document folder tree/move/rename endpoints in docs/api, Swagger, and the Go client; align frontend path normalization with server caps; guard rename no-ops and duplicate folder creation; enforce KB ownership on folder rename.
…nderscore scenarios - Updated SQL queries to properly escape LIKE wildcards in folder path filters. - Added tests for renaming knowledge folder paths, including cases with underscores in paths. - Enhanced existing tests to ensure correct folder listing behavior when using underscores.
lyingbug
force-pushed
the
cursor/knowledge-folder-tree-5c67
branch
from
August 4, 2026 12:42
cb7db8f to
e5b1f78
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
文件夹上传此前把相对目录塞进
file_name(例如docs/spec/design.md),导致:交互设计
列表展示所选目录的内容:子目录作为条目排在前面,然后是本层文档 —— 就是文件管理器的行为。因此:
目录调整都用轻量浮层,不用模态弹窗:移动文档是一个小的、可逆的选择,行菜单里它就是已经打开的那个菜单的下一层(与紧挨着的「移动到知识库」子菜单同一套交互),批量工具栏里它挂在按钮上;点中目录即执行,没有多余的确认步骤。目录本身在树上就地重命名,连带整棵子树。
两者都只是
folder_path一列的更新,不触发重新解析、切块或向量化。改动
后端
knowledges新增folder_path列(Postgres000079/ SQLite000002迁移),并把历史file_name中的目录部分回填进去,file_name只保留基础文件名。fileName表单字段拆分成folder_path+file_name,路径经过归一化(去掉../.、限制深度与长度)并复用现有输入校验。folder_path/folder_recursive查询参数;文件夹维度按参数是否出现生效,因为空字符串本身是有意义的值(知识库顶层)。GET /api/v1/knowledge-bases/:id/knowledge/folders返回带每层文档数的目录树。POST /api/v1/knowledge/folder批量修改归属目录;目标路径不存在时隐式创建,空路径表示顶层。PUT /api/v1/knowledge-bases/:id/knowledge/folders重命名/移动目录及其子树;目标已存在时合并,拒绝移动到自身子目录。folder_path。前端
KbFolderTree.vue,风格沿用 Wiki 侧边栏的行高、缩进与 TDesign 变量;根节点是真实父节点,所有文件夹嵌在其下;hover 出现重命名入口,行内编辑。FolderPickerMenu.vue:一份目录选择实现,被行菜单、列表行菜单、批量工具栏三处复用;含「新建子目录」行内输入,回车即创建并移动。folderTree.ts,与kbListMerge.ts/wikiStatusRefresh.ts的惯例一致,并配单测。修过的两个自身 bug
根目录行常驻一个重命名输入框。 重命名状态用空字符串表示「没有在重命名」,而空字符串恰好是根目录自己的路径,于是根目录行永远匹配。哨兵值改为
null,并额外要求该行是真实文件夹,两道独立的闸;KbFolderTree.test.ts锁住这两条,避免复发。上传目标行把中文目录名显示错乱。 用
direction: rtl从左侧截断会重排 CJK 文本。改为显示目标路径的最后一段、完整路径放在 title,并从灰色横幅改成一行安静的说明文字。测试
自动化
go build ./...;go test ./internal/types/... ./internal/handler/... ./internal/application/repository/... ./internal/application/service/...全绿。新增 7 个测试文件覆盖路径归一化、树构建、仓储筛选/聚合、批量改目录的跨库隔离、子树前缀重写(含同前缀兄弟目录不被误伤)、上传路径拆分、handler 参数语义、移动合法性校验。npm test330 passed、npm run type-check、npm run check-i18n、npm run build。接口级端到端(Lite 模式 + SQLite,本地 embedding 桩服务)
把两个未归类文件移动到全新目录
归档/2026,目录被隐式创建、顶层直属数归零:重命名
团队手册/handbook→团队手册/员工手册连带子树共 4 条,onboarding与policies随之改路径;护栏生效:移动到自身子目录被拒(「不能将文件夹移动到它自己的子目录下」)、空目标被拒、不存在的目录为 no-op。浏览态与筛选态逐一核对:
团队手册团队手册/员工手册归档/2026standalone团队手册内筛md此外:
folder_path=../../…被归一化,无法越出知识库顶层;所有文档parse_status为completed,即folder_path不影响解析链路。界面手工验证由作者本地进行。
附注(不在本 PR 范围内)
在本地跑 Lite 模式时发现
migrations/sqlite落后于migrations/versioned,缺少tenants.api_principal_config、users.is_system_admin、messages.attachments、knowledges.pending_subtasks_count、mcp_oauth_tokens.principal_type以及task_pending_ops表,导致 Lite 版无法完成 auto-setup。这是既有问题,与本次改动无关,本 PR 未做修改。