mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
feat: show root-level files (index.md, log.md) in knowledge tree
This commit is contained in:
6
.github/workflows/deploy-image-arm.yml
vendored
6
.github/workflows/deploy-image-arm.yml
vendored
@@ -52,6 +52,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
${{ 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
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
@@ -60,7 +64,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
file: ./docker/Dockerfile.latest
|
file: ./docker/Dockerfile.latest
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
tags: ${{ steps.meta.outputs.tags }}-arm64
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
- uses: actions/delete-package-versions@v4
|
- uses: actions/delete-package-versions@v4
|
||||||
|
|||||||
4
.github/workflows/deploy-image.yml
vendored
4
.github/workflows/deploy-image.yml
vendored
@@ -49,6 +49,10 @@ jobs:
|
|||||||
images: |
|
images: |
|
||||||
${{ env.IMAGE_NAME }}
|
${{ env.IMAGE_NAME }}
|
||||||
${{ env.REGISTRY }}/${{ 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
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
|
|||||||
@@ -68,41 +68,45 @@ class KnowledgeService:
|
|||||||
return {"tree": [], "stats": {"pages": 0, "size": 0}, "enabled": conf().get("knowledge", True)}
|
return {"tree": [], "stats": {"pages": 0, "size": 0}, "enabled": conf().get("knowledge", True)}
|
||||||
|
|
||||||
stats = {"pages": 0, "size": 0}
|
stats = {"pages": 0, "size": 0}
|
||||||
tree = self._scan_dir(self.knowledge_dir, stats)
|
root_files, tree = self._scan_dir(self.knowledge_dir, stats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
"root_files": root_files,
|
||||||
"tree": tree,
|
"tree": tree,
|
||||||
"stats": stats,
|
"stats": stats,
|
||||||
"enabled": conf().get("knowledge", True),
|
"enabled": conf().get("knowledge", True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _scan_dir(self, dir_path: str, stats: dict) -> list:
|
def _scan_dir(self, dir_path: str, stats: dict) -> tuple:
|
||||||
"""Recursively scan a directory and return list of group nodes."""
|
"""
|
||||||
result = []
|
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)):
|
for name in sorted(os.listdir(dir_path)):
|
||||||
if name.startswith("."):
|
if name.startswith("."):
|
||||||
continue
|
continue
|
||||||
full = os.path.join(dir_path, name)
|
full = os.path.join(dir_path, name)
|
||||||
if os.path.isdir(full):
|
if os.path.isdir(full):
|
||||||
files = []
|
sub_files, sub_children = self._scan_dir(full, stats)
|
||||||
for fname in sorted(os.listdir(full)):
|
children.append({"dir": name, "files": sub_files, "children": sub_children})
|
||||||
fpath = os.path.join(full, fname)
|
elif name.endswith(".md"):
|
||||||
if os.path.isfile(fpath) and fname.endswith(".md") and not fname.startswith("."):
|
size = os.path.getsize(full)
|
||||||
size = os.path.getsize(fpath)
|
stats["pages"] += 1
|
||||||
stats["pages"] += 1
|
stats["size"] += size
|
||||||
stats["size"] += size
|
title = name.replace(".md", "")
|
||||||
title = fname.replace(".md", "")
|
try:
|
||||||
try:
|
with open(full, "r", encoding="utf-8") as f:
|
||||||
with open(fpath, "r", encoding="utf-8") as f:
|
first_line = f.readline().strip()
|
||||||
first_line = f.readline().strip()
|
if first_line.startswith("# "):
|
||||||
if first_line.startswith("# "):
|
title = first_line[2:].strip()
|
||||||
title = first_line[2:].strip()
|
except Exception:
|
||||||
except Exception:
|
pass
|
||||||
pass
|
files.append({"name": name, "title": title, "size": size})
|
||||||
files.append({"name": fname, "title": title, "size": size})
|
return files, children
|
||||||
children = self._scan_dir(full, stats)
|
|
||||||
result.append({"dir": name, "files": files, "children": children})
|
|
||||||
return result
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# read — single file content
|
# read — single file content
|
||||||
|
|||||||
@@ -3565,6 +3565,7 @@ navigateTo = function(viewId) {
|
|||||||
// Knowledge View
|
// Knowledge View
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
let _knowledgeTreeData = [];
|
let _knowledgeTreeData = [];
|
||||||
|
let _knowledgeRootFiles = [];
|
||||||
let _knowledgeCurrentFile = null;
|
let _knowledgeCurrentFile = null;
|
||||||
let _knowledgeGraphLoaded = false;
|
let _knowledgeGraphLoaded = false;
|
||||||
|
|
||||||
@@ -3582,7 +3583,9 @@ function loadKnowledgeView() {
|
|||||||
const statsEl = document.getElementById('knowledge-stats');
|
const statsEl = document.getElementById('knowledge-stats');
|
||||||
|
|
||||||
const tree = data.tree || [];
|
const tree = data.tree || [];
|
||||||
|
const rootFiles = data.root_files || [];
|
||||||
_knowledgeTreeData = tree;
|
_knowledgeTreeData = tree;
|
||||||
|
_knowledgeRootFiles = rootFiles;
|
||||||
const stats = data.stats || {};
|
const stats = data.stats || {};
|
||||||
const totalPages = stats.pages || 0;
|
const totalPages = stats.pages || 0;
|
||||||
const sizeStr = stats.size < 1024 ? stats.size + ' B' : (stats.size / 1024).toFixed(1) + ' KB';
|
const sizeStr = stats.size < 1024 ? stats.size + ' B' : (stats.size / 1024).toFixed(1) + ' KB';
|
||||||
@@ -3600,14 +3603,17 @@ function loadKnowledgeView() {
|
|||||||
emptyEl.classList.add('hidden');
|
emptyEl.classList.add('hidden');
|
||||||
docsPanel.classList.remove('hidden');
|
docsPanel.classList.remove('hidden');
|
||||||
|
|
||||||
renderKnowledgeTree(tree);
|
renderKnowledgeTree(tree, rootFiles);
|
||||||
|
|
||||||
// Auto-select the first file (desktop only)
|
// Auto-select the first file (desktop only)
|
||||||
if (window.innerWidth >= 768) {
|
if (window.innerWidth >= 768) {
|
||||||
const firstGroup = tree.find(g => g.files && g.files.length > 0);
|
const firstFile = rootFiles.length > 0 ? rootFiles[0] : null;
|
||||||
if (firstGroup) {
|
const firstGroup = !firstFile ? tree.find(g => g.files && g.files.length > 0) : null;
|
||||||
const firstFile = firstGroup.files[0];
|
if (firstFile) {
|
||||||
openKnowledgeFile(firstGroup.dir + '/' + firstFile.name, firstFile.title);
|
openKnowledgeFile(firstFile.name, firstFile.title);
|
||||||
|
} else if (firstGroup) {
|
||||||
|
const gf = firstGroup.files[0];
|
||||||
|
openKnowledgeFile(firstGroup.dir + '/' + gf.name, gf.title);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('knowledge-content-placeholder').classList.add('hidden');
|
document.getElementById('knowledge-content-placeholder').classList.add('hidden');
|
||||||
@@ -3616,10 +3622,26 @@ function loadKnowledgeView() {
|
|||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderKnowledgeTree(tree, filter) {
|
function renderKnowledgeTree(tree, rootFilesOrFilter, filter) {
|
||||||
const container = document.getElementById('knowledge-tree');
|
const container = document.getElementById('knowledge-tree');
|
||||||
container.innerHTML = '';
|
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 = `<i class="fas fa-file-lines text-[10px] text-slate-400"></i><span class="truncate">${escapeHtml(f.title)}</span>`;
|
||||||
|
fbtn.onclick = () => openKnowledgeFile(f.name, f.title);
|
||||||
|
container.appendChild(fbtn);
|
||||||
|
});
|
||||||
_renderKnowledgeGroups(container, tree, '', lowerFilter, 0);
|
_renderKnowledgeGroups(container, tree, '', lowerFilter, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3684,7 +3706,7 @@ function _countFiles(group) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function filterKnowledgeTree(query) {
|
function filterKnowledgeTree(query) {
|
||||||
renderKnowledgeTree(_knowledgeTreeData, query);
|
renderKnowledgeTree(_knowledgeTreeData, _knowledgeRootFiles, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveKnowledgePath(currentFilePath, relativeHref) {
|
function resolveKnowledgePath(currentFilePath, relativeHref) {
|
||||||
@@ -3763,6 +3785,9 @@ function bindChatKnowledgeLinks(container) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _findKnowledgeFileByName(filename) {
|
function _findKnowledgeFileByName(filename) {
|
||||||
|
for (const f of _knowledgeRootFiles) {
|
||||||
|
if (f.name === filename) return { path: f.name, title: f.title };
|
||||||
|
}
|
||||||
return _searchFileInGroups(_knowledgeTreeData, '', filename);
|
return _searchFileInGroups(_knowledgeTreeData, '', filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4099,7 +4124,10 @@ function initApp() {
|
|||||||
_restoreSessionPanel();
|
_restoreSessionPanel();
|
||||||
|
|
||||||
fetch('/api/knowledge/list').then(r => r.json()).then(data => {
|
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(() => {});
|
}).catch(() => {});
|
||||||
|
|
||||||
fetch('/api/version').then(r => r.json()).then(data => {
|
fetch('/api/version').then(r => r.json()).then(data => {
|
||||||
|
|||||||
Reference in New Issue
Block a user