fix: stabilize local folder AI document workflow

- scope local-folder PageTree revision and document sidebar rendering to fileTreeScope

- preserve projected table/image attrs for local Markdown aggregate fallback

- avoid FileTree restore forced layouts on cold design open

- add API ChatOnly provider runtime and local OCR task handling regressions
This commit is contained in:
lix-2026
2026-06-02 17:17:49 +08:00
parent 610b23d3a5
commit 9f4b5c4d48
27 changed files with 3825 additions and 229 deletions
@@ -1440,12 +1440,9 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
}
}
if (fileTreeState.loadingParents.has(key)) {
return fileTreeState.loadingParents.get(key).then(function(rows) {
fileTreeState.dirtyParents.delete(key);
return isFileTreeRootProjectionParent(parentRelativePath)
? renderFileProjection({ parentRelativePath: parentRelativePath, items: rows })
: patchFileTreeParentChildren(parentRelativePath, rows);
});
// 变更事件可能正好撞上同一 parent 的懒加载请求。
// 旧请求结果不包含刚落盘的文件,不能直接拿它满足本次刷新。
await fileTreeState.loadingParents.get(key).catch(function() { return []; });
}
var projection = await fetchFileTreeProjection(parentRelativePath, { childrenOnly: false });
if (!projection) return false;
@@ -1556,13 +1553,14 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
return '';
}
function setTreeRowExpanded(row, button, expanded) {
function setTreeRowExpanded(row, button, expanded, options) {
row.setAttribute('aria-expanded', expanded ? 'true' : 'false');
var shellMode = row.getAttribute('data-shell-mode') || '';
if (button) {
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
if (shellMode === 'filetree') button.textContent = expanded ? '▾' : '▸';
}
var shouldPersist = !(options && options.persist === false);
if (shellMode === 'page') {
var nodeId = String(row.getAttribute('data-node-id') || '').trim();
if (!nodeId) return;
@@ -1570,18 +1568,20 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
if (expanded) pageState.expandedIds.add(nodeId);
else pageState.expandedIds.delete(nodeId);
pageState.hasUserState = true;
persistSidebarTreeViewState('pagetree');
if (shouldPersist) persistSidebarTreeViewState('pagetree');
return;
}
var relativePath = localFileTreeRelativePathFromRow(row);
if (!relativePath) return;
if (expanded) fileTreeExpandedRelativePaths.add(relativePath);
else fileTreeExpandedRelativePaths.delete(relativePath);
persistFileTreeExpansionState();
persistSidebarTreeViewState('filetree');
if (shouldPersist) {
persistFileTreeExpansionState();
persistSidebarTreeViewState('filetree');
}
}
function markExistingFileTreeChildrenLoaded(row, button) {
function markExistingFileTreeChildrenLoaded(row, button, options) {
if (!(row instanceof HTMLElement)) return false;
var node = row.closest('.tree-node');
if (!node) return false;
@@ -1589,12 +1589,12 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
if (!(children instanceof HTMLElement)) return false;
row.setAttribute('data-filetree-children-loaded', 'true');
children.classList.remove('tree-children--collapsed');
setTreeRowExpanded(row, button, true);
setTreeRowExpanded(row, button, true, options);
syncSidebarFileTreeSelection();
return true;
}
function renderCachedFileTreeChildren(row, button, relativePath) {
function renderCachedFileTreeChildren(row, button, relativePath, options) {
var key = currentFileTreeParentKey(relativePath);
if (fileTreeState.staleParents.has(key) || fileTreeState.dirtyParents.has(key)) return false;
var cachedRows = cachedFileTreeRows(relativePath);
@@ -1612,7 +1612,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
children.replaceChildren(template.content.cloneNode(true));
children.classList.remove('tree-children--collapsed');
row.setAttribute('data-filetree-children-loaded', 'true');
setTreeRowExpanded(row, button, true);
setTreeRowExpanded(row, button, true, options);
syncSidebarFileTreeSelection();
return true;
}
@@ -1641,7 +1641,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
return promise;
}
async function loadFileTreeChildren(row, button) {
async function loadFileTreeChildren(row, button, options) {
if (!(row instanceof HTMLElement)) return false;
if (row.getAttribute('data-shell-mode') !== 'filetree') return false;
if (currentSourceKind() !== 'local_folder') return false;
@@ -1649,21 +1649,21 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
var relativePath = localFileTreeRelativePathFromRow(row);
var key = currentFileTreeParentKey(relativePath);
var stale = fileTreeState.staleParents.has(key) || fileTreeState.dirtyParents.has(key);
if (!stale && markExistingFileTreeChildrenLoaded(row, button)) return true;
if (!stale && relativePath && renderCachedFileTreeChildren(row, button, relativePath)) return true;
if (!stale && markExistingFileTreeChildrenLoaded(row, button, options)) return true;
if (!stale && relativePath && renderCachedFileTreeChildren(row, button, relativePath, options)) return true;
if (row.getAttribute('data-filetree-children-loaded') === 'true') return false;
var rootUri = currentRootUri();
if (!rootUri || !relativePath) return false;
setTreeRowExpanded(row, button, true);
setTreeRowExpanded(row, button, true, options);
row.setAttribute('data-filetree-children-loading', 'true');
try {
var rows = await getFileTreeChildren(relativePath);
if (!rows.length) {
row.setAttribute('data-filetree-children-loaded', 'true');
setTreeRowExpanded(row, button, true);
setTreeRowExpanded(row, button, true, options);
return true;
}
return renderCachedFileTreeChildren(row, button, relativePath);
return renderCachedFileTreeChildren(row, button, relativePath, options);
} catch (error) {
setTreeLiveApplyError(error && error.message ? error.message : '文件树子目录加载失败');
return false;
@@ -1731,6 +1731,15 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
setTreeRowExpanded(row, button, !collapsed);
}
function shouldAutoRestoreFileTreeExpansion(relativePath) {
var normalized = normalizeFileTreeRelativePath(relativePath);
if (!normalized) return false;
var scope = normalizeFileTreeRelativePath(currentFileTreeScope());
if (!scope) return normalized.indexOf('/') < 0;
if (normalized.indexOf(scope + '/') !== 0) return false;
return normalized.slice(scope.length + 1).indexOf('/') < 0;
}
function restorePersistedFileTreeExpansionState() {
if (currentSourceKind() !== 'local_folder') return false;
ensureFileTreeLazyCacheScope();
@@ -1741,17 +1750,20 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
if (String(row.getAttribute('data-row-kind') || '').trim() !== 'folder') return;
var relativePath = localFileTreeRelativePathFromRow(row);
if (!relativePath || !fileTreeExpandedRelativePaths.has(relativePath)) return;
// 首屏只自动恢复当前 scope 的直接子目录。历史 view-state 可能记录了
// 多层 design 目录展开;一次性递归恢复会长时间占满浏览器主线程。
if (!shouldAutoRestoreFileTreeExpansion(relativePath)) return;
var button = row.querySelector('[data-rust-action="toggle"]');
if (markExistingFileTreeChildrenLoaded(row, button)) {
if (markExistingFileTreeChildrenLoaded(row, button, { persist: false })) {
restored = true;
return;
}
if (row.getAttribute('data-filetree-children-loaded') === 'true') {
setTreeRowExpanded(row, button, true);
setTreeRowExpanded(row, button, true, { persist: false });
restored = true;
return;
}
void loadFileTreeChildren(row, button).then(function(loaded) {
void loadFileTreeChildren(row, button, { persist: false }).then(function(loaded) {
if (loaded) restorePersistedFileTreeExpansionState();
});
restored = true;