chore: checkpoint turso and ai runtime work
This commit is contained in:
@@ -415,7 +415,7 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
|| String(detail && (detail.localRelativePath || detail.path || detail.title) || '').trim();
|
||||
var rootUri = knowledgeRagRootUri(detail, trigger);
|
||||
if (!sourceRootRelativePath || !rootUri) {
|
||||
throw new Error('缺少资料库来源或 rootUri');
|
||||
throw new Error('缺少知识库来源或 rootUri');
|
||||
}
|
||||
var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim();
|
||||
document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', 'running');
|
||||
@@ -426,12 +426,14 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
body: JSON.stringify({
|
||||
workspaceId: workspaceId,
|
||||
rootUri: rootUri,
|
||||
sources: [{ sourcePath: sourceRootRelativePath }]
|
||||
sources: [{ sourcePath: sourceRootRelativePath }],
|
||||
knowledgeBaseId: String(detail && detail.kbId || '').trim() || undefined,
|
||||
providerKnowledgeBaseId: String(detail && (detail.providerKbId || detail.kbId) || '').trim() || undefined
|
||||
})
|
||||
});
|
||||
var payload = await response.json().catch(function() { return null; });
|
||||
if (!response.ok || !payload || payload.ok === false) {
|
||||
var message = payload && payload.message ? payload.message : '资料库索引失败';
|
||||
var message = payload && payload.message ? payload.message : '知识库索引失败';
|
||||
document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', 'failed');
|
||||
throw new Error(message);
|
||||
}
|
||||
@@ -450,12 +452,30 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
recordFileTreeActionStatus('pending', detail);
|
||||
void ingestKnowledgeRagForDetail(detail, trigger).then(function() {
|
||||
recordFileTreeActionStatus('done', detail);
|
||||
if (window.mnote && window.mnote.toast) {
|
||||
window.mnote.toast('已加入知识库', { kind: 'success' });
|
||||
}
|
||||
}).catch(function(error) {
|
||||
var msg = error && error.message ? error.message : '知识库索引失败';
|
||||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||||
window.alert(error && error.message ? error.message : '资料库索引失败');
|
||||
if (window.mnote && window.mnote.toast) {
|
||||
window.mnote.toast(msg, { kind: 'error' });
|
||||
} else {
|
||||
window.alert(msg);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action === 'open-knowledge-page') {
|
||||
var knowledgeUrl = '/knowledge';
|
||||
if (typeof window.open === 'function') {
|
||||
window.open(knowledgeUrl, '_blank', 'noopener,noreferrer');
|
||||
} else {
|
||||
window.location.href = knowledgeUrl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (detail.contextKind === 'attachment') {
|
||||
if (action === 'copy-link') {
|
||||
void copyTreeContextValue(detail.href || '', 'attachment-copy-link');
|
||||
@@ -683,6 +703,148 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
menu.appendChild(sep);
|
||||
return;
|
||||
}
|
||||
// Submenu root: render parent button with child flyout
|
||||
if (item.children && Array.isArray(item.children) && item.children.length > 0) {
|
||||
if (typeof injectKnowledgeSubmenuStyles === 'function') injectKnowledgeSubmenuStyles();
|
||||
var rootBtn = document.createElement('button');
|
||||
rootBtn.type = 'button';
|
||||
rootBtn.className = 'mnote-tree-context-menu__item mnote-tree-context-menu__item--submenu-root';
|
||||
rootBtn.setAttribute('role', 'menuitem');
|
||||
rootBtn.setAttribute('data-action', item.action || 'knowledge-rag-root');
|
||||
rootBtn.setAttribute('data-command-id', item.commandId || item.action || 'knowledge-rag-root');
|
||||
if (item.when) rootBtn.setAttribute('data-command-when', item.when);
|
||||
if (item.danger || item.destructive) rootBtn.setAttribute('data-destructive', 'true');
|
||||
if (item.requiresApproval) rootBtn.setAttribute('data-requires-approval', 'true');
|
||||
rootBtn.disabled = item.disabled === true;
|
||||
if (item.title) rootBtn.title = item.title;
|
||||
if (item.disabled === true && item.title) rootBtn.setAttribute('data-disabled-reason', item.title);
|
||||
var rootIcon = document.createElement('span');
|
||||
rootIcon.className = 'material-symbols-outlined mnote-tree-context-menu__icon';
|
||||
rootIcon.setAttribute('aria-hidden', 'true');
|
||||
rootIcon.setAttribute('data-icon', item.icon || 'radio_button_unchecked');
|
||||
var rootLabel = document.createElement('span');
|
||||
rootLabel.className = 'mnote-tree-context-menu__label';
|
||||
rootLabel.textContent = item.label;
|
||||
var arrow = document.createElement('span');
|
||||
arrow.className = 'mnote-tree-context-menu__submenu-arrow';
|
||||
arrow.textContent = '▶';
|
||||
arrow.setAttribute('aria-hidden', 'true');
|
||||
rootBtn.appendChild(rootIcon);
|
||||
rootBtn.appendChild(rootLabel);
|
||||
rootBtn.appendChild(arrow);
|
||||
var submenu = document.createElement('div');
|
||||
submenu.className = 'mnote-tree-context-menu__submenu';
|
||||
submenu.setAttribute('data-testid', 'mnote-knowledge-submenu');
|
||||
submenu.setAttribute('role', 'menu');
|
||||
function renderSubmenuChildren(children) {
|
||||
submenu.textContent = '';
|
||||
children.forEach(function(child) {
|
||||
if (child.separator) {
|
||||
var childSep = document.createElement('div');
|
||||
childSep.className = 'mnote-tree-context-menu__separator';
|
||||
childSep.setAttribute('role', 'separator');
|
||||
submenu.appendChild(childSep);
|
||||
return;
|
||||
}
|
||||
var childBtn = document.createElement('button');
|
||||
childBtn.type = 'button';
|
||||
childBtn.className = child.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item';
|
||||
if (child.heading) childBtn.className += ' mnote-tree-context-menu__item--heading';
|
||||
childBtn.setAttribute('role', 'menuitem');
|
||||
childBtn.setAttribute('data-action', child.action || '');
|
||||
childBtn.setAttribute('data-command-id', child.commandId || child.action || '');
|
||||
if (child.when) childBtn.setAttribute('data-command-when', child.when);
|
||||
childBtn.disabled = child.disabled === true || child.heading === true;
|
||||
if (child.title) childBtn.title = child.title;
|
||||
if (child.disabled === true && child.title) childBtn.setAttribute('data-disabled-reason', child.title);
|
||||
var childIcon = document.createElement('span');
|
||||
childIcon.className = 'material-symbols-outlined mnote-tree-context-menu__icon';
|
||||
childIcon.setAttribute('aria-hidden', 'true');
|
||||
childIcon.setAttribute('data-icon', child.icon || 'radio_button_unchecked');
|
||||
var childLabel = document.createElement('span');
|
||||
childLabel.className = 'mnote-tree-context-menu__label';
|
||||
childLabel.textContent = child.label;
|
||||
childBtn.appendChild(childIcon);
|
||||
childBtn.appendChild(childLabel);
|
||||
if (child.shortcut) {
|
||||
var childShortcut = document.createElement('span');
|
||||
childShortcut.className = 'mnote-tree-context-menu__shortcut';
|
||||
childShortcut.textContent = child.shortcut;
|
||||
childBtn.appendChild(childShortcut);
|
||||
}
|
||||
var childDetail = child.kbId ? Object.assign({}, detail, { kbId: child.kbId, providerKbId: child.providerKbId || child.kbId }) : detail;
|
||||
(function(act, actDetail, actTrigger) {
|
||||
childBtn.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (!act || childBtn.disabled) return;
|
||||
handleTreeContextMenuAction(act, actDetail, actTrigger);
|
||||
});
|
||||
})(child.action, childDetail, trigger);
|
||||
submenu.appendChild(childBtn);
|
||||
});
|
||||
}
|
||||
renderSubmenuChildren(item.children);
|
||||
rootBtn.appendChild(submenu);
|
||||
var submenuShown = false;
|
||||
var submenuHideTimer = null;
|
||||
var lastSubmenuPointerY = 0;
|
||||
function knowledgeSubmenuPosition(pointerY) {
|
||||
var btnRect = rootBtn.getBoundingClientRect();
|
||||
submenu.style.display = 'block';
|
||||
submenu.style.visibility = 'hidden';
|
||||
var smRect = submenu.getBoundingClientRect();
|
||||
var leftPos = btnRect.right + 4;
|
||||
var anchorY = Number(pointerY || lastSubmenuPointerY || 0);
|
||||
var topPos = anchorY > 0 ? anchorY - 18 : btnRect.top;
|
||||
if (leftPos + smRect.width > window.innerWidth - 8) {
|
||||
leftPos = Math.max(8, btnRect.left - smRect.width - 4);
|
||||
}
|
||||
if (topPos + smRect.height > window.innerHeight - 8) {
|
||||
topPos = Math.max(8, window.innerHeight - smRect.height - 8);
|
||||
}
|
||||
submenu.style.left = leftPos + 'px';
|
||||
submenu.style.top = topPos + 'px';
|
||||
submenu.style.visibility = 'visible';
|
||||
}
|
||||
function knowledgeSubmenuShow(pointerY) {
|
||||
if (Number(pointerY || 0) > 0) lastSubmenuPointerY = pointerY;
|
||||
if (submenuHideTimer) { window.clearTimeout(submenuHideTimer); submenuHideTimer = null; }
|
||||
if (!submenuShown) { knowledgeSubmenuPosition(pointerY); submenuShown = true; rootBtn.classList.add('mnote-tree-context-menu__item--submenu-open'); }
|
||||
}
|
||||
function knowledgeSubmenuScheduleHide() {
|
||||
if (submenuHideTimer) window.clearTimeout(submenuHideTimer);
|
||||
submenuHideTimer = window.setTimeout(function() {
|
||||
if (submenuShown) { submenu.style.display = 'none'; submenuShown = false; rootBtn.classList.remove('mnote-tree-context-menu__item--submenu-open'); }
|
||||
submenuHideTimer = null;
|
||||
}, 150);
|
||||
}
|
||||
rootBtn.addEventListener('mouseenter', function(event) { if (!rootBtn.disabled) knowledgeSubmenuShow(event.clientY); });
|
||||
rootBtn.addEventListener('mousemove', function(event) {
|
||||
if (rootBtn.disabled || !submenuShown) return;
|
||||
lastSubmenuPointerY = event.clientY;
|
||||
});
|
||||
rootBtn.addEventListener('mouseleave', function(event) {
|
||||
if (event.relatedTarget && (submenu.contains(event.relatedTarget) || rootBtn.contains(event.relatedTarget))) return;
|
||||
knowledgeSubmenuScheduleHide();
|
||||
});
|
||||
submenu.addEventListener('mouseenter', function() {
|
||||
if (submenuHideTimer) { window.clearTimeout(submenuHideTimer); submenuHideTimer = null; }
|
||||
});
|
||||
submenu.addEventListener('mouseleave', function(event) {
|
||||
if (event.relatedTarget && (rootBtn.contains(event.relatedTarget) || submenu.contains(event.relatedTarget))) return;
|
||||
knowledgeSubmenuScheduleHide();
|
||||
});
|
||||
if (typeof item.loadChildren === 'function') {
|
||||
item.loadChildren().then(function(children) {
|
||||
if (!Array.isArray(children) || children.length === 0) return;
|
||||
renderSubmenuChildren(children);
|
||||
if (submenuShown) knowledgeSubmenuPosition(lastSubmenuPointerY);
|
||||
}).catch(function() {});
|
||||
}
|
||||
menu.appendChild(rootBtn);
|
||||
return;
|
||||
}
|
||||
var button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = item.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item';
|
||||
@@ -718,6 +880,106 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
menu.appendChild(button);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── 知识库二级菜单 CSS 注入(仅一次) ──
|
||||
var __mnoteKnowledgeSubmenuStylesInjected = false;
|
||||
function injectKnowledgeSubmenuStyles() {
|
||||
if (__mnoteKnowledgeSubmenuStylesInjected) return;
|
||||
__mnoteKnowledgeSubmenuStylesInjected = true;
|
||||
var style = document.createElement('style');
|
||||
style.id = 'mnote-knowledge-submenu-styles';
|
||||
style.textContent = '.mnote-tree-context-menu__item--submenu-root{position:relative}.mnote-tree-context-menu__submenu{position:fixed;z-index:calc(var(--wolai-z-context-menu,9999) + 1);width:240px;min-width:220px;max-width:min(340px,calc(100vw - 16px));padding:6px;border:1px solid rgba(27,28,28,.08);border-radius:6px;background:#FFF;box-shadow:0 8px 20px rgba(27,28,28,.12);backdrop-filter:blur(24px);color:var(--atelier-text);display:none}.mnote-tree-context-menu__submenu-arrow{margin-left:auto;font-size:11px;color:#6D6A65;padding-left:12px}.mnote-tree-context-menu__item--submenu-open{background:#F4F3F3}.mnote-tree-context-menu__item--heading{font-size:12px;color:#6D6A65;background:transparent!important;cursor:default}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// ── 知识库列表获取:只读 MNote 后端 registry/status,避免前端直连 WeKnora service key。 ──
|
||||
function fetchKnowledgeBaseList(detail) {
|
||||
var rootUri = knowledgeRagRootUri(detail, null);
|
||||
if (!rootUri) return Promise.resolve([]);
|
||||
var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim();
|
||||
var sourcePath = knowledgeRagSourceRelativePath(detail);
|
||||
var statusUrl = '/api/knowledge-rag/status?rootUri=' + encodeURIComponent(rootUri);
|
||||
if (workspaceId) statusUrl += '&workspaceId=' + encodeURIComponent(workspaceId);
|
||||
return fetch(statusUrl, { credentials: 'include', headers: { accept: 'application/json' } })
|
||||
.then(function(response) { return response.ok ? response.json() : null; })
|
||||
.then(function(payload) {
|
||||
// 构建 knowledgeBase → 已索引 sourcePath 快速查找
|
||||
var indexedByKb = {};
|
||||
var registry = payload && payload.registry;
|
||||
if (registry && Array.isArray(registry.entries) && sourcePath) {
|
||||
registry.entries.forEach(function(entry) {
|
||||
var entryKbId = String(entry.providerKnowledgeBaseId || entry.provider_kb_id || '').trim();
|
||||
var entryPath = String(entry.sourceRootRelativePath || entry.source_root_relative_path || '').trim();
|
||||
if (entryKbId && entryPath && entry.indexedAtMs && entryPath === sourcePath) {
|
||||
if (!indexedByKb[entryKbId]) indexedByKb[entryKbId] = {};
|
||||
indexedByKb[entryKbId][entryPath] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
var bases = payload && payload.knowledgeBases && Array.isArray(payload.knowledgeBases.bases)
|
||||
? payload.knowledgeBases.bases
|
||||
: [];
|
||||
return bases.filter(function(base) {
|
||||
return base && base.canWrite !== false && (base.providerKbId || base.provider_kb_id || base.baseId || base.base_id);
|
||||
}).map(function(base) {
|
||||
var providerKbId = String(base.providerKbId || base.provider_kb_id || '').trim();
|
||||
var baseId = String(base.baseId || base.base_id || providerKbId).trim();
|
||||
var kbId = providerKbId || baseId;
|
||||
return {
|
||||
id: baseId,
|
||||
providerKbId: kbId,
|
||||
name: String(base.name || baseId || providerKbId || '未命名知识库').trim(),
|
||||
sourceCount: Number(base.sourceCount || base.source_count || 0) || 0,
|
||||
sourceAlreadyIndexed: Boolean(sourcePath && indexedByKb[kbId] && indexedByKb[kbId][sourcePath])
|
||||
};
|
||||
});
|
||||
}).catch(function() { return []; });
|
||||
}
|
||||
|
||||
function buildKnowledgeRagSubmenuChildren(detail, writableBases) {
|
||||
var children = [
|
||||
{ action: 'knowledge-rag-index', icon: 'travel_explore', label: '索引到默认知识库', when: '!workspace.readonly' },
|
||||
{ separator: true },
|
||||
{ heading: true, icon: 'history', label: '最近使用知识库' }
|
||||
];
|
||||
if (writableBases && writableBases.length > 0) {
|
||||
writableBases.slice(0, 3).forEach(function(base) {
|
||||
var indexed = base.sourceAlreadyIndexed;
|
||||
children.push({
|
||||
action: 'knowledge-rag-index',
|
||||
icon: indexed ? 'check_circle' : 'database',
|
||||
label: (indexed ? '✓ ' : '') + base.name,
|
||||
kbId: base.id,
|
||||
providerKbId: base.providerKbId,
|
||||
title: indexed ? '重新索引' : (base.sourceCount ? ('已有 ' + base.sourceCount + ' 个来源') : '加入此知识库'),
|
||||
when: '!workspace.readonly'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
children.push({ action: '', icon: 'database', label: '暂无可写知识库', disabled: true });
|
||||
}
|
||||
children.push({ heading: true, icon: 'folder_managed', label: '当前 workspace 知识库' });
|
||||
(writableBases || []).slice(3).forEach(function(base) {
|
||||
var indexed = base.sourceAlreadyIndexed;
|
||||
children.push({
|
||||
action: 'knowledge-rag-index',
|
||||
icon: indexed ? 'check_circle' : 'database',
|
||||
label: (indexed ? '✓ ' : '') + base.name,
|
||||
kbId: base.id,
|
||||
providerKbId: base.providerKbId,
|
||||
title: indexed ? '重新索引' : (base.sourceCount ? ('已有 ' + base.sourceCount + ' 个来源') : '加入此知识库'),
|
||||
when: '!workspace.readonly'
|
||||
});
|
||||
});
|
||||
children.push(
|
||||
{ separator: true },
|
||||
{ action: 'open-knowledge-page', icon: 'add', label: '新建知识库...' },
|
||||
{ action: 'open-knowledge-page', icon: 'settings', label: '管理知识库...' }
|
||||
);
|
||||
return children;
|
||||
}
|
||||
|
||||
// ── CommandContext 启用态辅助:与 Rust core-protocol CommandContext 保持同一口径 ──
|
||||
|
||||
function currentOpenEditorsSnapshotForCommandContext() {
|
||||
@@ -979,7 +1241,13 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' }
|
||||
];
|
||||
if (currentSourceKind() === 'local_folder' && detail.rowKind !== 'index' && knowledgeRagSupported) {
|
||||
var ragItem = { action: 'knowledge-rag-index', icon: 'travel_explore', label: '加入资料库索引', when: '!workspace.readonly' };
|
||||
var ragChildren = buildKnowledgeRagSubmenuChildren(detail, []);
|
||||
var ragItem = { action: 'knowledge-rag-root', icon: 'travel_explore', label: '加入知识库', children: ragChildren, when: '!workspace.readonly' };
|
||||
ragItem.loadChildren = function() {
|
||||
return fetchKnowledgeBaseList(detail).then(function(bases) {
|
||||
return buildKnowledgeRagSubmenuChildren(detail, bases);
|
||||
});
|
||||
};
|
||||
var insertAt = isAttachment ? 11 : isAsset ? 3 : 3;
|
||||
if (insertAt >= 0) items.splice(insertAt, 0, ragItem);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user