mirror of
https://github.com/flash555588/ai-model-workbench.git
synced 2026-07-22 06:56:38 +00:00
feat: workspace layout with sidebar and resize handles
Add a track-based workspace framework around the original workbench panel: - Main preview area + right sidebar with action icons (focus, disassembly, reset, generate-note, open-index) - Draggable resize handles for sidebar width and bottom panel height - Keep the original bottom workbench panel design: overview on the left, knowledge actions and registered part matches on the right - Mobile responsive: sidebar becomes a horizontal scrollable row This keeps the familiar two-column workbench panel while adding a proper resizable workspace frame so the preview area is no longer squeezed by the panel.
This commit is contained in:
parent
3a57da77e3
commit
355c2e1eb4
7 changed files with 457 additions and 579 deletions
|
|
@ -7,12 +7,6 @@
|
|||
- Preserve STEP XDE component labels during CAD conversion, exporting PCB/CAD components as individual GLB component meshes with `extras.ai3d` identity metadata.
|
||||
- Remove the direct-workbench explode controls and reorganize the panel around model status, knowledge actions, and registered part matches.
|
||||
- Preserve source extensions such as STEP, FBX, 3MF, and DAE in analysis records so registered part matching can link reused components across converted model formats.
|
||||
- Replace the legacy bottom workbench panel with a track-based workspace layout: main preview area gets full remaining space without being squeezed by the panel.
|
||||
- Add a right sidebar with icon buttons for focus, disassembly, reset, annotation, generate-note, and open-index actions.
|
||||
- Add draggable resize handles for sidebar width and bottom panel height (like IDE split panes).
|
||||
- Organize the bottom information panel into Overview / Matches / Knowledge tabs to reduce visual clutter.
|
||||
- Add tab translation keys for Overview, Matches, and Knowledge in both English and Chinese.
|
||||
- Make the workspace layout responsive: sidebar becomes a horizontal scrollable row on narrow screens.
|
||||
|
||||
## 0.4.3
|
||||
|
||||
|
|
|
|||
414
main.js
414
main.js
File diff suppressed because one or more lines are too long
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"asset": { "version": "2.0", "generator": "test" },
|
||||
"scene": 0,
|
||||
"scenes": [{ "nodes": [0] }],
|
||||
"nodes": [{ "mesh": 0 }],
|
||||
"meshes": [{
|
||||
"primitives": [{
|
||||
"attributes": { "POSITION": 0 },
|
||||
"indices": 1
|
||||
}]
|
||||
}],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"componentType": 5126,
|
||||
"count": 3,
|
||||
"type": "VEC3",
|
||||
"max": [1, 1, 0],
|
||||
"min": [0, 0, 0]
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"componentType": 5123,
|
||||
"count": 3,
|
||||
"type": "SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{ "buffer": 0, "byteOffset": 0, "byteLength": 36 },
|
||||
{ "buffer": 0, "byteOffset": 36, "byteLength": 6 }
|
||||
],
|
||||
"buffers": [{
|
||||
"uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAICPQAAAAAAAAAAAAICPQAAAAAAAAAAAAAAAAAAAAAAPAAABAAIAAw==",
|
||||
"byteLength": 42
|
||||
}]
|
||||
}
|
||||
163
src/i18n/en.ts
163
src/i18n/en.ts
|
|
@ -8,68 +8,68 @@ export const en = {
|
|||
"settings.converterMenu.desc": "Closed by default. Open this section only when you want to manually enable an optional local conversion route.",
|
||||
"settings.environmentInspector": "Environment inspector",
|
||||
"settings.environmentInspector.desc": "Closed by default. Open this section when you need to configure command paths or run environment checks.",
|
||||
"settings.paths": "Converter paths",
|
||||
"settings.diagnostics": "Converter command diagnostics",
|
||||
"settings.performance": "Performance & display",
|
||||
"settings.mobileSupport": "Mobile support",
|
||||
"settings.knowledgeGeneration": "Knowledge generation",
|
||||
"settings.paths": "Converter paths",
|
||||
"settings.diagnostics": "Converter command diagnostics",
|
||||
"settings.performance": "Performance & display",
|
||||
"settings.mobileSupport": "Mobile support",
|
||||
"settings.knowledgeGeneration": "Knowledge generation",
|
||||
|
||||
// Folders
|
||||
"settings.sourceModelFolder": "Source model folder",
|
||||
"settings.sourceModelFolder.desc": "Vault folder where source model files are stored.",
|
||||
"settings.reportFolder": "Report folder",
|
||||
"settings.reportFolder.desc": "Vault folder where generated knowledge notes are saved.",
|
||||
"settings.partFolder": "Part notes folder",
|
||||
"settings.partFolder.desc": "Vault folder where generated part note drafts are saved.",
|
||||
"settings.snapshotFolder": "Snapshot folder",
|
||||
"settings.reportFolder": "Report folder",
|
||||
"settings.reportFolder.desc": "Vault folder where generated knowledge notes are saved.",
|
||||
"settings.partFolder": "Part notes folder",
|
||||
"settings.partFolder.desc": "Vault folder where generated part note drafts are saved.",
|
||||
"settings.snapshotFolder": "Snapshot folder",
|
||||
"settings.snapshotFolder.desc": "Vault folder where exported snapshots are saved.",
|
||||
|
||||
// Behavior
|
||||
"settings.autoGenerateKnowledgeNotes": "Auto-generate knowledge notes",
|
||||
"settings.autoGenerateKnowledgeNotes.desc": "Reserved for a future automation flow. For now, generate notes manually from the command palette.",
|
||||
"settings.annotationPreviewMode": "Annotation preview mode",
|
||||
"settings.annotationPreviewMode.desc": "Choose how bound note previews render in annotation popovers and the editor.",
|
||||
"settings.annotationPreviewMode.plainText": "Plain text",
|
||||
"settings.annotationPreviewMode.markdown": "Markdown",
|
||||
"settings.annotationDisplayMode": "Annotation display mode",
|
||||
"settings.annotationDisplayMode.desc": "Choose how bookmark pins appear over the model.",
|
||||
"settings.annotationDisplayMode.snippet": "Full snippet",
|
||||
"settings.annotationDisplayMode.surface": "Single surface",
|
||||
"settings.annotationDisplayMode.dot": "Single dot",
|
||||
"settings.previewRendererRollout": "Preview compatibility mode",
|
||||
"settings.annotationPreviewMode.desc": "Choose how bound note previews render in annotation popovers and the editor.",
|
||||
"settings.annotationPreviewMode.plainText": "Plain text",
|
||||
"settings.annotationPreviewMode.markdown": "Markdown",
|
||||
"settings.annotationDisplayMode": "Annotation display mode",
|
||||
"settings.annotationDisplayMode.desc": "Choose how bookmark pins appear over the model.",
|
||||
"settings.annotationDisplayMode.snippet": "Full snippet",
|
||||
"settings.annotationDisplayMode.surface": "Single surface",
|
||||
"settings.annotationDisplayMode.dot": "Single dot",
|
||||
"settings.previewRendererRollout": "Preview compatibility mode",
|
||||
"settings.previewRendererRollout.desc": "Controls how widely the Three.js preview path is used for single-model previews (GLB, GLTF, STL, PLY, OBJ). Switch back to Compatibility mode if pin projection, occlusion, edit pins, snapshots, or toolbar behavior regress. 3dgrid is not affected by this setting.",
|
||||
"settings.previewRendererRollout.babylonSafe": "Compatibility mode",
|
||||
"settings.previewRendererRollout.readonly": "Reading surfaces only",
|
||||
"settings.previewRendererRollout.direct": "Reading + file view (Recommended)",
|
||||
"settings.useThreeRenderer": "Use Three.js renderer",
|
||||
"settings.useThreeRenderer.desc": "Toggle between Three.js (faster, lighter) and Babylon.js (full compatibility) for single-model previews. Three.js supports GLB/GLTF/STL/PLY/OBJ. 3dgrid always uses Babylon.js.",
|
||||
"settings.experimentalThreeWorkbench": "Experimental Three workbench",
|
||||
"settings.experimentalThreeWorkbench.desc": "Try the Three.js workbench path for direct GLB/GLTF files only. If loading fails, the file view falls back to Babylon.js automatically.",
|
||||
"settings.autoRotateDefault": "Auto-rotate by default",
|
||||
"settings.useThreeRenderer": "Use Three.js renderer",
|
||||
"settings.useThreeRenderer.desc": "Toggle between Three.js (faster, lighter) and Babylon.js (full compatibility) for single-model previews. Three.js supports GLB/GLTF/STL/PLY/OBJ. 3dgrid always uses Babylon.js.",
|
||||
"settings.experimentalThreeWorkbench": "Experimental Three workbench",
|
||||
"settings.experimentalThreeWorkbench.desc": "Try the Three.js workbench path for direct GLB/GLTF files only. If loading fails, the file view falls back to Babylon.js automatically.",
|
||||
"settings.autoRotateDefault": "Auto-rotate by default",
|
||||
"settings.autoRotateDefault.desc": "Start model previews with auto-rotation enabled.",
|
||||
"settings.snapshotNaming": "Snapshot naming",
|
||||
"settings.snapshotNaming.desc": "How exported snapshot files are named.",
|
||||
"settings.snapshotNaming.modelName": "Model name + timestamp",
|
||||
"settings.snapshotNaming.timestamp": "Timestamp only",
|
||||
"settings.logLevel": "Log level",
|
||||
"settings.logLevel.desc": "Controls plugin runtime log verbosity in the developer console.",
|
||||
"settings.language": "Language",
|
||||
"settings.language.desc": "Display language for plugin settings. Takes effect immediately.",
|
||||
"settings.analysisMode": "AI drafting mode",
|
||||
"settings.analysisMode.desc": "Controls whether knowledge-note generation stays local or also requests a sanitized remote draft. Raw model upload is blocked by the current client.",
|
||||
"settings.analysisMode.local": "Local evidence only",
|
||||
"settings.analysisMode.hybrid": "Local evidence + remote draft",
|
||||
"settings.analysisMode.remote": "Remote draft from evidence",
|
||||
"settings.serviceBaseUrl": "Draft service URL",
|
||||
"settings.serviceBaseUrl.desc": "Optional base URL for a service that accepts POST /draft-note. Leave empty to keep all drafting local.",
|
||||
"settings.sendGeometrySummaryToRemote": "Send geometry summary",
|
||||
"settings.sendGeometrySummaryToRemote.desc": "Allow sanitized mesh counts, bounds, part candidates, annotation coordinates, and nearest-part links to be included in remote draft input.",
|
||||
"settings.sendPreviewImagesToRemote": "Send preview image references",
|
||||
"settings.sendPreviewImagesToRemote.desc": "Allow generated preview image paths to be listed in the remote draft input. Image bytes are not uploaded by this client.",
|
||||
"settings.sendRawModelToRemote": "Send raw model file",
|
||||
"settings.sendRawModelToRemote.desc": "Reserved for future explicit upload support. If enabled manually, remote draft requests are blocked instead of uploading the model.",
|
||||
|
||||
// Converters
|
||||
"settings.logLevel": "Log level",
|
||||
"settings.logLevel.desc": "Controls plugin runtime log verbosity in the developer console.",
|
||||
"settings.language": "Language",
|
||||
"settings.language.desc": "Display language for plugin settings. Takes effect immediately.",
|
||||
"settings.analysisMode": "AI drafting mode",
|
||||
"settings.analysisMode.desc": "Controls whether knowledge-note generation stays local or also requests a sanitized remote draft. Raw model upload is blocked by the current client.",
|
||||
"settings.analysisMode.local": "Local evidence only",
|
||||
"settings.analysisMode.hybrid": "Local evidence + remote draft",
|
||||
"settings.analysisMode.remote": "Remote draft from evidence",
|
||||
"settings.serviceBaseUrl": "Draft service URL",
|
||||
"settings.serviceBaseUrl.desc": "Optional base URL for a service that accepts POST /draft-note. Leave empty to keep all drafting local.",
|
||||
"settings.sendGeometrySummaryToRemote": "Send geometry summary",
|
||||
"settings.sendGeometrySummaryToRemote.desc": "Allow sanitized mesh counts, bounds, part candidates, annotation coordinates, and nearest-part links to be included in remote draft input.",
|
||||
"settings.sendPreviewImagesToRemote": "Send preview image references",
|
||||
"settings.sendPreviewImagesToRemote.desc": "Allow generated preview image paths to be listed in the remote draft input. Image bytes are not uploaded by this client.",
|
||||
"settings.sendRawModelToRemote": "Send raw model file",
|
||||
"settings.sendRawModelToRemote.desc": "Reserved for future explicit upload support. If enabled manually, remote draft requests are blocked instead of uploading the model.",
|
||||
|
||||
// Converters
|
||||
"settings.enableCad": "Enable cad conversion for step, iges, and brep files",
|
||||
"settings.enableCad.desc": "Enable cad conversion for step, iges, and brep formats. Requires cadquery and trimesh in your python environment.",
|
||||
"settings.enableObj2gltf": "Enable obj2gltf converter (experimental)",
|
||||
|
|
@ -216,8 +216,8 @@ export const en = {
|
|||
"workbench.previewViewsTitle": "Preview views",
|
||||
"workbench.connectionsTitle": "Knowledge connections",
|
||||
"workbench.currentModelLabel": "Current model",
|
||||
"workbench.noReportYet": "No report note yet",
|
||||
"workbench.noIndexYet": "No knowledge index yet. Generate a knowledge note first.",
|
||||
"workbench.noReportYet": "No report note yet",
|
||||
"workbench.noIndexYet": "No knowledge index yet. Generate a knowledge note first.",
|
||||
"workbench.noModelLoaded": "No model loaded",
|
||||
"workbench.disassemblyTitle": "Disassembly",
|
||||
"workbench.explodeLabel": "Explode",
|
||||
|
|
@ -252,14 +252,14 @@ export const en = {
|
|||
"workbench.pauseAction": "Pause",
|
||||
"workbench.saveProfileAction": "Save profile",
|
||||
"workbench.generateNoteAction": "Generate note",
|
||||
"workbench.openNoteAction": "Open note",
|
||||
"workbench.openIndexAction": "Open index",
|
||||
"workbench.openNoteAction": "Open note",
|
||||
"workbench.openIndexAction": "Open index",
|
||||
"workbench.recordTitle": "Specimen card",
|
||||
"workbench.recordSubtitle": "Current asset profile",
|
||||
"workbench.notesTitle": "Analysis notes",
|
||||
"workbench.whereTitle": "Where it connects",
|
||||
"workbench.noteReady": "Linked note ready",
|
||||
"workbench.indexReady": "Knowledge index ready",
|
||||
"workbench.noteReady": "Linked note ready",
|
||||
"workbench.indexReady": "Knowledge index ready",
|
||||
"workbench.notePending": "Linked note pending",
|
||||
"workbench.analysisLabel": "Analysis",
|
||||
"workbench.settingsUnavailable": "Open this plugin from Obsidian settings to change workbench options.",
|
||||
|
|
@ -269,44 +269,41 @@ export const en = {
|
|||
"workbench.infoCopied": "Model info copied",
|
||||
"workbench.templateInserted": "Template inserted",
|
||||
"workbench.templateCopied": "Template copied",
|
||||
"workbench.mobileHint": "Mobile tip: tap Interact to move the model, then switch back to Scroll to keep reading the note.",
|
||||
"workbench.mobileHintInteractive": "Interaction mode is on. Switch back to Scroll when you want to move through the note again.",
|
||||
"directView.mobileHint": "Mobile tip: use the toolbar Interact button to switch between model interaction and note scrolling.",
|
||||
"directWorkbench.backendLabel": "Backend",
|
||||
"directWorkbench.routeLabel": "Route",
|
||||
"directWorkbench.performanceLabel": "Performance",
|
||||
"directWorkbench.partCandidatesLabel": "Part candidates",
|
||||
"directWorkbench.knowledgeTitle": "Knowledge",
|
||||
"directWorkbench.tabOverview": "Overview",
|
||||
"directWorkbench.tabMatches": "Matches",
|
||||
"directWorkbench.tabKnowledge": "Knowledge",
|
||||
"directWorkbench.registeredTitle": "Registered part matches",
|
||||
"directWorkbench.registeredLoading": "Checking...",
|
||||
"directWorkbench.registeredCount": "{count} match(es)",
|
||||
"directWorkbench.registeredEmpty": "No cross-model part match yet",
|
||||
"directWorkbench.registeredUnavailable": "Part evidence unavailable",
|
||||
"directWorkbench.registeredOpen": "Open",
|
||||
"directWorkbench.registeredOpenNote": "Note",
|
||||
"directWorkbench.registeredOpenModel": "Model",
|
||||
"directWorkbench.registeredSourceModel": "From {model}",
|
||||
"directWorkbench.registeredTargetPartNote": "Opens matched part note",
|
||||
"directWorkbench.registeredTargetSourceModel": "Opens source model",
|
||||
"directWorkbench.registeredTargetUnavailable": "No linked target",
|
||||
"workbench.fileNotFound": "File not found: {path}",
|
||||
"workbench.mobileHint": "Mobile tip: tap Interact to move the model, then switch back to Scroll to keep reading the note.",
|
||||
"workbench.mobileHintInteractive": "Interaction mode is on. Switch back to Scroll when you want to move through the note again.",
|
||||
"directView.mobileHint": "Mobile tip: use the toolbar Interact button to switch between model interaction and note scrolling.",
|
||||
"directWorkbench.backendLabel": "Backend",
|
||||
"directWorkbench.routeLabel": "Route",
|
||||
"directWorkbench.performanceLabel": "Performance",
|
||||
"directWorkbench.partCandidatesLabel": "Part candidates",
|
||||
"directWorkbench.knowledgeTitle": "Knowledge",
|
||||
"directWorkbench.registeredTitle": "Registered part matches",
|
||||
"directWorkbench.registeredLoading": "Checking...",
|
||||
"directWorkbench.registeredCount": "{count} match(es)",
|
||||
"directWorkbench.registeredEmpty": "No cross-model part match yet",
|
||||
"directWorkbench.registeredUnavailable": "Part evidence unavailable",
|
||||
"directWorkbench.registeredOpen": "Open",
|
||||
"directWorkbench.registeredOpenNote": "Note",
|
||||
"directWorkbench.registeredOpenModel": "Model",
|
||||
"directWorkbench.registeredSourceModel": "From {model}",
|
||||
"directWorkbench.registeredTargetPartNote": "Opens matched part note",
|
||||
"directWorkbench.registeredTargetSourceModel": "Opens source model",
|
||||
"directWorkbench.registeredTargetUnavailable": "No linked target",
|
||||
"workbench.fileNotFound": "File not found: {path}",
|
||||
"annotation.selectColor": "Select color {color}",
|
||||
"annotation.sectionEmpty": "Section is empty.",
|
||||
"modal.selectModel": "Select a 3D model...",
|
||||
"main.commandImportModel": "Import a 3D model",
|
||||
"main.commandGenerateNote": "Generate knowledge note",
|
||||
"main.commandOpenKnowledgeIndex": "Open knowledge index",
|
||||
"main.commandClearConversionCache": "Clear conversion cache",
|
||||
"main.commandCheckConverters": "Check converters",
|
||||
"main.commandCopyDiagnostics": "Copy diagnostics report",
|
||||
"main.converterDiagnosticsMobileUnavailable": "Converter diagnostics are only available on desktop. On iOS, iPadOS, and Android, direct formats can still load.",
|
||||
"main.diagnosticsCopied": "AI Model Workbench diagnostics copied to clipboard.",
|
||||
"main.diagnosticsCopyFailed": "Couldn't copy diagnostics. A sanitized diagnostics note was created instead.",
|
||||
|
||||
// Inline code block and loading
|
||||
"main.commandGenerateNote": "Generate knowledge note",
|
||||
"main.commandOpenKnowledgeIndex": "Open knowledge index",
|
||||
"main.commandClearConversionCache": "Clear conversion cache",
|
||||
"main.commandCheckConverters": "Check converters",
|
||||
"main.commandCopyDiagnostics": "Copy diagnostics report",
|
||||
"main.converterDiagnosticsMobileUnavailable": "Converter diagnostics are only available on desktop. On iOS, iPadOS, and Android, direct formats can still load.",
|
||||
"main.diagnosticsCopied": "AI Model Workbench diagnostics copied to clipboard.",
|
||||
"main.diagnosticsCopyFailed": "Couldn't copy diagnostics. A sanitized diagnostics note was created instead.",
|
||||
|
||||
// Inline code block and loading
|
||||
"codeBlock.noModelPathOrConfig": "No model path or config specified.",
|
||||
"codeBlock.jsonParseError": "JSON parse error: {error}",
|
||||
"codeBlock.jsonParseLine": " (line {line})",
|
||||
|
|
|
|||
|
|
@ -10,68 +10,68 @@ export const zhCN: Record<TranslationKey, string> = {
|
|||
"settings.converterMenu.desc": "默认收起。仅在你需要手动启用某条本地转换路线时再展开。",
|
||||
"settings.environmentInspector": "环境检查器",
|
||||
"settings.environmentInspector.desc": "默认收起。仅在需要配置命令路径或手动执行环境检查时再展开。",
|
||||
"settings.paths": "转换器路径",
|
||||
"settings.diagnostics": "转换器命令诊断",
|
||||
"settings.performance": "性能与显示",
|
||||
"settings.mobileSupport": "移动端支持",
|
||||
"settings.knowledgeGeneration": "知识生成",
|
||||
"settings.paths": "转换器路径",
|
||||
"settings.diagnostics": "转换器命令诊断",
|
||||
"settings.performance": "性能与显示",
|
||||
"settings.mobileSupport": "移动端支持",
|
||||
"settings.knowledgeGeneration": "知识生成",
|
||||
|
||||
// 文件夹
|
||||
"settings.sourceModelFolder": "源模型文件夹",
|
||||
"settings.sourceModelFolder.desc": "存放源 3D 模型的库文件夹。",
|
||||
"settings.reportFolder": "报告文件夹",
|
||||
"settings.reportFolder.desc": "保存生成的知识笔记的库文件夹。",
|
||||
"settings.partFolder": "部件笔记文件夹",
|
||||
"settings.partFolder.desc": "保存生成的部件笔记草稿的库文件夹。",
|
||||
"settings.snapshotFolder": "快照文件夹",
|
||||
"settings.reportFolder": "报告文件夹",
|
||||
"settings.reportFolder.desc": "保存生成的知识笔记的库文件夹。",
|
||||
"settings.partFolder": "部件笔记文件夹",
|
||||
"settings.partFolder.desc": "保存生成的部件笔记草稿的库文件夹。",
|
||||
"settings.snapshotFolder": "快照文件夹",
|
||||
"settings.snapshotFolder.desc": "保存导出快照的库文件夹。",
|
||||
|
||||
// 行为
|
||||
"settings.autoGenerateKnowledgeNotes": "自动生成知识笔记",
|
||||
"settings.autoGenerateKnowledgeNotes.desc": "预留给后续自动化流程。当前请通过命令或工作台按钮手动生成知识笔记。",
|
||||
"settings.annotationPreviewMode": "标注预览模式",
|
||||
"settings.annotationPreviewMode.desc": "选择标注绑定笔记在悬浮预览和编辑器中的显示方式。",
|
||||
"settings.annotationPreviewMode.plainText": "纯文本",
|
||||
"settings.annotationPreviewMode.markdown": "Markdown",
|
||||
"settings.annotationDisplayMode": "标注显示模式",
|
||||
"settings.annotationDisplayMode.desc": "选择模型上书签标注的显示方式。",
|
||||
"settings.annotationDisplayMode.snippet": "完整片段",
|
||||
"settings.annotationDisplayMode.surface": "单个标签面",
|
||||
"settings.annotationDisplayMode.dot": "单个圆点",
|
||||
"settings.previewRendererRollout": "预览兼容模式",
|
||||
"settings.annotationPreviewMode.desc": "选择标注绑定笔记在悬浮预览和编辑器中的显示方式。",
|
||||
"settings.annotationPreviewMode.plainText": "纯文本",
|
||||
"settings.annotationPreviewMode.markdown": "Markdown",
|
||||
"settings.annotationDisplayMode": "标注显示模式",
|
||||
"settings.annotationDisplayMode.desc": "选择模型上书签标注的显示方式。",
|
||||
"settings.annotationDisplayMode.snippet": "完整片段",
|
||||
"settings.annotationDisplayMode.surface": "单个标签面",
|
||||
"settings.annotationDisplayMode.dot": "单个圆点",
|
||||
"settings.previewRendererRollout": "预览兼容模式",
|
||||
"settings.previewRendererRollout.desc": "控制单模型预览(GLB、GLTF、STL、PLY、OBJ)中 Three.js 渲染路径的启用范围。只要出现 pin 投影、遮挡、编辑态 pin、快照或工具栏行为回退,就切回“兼容优先”。workbench 和 3dgrid 不受这个设置影响。",
|
||||
"settings.previewRendererRollout.babylonSafe": "兼容优先",
|
||||
"settings.previewRendererRollout.readonly": "仅阅读场景",
|
||||
"settings.previewRendererRollout.direct": "阅读 + 文件视图(推荐)",
|
||||
"settings.useThreeRenderer": "使用 Three.js 渲染器",
|
||||
"settings.useThreeRenderer.desc": "在 Three.js(更快、更轻量)和 Babylon.js(完全兼容)之间切换单模型预览渲染器。Three.js 支持 GLB/GLTF/STL/PLY/OBJ。工作台和 3dgrid 始终使用 Babylon.js。",
|
||||
"settings.experimentalThreeWorkbench": "实验性 Three 工作台",
|
||||
"settings.experimentalThreeWorkbench.desc": "仅对直读 GLB/GLTF 文件尝试使用 Three.js 工作台路径。加载失败时,文件视图会自动回退到 Babylon.js。",
|
||||
"settings.autoRotateDefault": "默认自动旋转",
|
||||
"settings.useThreeRenderer": "使用 Three.js 渲染器",
|
||||
"settings.useThreeRenderer.desc": "在 Three.js(更快、更轻量)和 Babylon.js(完全兼容)之间切换单模型预览渲染器。Three.js 支持 GLB/GLTF/STL/PLY/OBJ。工作台和 3dgrid 始终使用 Babylon.js。",
|
||||
"settings.experimentalThreeWorkbench": "实验性 Three 工作台",
|
||||
"settings.experimentalThreeWorkbench.desc": "仅对直读 GLB/GLTF 文件尝试使用 Three.js 工作台路径。加载失败时,文件视图会自动回退到 Babylon.js。",
|
||||
"settings.autoRotateDefault": "默认自动旋转",
|
||||
"settings.autoRotateDefault.desc": "启动 3D 预览时默认开启自动旋转。",
|
||||
"settings.snapshotNaming": "快照命名",
|
||||
"settings.snapshotNaming.desc": "导出快照文件的命名方式。",
|
||||
"settings.snapshotNaming.modelName": "模型名 + 时间戳",
|
||||
"settings.snapshotNaming.timestamp": "仅时间戳",
|
||||
"settings.logLevel": "日志级别",
|
||||
"settings.logLevel.desc": "控制插件在开发者控制台中的日志详细程度。",
|
||||
"settings.language": "语言",
|
||||
"settings.language.desc": "插件设置界面的显示语言。立即生效。",
|
||||
"settings.analysisMode": "AI 草稿模式",
|
||||
"settings.analysisMode.desc": "控制知识笔记生成保持本地,还是额外请求经过裁剪的远程草稿。当前客户端会阻止原始模型上传。",
|
||||
"settings.analysisMode.local": "仅本地证据",
|
||||
"settings.analysisMode.hybrid": "本地证据 + 远程草稿",
|
||||
"settings.analysisMode.remote": "基于证据的远程草稿",
|
||||
"settings.serviceBaseUrl": "草稿服务 URL",
|
||||
"settings.serviceBaseUrl.desc": "可选的草稿服务基础地址,服务需接收 POST /draft-note。留空时所有草稿输入都保留在本地。",
|
||||
"settings.sendGeometrySummaryToRemote": "发送几何摘要",
|
||||
"settings.sendGeometrySummaryToRemote.desc": "允许把经过裁剪的网格数量、包围盒、部件候选、标注坐标和最近部件关联加入远程草稿输入。",
|
||||
"settings.sendPreviewImagesToRemote": "发送预览图引用",
|
||||
"settings.sendPreviewImagesToRemote.desc": "允许在远程草稿输入中列出生成的预览图路径。当前客户端不会上传图片字节。",
|
||||
"settings.sendRawModelToRemote": "发送原始模型文件",
|
||||
"settings.sendRawModelToRemote.desc": "预留给未来显式上传支持。若手动开启,远程草稿请求会被阻止,不会上传模型。",
|
||||
|
||||
// 转换器
|
||||
"settings.logLevel": "日志级别",
|
||||
"settings.logLevel.desc": "控制插件在开发者控制台中的日志详细程度。",
|
||||
"settings.language": "语言",
|
||||
"settings.language.desc": "插件设置界面的显示语言。立即生效。",
|
||||
"settings.analysisMode": "AI 草稿模式",
|
||||
"settings.analysisMode.desc": "控制知识笔记生成保持本地,还是额外请求经过裁剪的远程草稿。当前客户端会阻止原始模型上传。",
|
||||
"settings.analysisMode.local": "仅本地证据",
|
||||
"settings.analysisMode.hybrid": "本地证据 + 远程草稿",
|
||||
"settings.analysisMode.remote": "基于证据的远程草稿",
|
||||
"settings.serviceBaseUrl": "草稿服务 URL",
|
||||
"settings.serviceBaseUrl.desc": "可选的草稿服务基础地址,服务需接收 POST /draft-note。留空时所有草稿输入都保留在本地。",
|
||||
"settings.sendGeometrySummaryToRemote": "发送几何摘要",
|
||||
"settings.sendGeometrySummaryToRemote.desc": "允许把经过裁剪的网格数量、包围盒、部件候选、标注坐标和最近部件关联加入远程草稿输入。",
|
||||
"settings.sendPreviewImagesToRemote": "发送预览图引用",
|
||||
"settings.sendPreviewImagesToRemote.desc": "允许在远程草稿输入中列出生成的预览图路径。当前客户端不会上传图片字节。",
|
||||
"settings.sendRawModelToRemote": "发送原始模型文件",
|
||||
"settings.sendRawModelToRemote.desc": "预留给未来显式上传支持。若手动开启,远程草稿请求会被阻止,不会上传模型。",
|
||||
|
||||
// 转换器
|
||||
"settings.enableCad": "启用 CAD 转换器 (STEP/IGES/BREP)",
|
||||
"settings.enableCad.desc": "启用 STEP/IGES/BREP 格式的 CAD 转换路线,通过 Python CadQuery (OpenCASCADE)。需要:pip install cadquery trimesh",
|
||||
"settings.enableObj2gltf": "启用 obj2gltf 转换器(实验性)",
|
||||
|
|
@ -218,8 +218,8 @@ export const zhCN: Record<TranslationKey, string> = {
|
|||
"workbench.previewViewsTitle": "预览视图",
|
||||
"workbench.connectionsTitle": "知识连接",
|
||||
"workbench.currentModelLabel": "当前模型",
|
||||
"workbench.noReportYet": "还没有报告笔记",
|
||||
"workbench.noIndexYet": "还没有知识索引。请先生成知识笔记。",
|
||||
"workbench.noReportYet": "还没有报告笔记",
|
||||
"workbench.noIndexYet": "还没有知识索引。请先生成知识笔记。",
|
||||
"workbench.noModelLoaded": "尚未加载模型",
|
||||
"workbench.disassemblyTitle": "分解",
|
||||
"workbench.explodeLabel": "爆炸",
|
||||
|
|
@ -254,14 +254,14 @@ export const zhCN: Record<TranslationKey, string> = {
|
|||
"workbench.pauseAction": "暂停",
|
||||
"workbench.saveProfileAction": "保存配置",
|
||||
"workbench.generateNoteAction": "生成笔记",
|
||||
"workbench.openNoteAction": "打开笔记",
|
||||
"workbench.openIndexAction": "打开索引",
|
||||
"workbench.openNoteAction": "打开笔记",
|
||||
"workbench.openIndexAction": "打开索引",
|
||||
"workbench.recordTitle": "标本卡",
|
||||
"workbench.recordSubtitle": "当前资产档案",
|
||||
"workbench.notesTitle": "分析札记",
|
||||
"workbench.whereTitle": "关联位置",
|
||||
"workbench.noteReady": "关联笔记已就绪",
|
||||
"workbench.indexReady": "知识索引已就绪",
|
||||
"workbench.noteReady": "关联笔记已就绪",
|
||||
"workbench.indexReady": "知识索引已就绪",
|
||||
"workbench.notePending": "关联笔记待生成",
|
||||
"workbench.analysisLabel": "分析",
|
||||
"workbench.settingsUnavailable": "请从 Obsidian 设置中打开本插件来调整工作台选项。",
|
||||
|
|
@ -271,44 +271,41 @@ export const zhCN: Record<TranslationKey, string> = {
|
|||
"workbench.infoCopied": "模型信息已复制",
|
||||
"workbench.templateInserted": "模板已插入",
|
||||
"workbench.templateCopied": "模板已复制",
|
||||
"workbench.mobileHint": "移动端提示:需要操作模型时点“交互”,继续阅读笔记时切回“滚动”。",
|
||||
"workbench.mobileHintInteractive": "当前为交互模式。需要继续滚动笔记时,请切回“滚动”。",
|
||||
"directView.mobileHint": "移动端提示:使用下方工具栏里的“交互”按钮,在模型操作和笔记滚动之间切换。",
|
||||
"directWorkbench.backendLabel": "后端",
|
||||
"directWorkbench.routeLabel": "路由",
|
||||
"directWorkbench.performanceLabel": "性能",
|
||||
"directWorkbench.partCandidatesLabel": "候选零件",
|
||||
"directWorkbench.knowledgeTitle": "知识",
|
||||
"directWorkbench.tabOverview": "概览",
|
||||
"directWorkbench.tabMatches": "匹配",
|
||||
"directWorkbench.tabKnowledge": "知识",
|
||||
"directWorkbench.registeredTitle": "已注册零件匹配",
|
||||
"directWorkbench.registeredLoading": "检查中...",
|
||||
"directWorkbench.registeredCount": "{count} 个匹配",
|
||||
"directWorkbench.registeredEmpty": "暂无跨模型零件匹配",
|
||||
"directWorkbench.registeredUnavailable": "暂无零件证据",
|
||||
"directWorkbench.registeredOpen": "打开",
|
||||
"directWorkbench.registeredOpenNote": "笔记",
|
||||
"directWorkbench.registeredOpenModel": "模型",
|
||||
"directWorkbench.registeredSourceModel": "来自 {model}",
|
||||
"directWorkbench.registeredTargetPartNote": "打开匹配零件笔记",
|
||||
"directWorkbench.registeredTargetSourceModel": "打开来源模型",
|
||||
"directWorkbench.registeredTargetUnavailable": "暂无可打开目标",
|
||||
"workbench.fileNotFound": "未找到文件:{path}",
|
||||
"workbench.mobileHint": "移动端提示:需要操作模型时点“交互”,继续阅读笔记时切回“滚动”。",
|
||||
"workbench.mobileHintInteractive": "当前为交互模式。需要继续滚动笔记时,请切回“滚动”。",
|
||||
"directView.mobileHint": "移动端提示:使用下方工具栏里的“交互”按钮,在模型操作和笔记滚动之间切换。",
|
||||
"directWorkbench.backendLabel": "后端",
|
||||
"directWorkbench.routeLabel": "路由",
|
||||
"directWorkbench.performanceLabel": "性能",
|
||||
"directWorkbench.partCandidatesLabel": "候选零件",
|
||||
"directWorkbench.knowledgeTitle": "知识",
|
||||
"directWorkbench.registeredTitle": "已注册零件匹配",
|
||||
"directWorkbench.registeredLoading": "检查中...",
|
||||
"directWorkbench.registeredCount": "{count} 个匹配",
|
||||
"directWorkbench.registeredEmpty": "暂无跨模型零件匹配",
|
||||
"directWorkbench.registeredUnavailable": "暂无零件证据",
|
||||
"directWorkbench.registeredOpen": "打开",
|
||||
"directWorkbench.registeredOpenNote": "笔记",
|
||||
"directWorkbench.registeredOpenModel": "模型",
|
||||
"directWorkbench.registeredSourceModel": "来自 {model}",
|
||||
"directWorkbench.registeredTargetPartNote": "打开匹配零件笔记",
|
||||
"directWorkbench.registeredTargetSourceModel": "打开来源模型",
|
||||
"directWorkbench.registeredTargetUnavailable": "暂无可打开目标",
|
||||
"workbench.fileNotFound": "未找到文件:{path}",
|
||||
"annotation.selectColor": "选择颜色 {color}",
|
||||
"annotation.sectionEmpty": "该段内容为空。",
|
||||
"modal.selectModel": "选择一个 3D 模型...",
|
||||
"main.commandImportModel": "导入 3D 模型",
|
||||
"main.commandGenerateNote": "生成知识笔记",
|
||||
"main.commandOpenKnowledgeIndex": "打开知识索引",
|
||||
"main.commandClearConversionCache": "清除转换缓存",
|
||||
"main.commandCheckConverters": "检查转换器",
|
||||
"main.commandCopyDiagnostics": "复制诊断报告",
|
||||
"main.converterDiagnosticsMobileUnavailable": "转换器诊断仅支持桌面端。在 iOS、iPadOS 和 Android 上,直读格式仍可加载。",
|
||||
"main.diagnosticsCopied": "AI Model Workbench 诊断报告已复制到剪贴板。",
|
||||
"main.diagnosticsCopyFailed": "无法复制诊断报告,已改为创建一份脱敏诊断笔记。",
|
||||
|
||||
// 内联代码块与加载
|
||||
"main.commandGenerateNote": "生成知识笔记",
|
||||
"main.commandOpenKnowledgeIndex": "打开知识索引",
|
||||
"main.commandClearConversionCache": "清除转换缓存",
|
||||
"main.commandCheckConverters": "检查转换器",
|
||||
"main.commandCopyDiagnostics": "复制诊断报告",
|
||||
"main.converterDiagnosticsMobileUnavailable": "转换器诊断仅支持桌面端。在 iOS、iPadOS 和 Android 上,直读格式仍可加载。",
|
||||
"main.diagnosticsCopied": "AI Model Workbench 诊断报告已复制到剪贴板。",
|
||||
"main.diagnosticsCopyFailed": "无法复制诊断报告,已改为创建一份脱敏诊断笔记。",
|
||||
|
||||
// 内联代码块与加载
|
||||
"codeBlock.noModelPathOrConfig": "未指定模型路径或配置。",
|
||||
"codeBlock.jsonParseError": "JSON 解析错误:{error}",
|
||||
"codeBlock.jsonParseLine": "(第 {line} 行)",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { PluginSettings, ModelAssetProfile, ModelPreviewSummary, ModelEvide
|
|||
import { AnnotationManager } from "../render/preview/annotations";
|
||||
import { createLoggedModelPreview } from "../render/preview/selection";
|
||||
import type { AnnotationPreview } from "../render/preview/types";
|
||||
import { supportsDisassemblyPreview, supportsFocusSelectionPreview } from "../render/preview/types";
|
||||
import { createHelperButtons } from "./inline/helper-buttons";
|
||||
import { createConversionManager } from "../io/conversion/factory";
|
||||
import type { ConvertedAssetCache } from "../io/cache/converted-asset-cache";
|
||||
|
|
@ -101,9 +100,6 @@ export class DirectModelView extends FileView {
|
|||
private workbenchSummary: ModelPreviewSummary | null = null;
|
||||
private workbenchRoute: { backend: string; reason: string } | null = null;
|
||||
private workbenchModelPath: string | null = null;
|
||||
private activeTab: "overview" | "matches" | "knowledge" = "overview";
|
||||
private bottomPanel: HTMLElement | null = null;
|
||||
private tabButtons: Map<string, HTMLButtonElement> = new Map();
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, getSettings: () => PluginSettings, convertedAssetCache: ConvertedAssetCache, ps: PluginStore) {
|
||||
super(leaf);
|
||||
|
|
@ -160,8 +156,6 @@ export class DirectModelView extends FileView {
|
|||
this.workbenchSummary = null;
|
||||
this.workbenchRoute = null;
|
||||
this.workbenchModelPath = null;
|
||||
this.bottomPanel = null;
|
||||
this.tabButtons.clear();
|
||||
this.preview?.destroy();
|
||||
this.preview = null;
|
||||
this.ps.store.setState({
|
||||
|
|
@ -169,7 +163,7 @@ export class DirectModelView extends FileView {
|
|||
modelPreview: null,
|
||||
selectedPart: null,
|
||||
});
|
||||
// === New workspace layout ===
|
||||
// === Workspace layout with draggable resize ===
|
||||
const workspace = this.contentEl.createDiv({ cls: "ai3d-workspace" });
|
||||
// Top track: main preview + sidebar
|
||||
const topTrack = workspace.createDiv({ cls: "ai3d-workspace-track-top" });
|
||||
|
|
@ -229,10 +223,8 @@ export class DirectModelView extends FileView {
|
|||
this.createSidebarButtons(sidebar, file);
|
||||
// Vertical resize handle + bottom panel
|
||||
const vHandle = workspace.createDiv({ cls: "ai3d-resize-handle ai3d-resize-handle-v" });
|
||||
const bottomPanel = workspace.createDiv({ cls: "ai3d-workspace-bottom is-hidden" });
|
||||
this.bottomPanel = bottomPanel;
|
||||
// Setup tabs and resize handles
|
||||
this.workbenchPanel = this.createBottomTabs(bottomPanel, file);
|
||||
const workbenchPanel = workspace.createDiv({ cls: "ai3d-direct-workbench-panel is-hidden" });
|
||||
this.workbenchPanel = workbenchPanel;
|
||||
this.setupResizeHandles(hHandle, vHandle, topTrack, workspace);
|
||||
if (mobile) {
|
||||
mainArea.createDiv({
|
||||
|
|
@ -278,11 +270,11 @@ export class DirectModelView extends FileView {
|
|||
const evidence = this.preview.getModelEvidence?.() ?? null;
|
||||
this.registerModelPartsFromEvidence(file.path, evidence);
|
||||
renderModelPerformanceFeedback(host, summary);
|
||||
this.workbenchPanel = this.workbenchPanel;
|
||||
this.workbenchPanel = workbenchPanel;
|
||||
this.workbenchSummary = summary;
|
||||
this.workbenchRoute = created.route;
|
||||
this.workbenchModelPath = file.path;
|
||||
this.renderWorkbenchPanel(this.workbenchPanel, summary, created.route, file.path);
|
||||
this.renderWorkbenchPanel(workbenchPanel, summary, created.route, file.path);
|
||||
this.ps.store.setState({
|
||||
currentModelPath: file.path,
|
||||
modelPreview: summary,
|
||||
|
|
@ -353,7 +345,7 @@ export class DirectModelView extends FileView {
|
|||
this.preview?.destroy();
|
||||
this.preview = null;
|
||||
host.replaceChildren();
|
||||
this.bottomPanel?.addClass("is-hidden");
|
||||
this.workbenchPanel?.addClass("is-hidden");
|
||||
const failure = describeModelLoadFailure(err);
|
||||
if (isMissingConverterError(err)) {
|
||||
console.warn("[AI3D] Direct view blocked by converter settings:", failure.message);
|
||||
|
|
@ -406,24 +398,7 @@ export class DirectModelView extends FileView {
|
|||
panel.removeClass("is-hidden");
|
||||
panel.dataset.ai3dBackend = route.backend;
|
||||
panel.dataset.ai3dRouteReason = route.reason;
|
||||
switch (this.activeTab) {
|
||||
case "overview":
|
||||
this.renderOverviewTab(panel, summary, route, modelPath);
|
||||
break;
|
||||
case "matches":
|
||||
this.renderMatchesTab(panel, modelPath, summary);
|
||||
break;
|
||||
case "knowledge":
|
||||
this.renderKnowledgeTab(panel, modelPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
private renderOverviewTab(
|
||||
panel: HTMLElement,
|
||||
summary: ModelPreviewSummary,
|
||||
route: { backend: string; reason: string },
|
||||
modelPath: string,
|
||||
): void {
|
||||
|
||||
const overview = panel.createDiv({ cls: "ai3d-direct-workbench-overview" });
|
||||
const status = overview.createDiv({ cls: "ai3d-direct-workbench-status" });
|
||||
const backendLine = status.createDiv({ cls: "ai3d-direct-workbench-line" });
|
||||
|
|
@ -432,6 +407,7 @@ export class DirectModelView extends FileView {
|
|||
const routeLine = status.createDiv({ cls: "ai3d-direct-workbench-line ai3d-direct-workbench-route" });
|
||||
routeLine.createSpan({ cls: "ai3d-direct-workbench-label", text: t("directWorkbench.routeLabel") });
|
||||
routeLine.createSpan({ cls: "ai3d-direct-workbench-value", text: route.reason });
|
||||
|
||||
const metrics = overview.createDiv({ cls: "ai3d-direct-workbench-metrics" });
|
||||
this.renderMetric(metrics, t("workbench.meshesLabel"), formatCount(summary.meshCount));
|
||||
this.renderMetric(
|
||||
|
|
@ -447,19 +423,17 @@ export class DirectModelView extends FileView {
|
|||
this.renderMetric(metrics, t("workbench.materialsLabel"), formatCount(summary.materialCount));
|
||||
this.renderMetric(metrics, t("workbench.boundingSizeLabel"), formatBounds(summary));
|
||||
this.renderMetric(metrics, t("directWorkbench.performanceLabel"), summary.performanceTier ?? "light");
|
||||
|
||||
const controls = panel.createDiv({ cls: "ai3d-direct-workbench-controls" });
|
||||
this.renderKnowledgeControls(controls, modelPath);
|
||||
this.renderRegisteredPartMatches(controls, modelPath, summary);
|
||||
}
|
||||
private renderMatchesTab(
|
||||
panel: HTMLElement,
|
||||
modelPath: string,
|
||||
summary: ModelPreviewSummary,
|
||||
): void {
|
||||
this.renderRegisteredPartMatches(panel, modelPath, summary);
|
||||
}
|
||||
private renderKnowledgeTab(
|
||||
panel: HTMLElement,
|
||||
modelPath: string,
|
||||
): void {
|
||||
this.renderKnowledgeControls(panel, modelPath);
|
||||
|
||||
private refreshWorkbenchPanel(): void {
|
||||
if (!this.workbenchPanel || !this.workbenchSummary || !this.workbenchRoute || !this.workbenchModelPath) {
|
||||
return;
|
||||
}
|
||||
this.renderWorkbenchPanel(this.workbenchPanel, this.workbenchSummary, this.workbenchRoute, this.workbenchModelPath);
|
||||
}
|
||||
private createSidebarButtons(sidebar: HTMLElement, file: TFile): void {
|
||||
const createBtn = (action: string, label: string, icon: string, onClick: () => void): void => {
|
||||
|
|
@ -471,24 +445,26 @@ export class DirectModelView extends FileView {
|
|||
btn.addEventListener("click", onClick);
|
||||
};
|
||||
createBtn("toggle-focus", t("helper.toggleFocusSelectionLabel"), `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>`, () => {
|
||||
if (this.preview && supportsFocusSelectionPreview(this.preview)) {
|
||||
this.preview.toggleFocusSelection();
|
||||
if (this.preview) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const p = this.preview as any;
|
||||
if (typeof p.toggleFocusSelection === "function") p.toggleFocusSelection();
|
||||
}
|
||||
});
|
||||
createBtn("toggle-disassembly", t("helper.toggleDisassemblyLabel"), `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h3"/><path d="M4 17v3h3"/><path d="M20 7V4h-3"/><path d="M20 17v3h-3"/></svg>`, () => {
|
||||
if (this.preview && supportsDisassemblyPreview(this.preview)) {
|
||||
this.preview.toggleDisassembly();
|
||||
if (this.preview) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const p = this.preview as any;
|
||||
if (typeof p.toggleDisassembly === "function") p.toggleDisassembly();
|
||||
}
|
||||
});
|
||||
createBtn("reset-parts", t("helper.resetPartsLabel"), `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74-2.74L3 12"/></svg>`, () => {
|
||||
if (this.preview && supportsDisassemblyPreview(this.preview)) {
|
||||
this.preview.resetDisassembly();
|
||||
if (this.preview) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const p = this.preview as any;
|
||||
if (typeof p.resetDisassembly === "function") p.resetDisassembly();
|
||||
}
|
||||
});
|
||||
createBtn("toggle-annotation", t("helper.toggleAnnotationLabel"), `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h6"/></svg>`, () => {
|
||||
// annotation toggle is handled by helper toolbar, sync sidebar state
|
||||
// This is a simplified version; ideally we sync with the main toolbar
|
||||
});
|
||||
createBtn("generate-note", t("workbench.generateNoteAction"), `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`, () => {
|
||||
const btn = sidebar.querySelector('[data-ai3d-action="generate-note"]');
|
||||
if (btn instanceof HTMLButtonElement) btn.disabled = true;
|
||||
|
|
@ -511,35 +487,6 @@ export class DirectModelView extends FileView {
|
|||
}
|
||||
});
|
||||
}
|
||||
private createBottomTabs(bottomPanel: HTMLElement, file: TFile): HTMLElement {
|
||||
const tabHeader = bottomPanel.createDiv({ cls: "ai3d-workspace-bottom-tabs" });
|
||||
const tabContent = bottomPanel.createDiv({ cls: "ai3d-workspace-bottom-content" });
|
||||
const tabs: { id: "overview" | "matches" | "knowledge"; labelKey: Parameters<typeof t>[0] }[] = [
|
||||
{ id: "overview", labelKey: "directWorkbench.tabOverview" },
|
||||
{ id: "matches", labelKey: "directWorkbench.tabMatches" },
|
||||
{ id: "knowledge", labelKey: "directWorkbench.tabKnowledge" },
|
||||
];
|
||||
for (const tab of tabs) {
|
||||
const btn = tabHeader.createEl("button", {
|
||||
cls: "ai3d-workspace-tab-btn",
|
||||
text: t(tab.labelKey),
|
||||
attr: { type: "button", "data-tab": tab.id },
|
||||
});
|
||||
btn.addEventListener("click", () => {
|
||||
this.activeTab = tab.id;
|
||||
this.refreshWorkbenchPanel();
|
||||
this.updateTabButtons();
|
||||
});
|
||||
this.tabButtons.set(tab.id, btn);
|
||||
}
|
||||
this.updateTabButtons();
|
||||
return tabContent;
|
||||
}
|
||||
private updateTabButtons(): void {
|
||||
for (const [id, btn] of this.tabButtons) {
|
||||
btn.classList.toggle("is-active", id === this.activeTab);
|
||||
}
|
||||
}
|
||||
private setupResizeHandles(
|
||||
hHandle: HTMLElement,
|
||||
vHandle: HTMLElement,
|
||||
|
|
@ -584,14 +531,6 @@ export class DirectModelView extends FileView {
|
|||
workspace.style.gridTemplateRows = `1fr 4px ${bottomHeight}px`;
|
||||
}, () => {});
|
||||
}
|
||||
|
||||
private refreshWorkbenchPanel(): void {
|
||||
if (!this.workbenchPanel || !this.workbenchSummary || !this.workbenchRoute || !this.workbenchModelPath) {
|
||||
return;
|
||||
}
|
||||
this.renderWorkbenchPanel(this.workbenchPanel, this.workbenchSummary, this.workbenchRoute, this.workbenchModelPath);
|
||||
}
|
||||
|
||||
private renderMetric(parent: HTMLElement, label: string, value: string): void {
|
||||
const metric = parent.createDiv({ cls: "ai3d-direct-workbench-metric" });
|
||||
metric.createSpan({ cls: "ai3d-direct-workbench-label", text: label });
|
||||
|
|
|
|||
133
styles.css
133
styles.css
|
|
@ -502,7 +502,6 @@ body {
|
|||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
/* Top track: main + sidebar */
|
||||
.ai3d-workspace-track-top {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 4px 200px;
|
||||
|
|
@ -523,7 +522,6 @@ body {
|
|||
.ai3d-workspace-main .ai3d-helper-toolbar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* Sidebar */
|
||||
.ai3d-workspace-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -559,7 +557,6 @@ body {
|
|||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
/* Resize handles */
|
||||
.ai3d-resize-handle {
|
||||
background: var(--background-modifier-border);
|
||||
user-select: none;
|
||||
|
|
@ -576,54 +573,11 @@ body {
|
|||
.ai3d-resize-handle-v {
|
||||
cursor: row-resize;
|
||||
}
|
||||
/* Bottom panel */
|
||||
.ai3d-workspace-bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--background-secondary);
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ai3d-workspace-bottom.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
.ai3d-workspace-bottom-tabs {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
padding: 4px 6px 0;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
flex-shrink: 0;
|
||||
background: var(--background-secondary);
|
||||
}
|
||||
.ai3d-workspace-tab-btn {
|
||||
padding: 4px 12px;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
border-radius: var(--radius-s) var(--radius-s) 0 0;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-smaller);
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.ai3d-workspace-tab-btn:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
.ai3d-workspace-tab-btn.is-active {
|
||||
background: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
border-color: var(--background-modifier-border);
|
||||
font-weight: 600;
|
||||
}
|
||||
.ai3d-workspace-bottom-content {
|
||||
.ai3d-direct-view .ai3d-preview-host {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
/* Legacy workbench panel styles (used inside tabs) */
|
||||
|
||||
.ai3d-direct-workbench-panel {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 0.9fr) minmax(320px, 1.4fr);
|
||||
|
|
@ -636,6 +590,7 @@ body {
|
|||
background: var(--background-secondary);
|
||||
font-size: var(--font-ui-smaller);
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-overview,
|
||||
.ai3d-direct-workbench-status,
|
||||
.ai3d-direct-workbench-metrics,
|
||||
|
|
@ -644,15 +599,18 @@ body {
|
|||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-overview,
|
||||
.ai3d-direct-workbench-status,
|
||||
.ai3d-direct-workbench-controls {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-metrics {
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-line,
|
||||
.ai3d-direct-workbench-metric,
|
||||
.ai3d-direct-workbench-control {
|
||||
|
|
@ -662,106 +620,128 @@ body {
|
|||
border-radius: var(--radius-s);
|
||||
background: var(--background-primary);
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-line,
|
||||
.ai3d-direct-workbench-metric {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-metric {
|
||||
flex: 1 1 84px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-route {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-label {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-value {
|
||||
min-width: 0;
|
||||
color: var(--text-normal);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-control {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-control-head,
|
||||
.ai3d-direct-workbench-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-control-head {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-action {
|
||||
padding: 5px 10px;
|
||||
min-height: 26px;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
background: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-smaller);
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-action:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
color: var(--text-normal);
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-action:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
cursor: default;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-knowledge {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-registered {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-registered-body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-empty {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-smaller);
|
||||
padding: 8px 0;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 6px;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 6px 8px;
|
||||
min-width: 0;
|
||||
padding: 6px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
background: var(--background-primary);
|
||||
background: var(--background-secondary);
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-main,
|
||||
.ai3d-direct-workbench-match-side {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-side {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-title,
|
||||
.ai3d-direct-workbench-match-source,
|
||||
.ai3d-direct-workbench-match-model,
|
||||
|
|
@ -772,34 +752,40 @@ body {
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-title {
|
||||
color: var(--text-normal);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-source {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-model {
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-target {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
font-size: var(--font-ui-smaller);
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-reasons {
|
||||
color: var(--text-faint);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-score {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 34px;
|
||||
color: var(--text-accent);
|
||||
font-weight: 700;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ai3d-direct-workbench-match-open {
|
||||
min-width: 44px;
|
||||
}
|
||||
/* Mobile responsive */
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.ai3d-workspace-track-top {
|
||||
grid-template-columns: 1fr;
|
||||
|
|
@ -829,6 +815,7 @@ body {
|
|||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Responsive: Narrow Sidebar --- */
|
||||
|
||||
@media (max-width: 400px) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue