From ba3f66d3d1ec5b87528cc84ee1ba49be2d529b19 Mon Sep 17 00:00:00 2001 From: zhayujie Date: Thu, 16 Apr 2026 21:47:44 +0800 Subject: [PATCH] feat: show root-level files (index.md, log.md) in knowledge tree --- .github/workflows/deploy-image-arm.yml | 6 +++- .github/workflows/deploy-image.yml | 4 +++ agent/knowledge/service.py | 50 ++++++++++++++------------ channel/web/static/js/console.js | 46 +++++++++++++++++++----- 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/.github/workflows/deploy-image-arm.yml b/.github/workflows/deploy-image-arm.yml index 32b60ca6..c9a79946 100644 --- a/.github/workflows/deploy-image-arm.yml +++ b/.github/workflows/deploy-image-arm.yml @@ -52,6 +52,10 @@ jobs: with: images: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest-arm64,enable={{is_default_branch}} + type=ref,event=branch,suffix=-arm64 + type=ref,event=tag,suffix=-arm64 - name: Build and push Docker image uses: docker/build-push-action@v3 @@ -60,7 +64,7 @@ jobs: push: true file: ./docker/Dockerfile.latest platforms: linux/arm64 - tags: ${{ steps.meta.outputs.tags }}-arm64 + tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - uses: actions/delete-package-versions@v4 diff --git a/.github/workflows/deploy-image.yml b/.github/workflows/deploy-image.yml index d82df462..b16f8d35 100644 --- a/.github/workflows/deploy-image.yml +++ b/.github/workflows/deploy-image.yml @@ -49,6 +49,10 @@ jobs: images: | ${{ env.IMAGE_NAME }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=tag - name: Build and push Docker image uses: docker/build-push-action@v3 diff --git a/agent/knowledge/service.py b/agent/knowledge/service.py index a9425385..1db1e021 100644 --- a/agent/knowledge/service.py +++ b/agent/knowledge/service.py @@ -68,41 +68,45 @@ class KnowledgeService: return {"tree": [], "stats": {"pages": 0, "size": 0}, "enabled": conf().get("knowledge", True)} stats = {"pages": 0, "size": 0} - tree = self._scan_dir(self.knowledge_dir, stats) + root_files, tree = self._scan_dir(self.knowledge_dir, stats) return { + "root_files": root_files, "tree": tree, "stats": stats, "enabled": conf().get("knowledge", True), } - def _scan_dir(self, dir_path: str, stats: dict) -> list: - """Recursively scan a directory and return list of group nodes.""" - result = [] + def _scan_dir(self, dir_path: str, stats: dict) -> tuple: + """ + Recursively scan a directory. + + :return: (files, children) where files is a list of .md file dicts + in this directory and children is a list of sub-directory nodes. + """ + files = [] + children = [] for name in sorted(os.listdir(dir_path)): if name.startswith("."): continue full = os.path.join(dir_path, name) if os.path.isdir(full): - files = [] - for fname in sorted(os.listdir(full)): - fpath = os.path.join(full, fname) - if os.path.isfile(fpath) and fname.endswith(".md") and not fname.startswith("."): - size = os.path.getsize(fpath) - stats["pages"] += 1 - stats["size"] += size - title = fname.replace(".md", "") - try: - with open(fpath, "r", encoding="utf-8") as f: - first_line = f.readline().strip() - if first_line.startswith("# "): - title = first_line[2:].strip() - except Exception: - pass - files.append({"name": fname, "title": title, "size": size}) - children = self._scan_dir(full, stats) - result.append({"dir": name, "files": files, "children": children}) - return result + sub_files, sub_children = self._scan_dir(full, stats) + children.append({"dir": name, "files": sub_files, "children": sub_children}) + elif name.endswith(".md"): + size = os.path.getsize(full) + stats["pages"] += 1 + stats["size"] += size + title = name.replace(".md", "") + try: + with open(full, "r", encoding="utf-8") as f: + first_line = f.readline().strip() + if first_line.startswith("# "): + title = first_line[2:].strip() + except Exception: + pass + files.append({"name": name, "title": title, "size": size}) + return files, children # ------------------------------------------------------------------ # read — single file content diff --git a/channel/web/static/js/console.js b/channel/web/static/js/console.js index 61cf9bda..17aa6cc2 100644 --- a/channel/web/static/js/console.js +++ b/channel/web/static/js/console.js @@ -3565,6 +3565,7 @@ navigateTo = function(viewId) { // Knowledge View // ===================================================================== let _knowledgeTreeData = []; +let _knowledgeRootFiles = []; let _knowledgeCurrentFile = null; let _knowledgeGraphLoaded = false; @@ -3582,7 +3583,9 @@ function loadKnowledgeView() { const statsEl = document.getElementById('knowledge-stats'); const tree = data.tree || []; + const rootFiles = data.root_files || []; _knowledgeTreeData = tree; + _knowledgeRootFiles = rootFiles; const stats = data.stats || {}; const totalPages = stats.pages || 0; const sizeStr = stats.size < 1024 ? stats.size + ' B' : (stats.size / 1024).toFixed(1) + ' KB'; @@ -3600,14 +3603,17 @@ function loadKnowledgeView() { emptyEl.classList.add('hidden'); docsPanel.classList.remove('hidden'); - renderKnowledgeTree(tree); + renderKnowledgeTree(tree, rootFiles); // Auto-select the first file (desktop only) if (window.innerWidth >= 768) { - const firstGroup = tree.find(g => g.files && g.files.length > 0); - if (firstGroup) { - const firstFile = firstGroup.files[0]; - openKnowledgeFile(firstGroup.dir + '/' + firstFile.name, firstFile.title); + const firstFile = rootFiles.length > 0 ? rootFiles[0] : null; + const firstGroup = !firstFile ? tree.find(g => g.files && g.files.length > 0) : null; + if (firstFile) { + openKnowledgeFile(firstFile.name, firstFile.title); + } else if (firstGroup) { + const gf = firstGroup.files[0]; + openKnowledgeFile(firstGroup.dir + '/' + gf.name, gf.title); } } else { document.getElementById('knowledge-content-placeholder').classList.add('hidden'); @@ -3616,10 +3622,26 @@ function loadKnowledgeView() { }).catch(() => {}); } -function renderKnowledgeTree(tree, filter) { +function renderKnowledgeTree(tree, rootFilesOrFilter, filter) { const container = document.getElementById('knowledge-tree'); container.innerHTML = ''; - const lowerFilter = (filter || '').toLowerCase(); + let rootFiles, lowerFilter; + if (typeof rootFilesOrFilter === 'string') { + rootFiles = _knowledgeRootFiles; + lowerFilter = (rootFilesOrFilter || '').toLowerCase(); + } else { + rootFiles = rootFilesOrFilter || _knowledgeRootFiles; + lowerFilter = (filter || '').toLowerCase(); + } + (rootFiles || []).forEach(f => { + if (lowerFilter && !f.title.toLowerCase().includes(lowerFilter) && !f.name.toLowerCase().includes(lowerFilter)) return; + const fbtn = document.createElement('button'); + fbtn.className = 'knowledge-tree-file' + (_knowledgeCurrentFile === f.name ? ' active' : ''); + fbtn.dataset.path = f.name; + fbtn.innerHTML = `${escapeHtml(f.title)}`; + fbtn.onclick = () => openKnowledgeFile(f.name, f.title); + container.appendChild(fbtn); + }); _renderKnowledgeGroups(container, tree, '', lowerFilter, 0); } @@ -3684,7 +3706,7 @@ function _countFiles(group) { } function filterKnowledgeTree(query) { - renderKnowledgeTree(_knowledgeTreeData, query); + renderKnowledgeTree(_knowledgeTreeData, _knowledgeRootFiles, query); } function resolveKnowledgePath(currentFilePath, relativeHref) { @@ -3763,6 +3785,9 @@ function bindChatKnowledgeLinks(container) { } function _findKnowledgeFileByName(filename) { + for (const f of _knowledgeRootFiles) { + if (f.name === filename) return { path: f.name, title: f.title }; + } return _searchFileInGroups(_knowledgeTreeData, '', filename); } @@ -4099,7 +4124,10 @@ function initApp() { _restoreSessionPanel(); fetch('/api/knowledge/list').then(r => r.json()).then(data => { - if (data.status === 'success') _knowledgeTreeData = data.tree || []; + if (data.status === 'success') { + _knowledgeTreeData = data.tree || []; + _knowledgeRootFiles = data.root_files || []; + } }).catch(() => {}); fetch('/api/version').then(r => r.json()).then(data => {