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 => {