From bbe27bd555e5c538df2c5a9f627116618db436cc Mon Sep 17 00:00:00 2001 From: 134 Date: Mon, 27 Jul 2026 20:09:12 +0800 Subject: [PATCH] fix: isolate HiCAD manufacturing extension --- scripts/vendor-zoo-modeling-app.mjs | 18 +++- .../hicad-manufacturing-extension.test.ts | 15 ++- .../HicadManufacturingLauncher.tsx | 100 ++++++++++++++++++ .../HicadManufacturingPanel.tsx | 88 ++++++++------- vendor/zoo-modeling-app/README.md | 3 + 5 files changed, 179 insertions(+), 45 deletions(-) create mode 100644 vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx diff --git a/scripts/vendor-zoo-modeling-app.mjs b/scripts/vendor-zoo-modeling-app.mjs index 3389f0eb2..494b5c55c 100644 --- a/scripts/vendor-zoo-modeling-app.mjs +++ b/scripts/vendor-zoo-modeling-app.mjs @@ -142,6 +142,10 @@ function patchOfficialSource() { path.join(customizationDir, "HicadManufacturingPanel.tsx"), path.join(sourceDir, "src/components/HicadManufacturingPanel.tsx") ); + copyFileSync( + path.join(customizationDir, "HicadManufacturingLauncher.tsx"), + path.join(sourceDir, "src/components/HicadManufacturingLauncher.tsx") + ); copyFileSync( path.join(customizationDir, "localization.ts"), path.join(sourceDir, "src/lib/hicadLocalization.ts") @@ -197,15 +201,25 @@ function patchOfficialSource() { "import { isDesktop } from '@src/lib/isDesktop'\n", "" ); + replaceSource( + "src/components/OpenedProject.tsx", + "import { HicadManufacturingPanel } from '@src/components/HicadManufacturingPanel'\n", + "" + ); + replaceSource( + "src/components/OpenedProject.tsx", + " \n", + "" + ); replaceSource( "src/components/OpenedProject.tsx", "import { AppHeader } from '@src/components/AppHeader'\n", - "import { AppHeader } from '@src/components/AppHeader'\nimport { HicadManufacturingPanel } from '@src/components/HicadManufacturingPanel'\n" + "import { AppHeader } from '@src/components/AppHeader'\nimport { HicadManufacturingLauncher } from '@src/components/HicadManufacturingLauncher'\n" ); replaceSource( "src/components/OpenedProject.tsx", " \n \n { ) expect(vendorScript).toContain( - 'vendor/zoo-modeling-app/HicadManufacturingPanel.tsx' + 'vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx' .split('/') .at(-1) ) expect(vendorScript).toContain( - "import { HicadManufacturingPanel } from '@src/components/HicadManufacturingPanel'" + "import { HicadManufacturingLauncher } from '@src/components/HicadManufacturingLauncher'" ) expect(vendorScript).toContain( - "" + "" ) + const launcher = fs.readFileSync( + path.join( + projectRoot, + 'vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx' + ), + 'utf8' + ) + expect(launcher).toContain("lazy(() =>") + expect(launcher).toContain('ManufacturingBoundary') }) test('creates non-zero reversible offsets for every independent body', () => { diff --git a/vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx b/vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx new file mode 100644 index 000000000..0cc43429c --- /dev/null +++ b/vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx @@ -0,0 +1,100 @@ +import { + Component, + lazy, + Suspense, + useState, + type ErrorInfo, + type ReactNode, +} from 'react' + +const ManufacturingPanel = lazy(() => + import('./HicadManufacturingPanel').then((module) => ({ + default: module.HicadManufacturingPanel, + })) +) + +class ManufacturingBoundary extends Component< + { + children: ReactNode + onClose: () => void + }, + { failed: boolean } +> { + state = { failed: false } + + static getDerivedStateFromError() { + return { failed: true } + } + + componentDidCatch(error: unknown, info: ErrorInfo) { + console.error('[HiCAD Manufacturing] Extension failed safely', error, info) + } + + render() { + if (!this.state.failed) return this.props.children + return ( +
+
+

工程工具暂时无法打开

+

+ HiCAD 主编辑器没有受到影响。关闭后可以继续建模并稍后重试。 +

+ +
+
+ ) + } +} + +export function HicadManufacturingLauncher({ + projectName, +}: { + projectName: string +}) { + const [active, setActive] = useState(false) + + return ( + <> + + + {active && ( + setActive(false)}> + +
+ 正在加载工程工具… +
+ + } + > + setActive(false)} + projectName={projectName} + /> +
+
+ )} + + ) +} diff --git a/vendor/zoo-modeling-app/HicadManufacturingPanel.tsx b/vendor/zoo-modeling-app/HicadManufacturingPanel.tsx index c10b8b65a..600036f22 100644 --- a/vendor/zoo-modeling-app/HicadManufacturingPanel.tsx +++ b/vendor/zoo-modeling-app/HicadManufacturingPanel.tsx @@ -1,5 +1,4 @@ import type { BoundingBox, ModelingCmd } from '@kittycad/lib' -import { useSignals } from '@preact/signals-react/runtime' import { getBodiesFromArtifactGraph } from '@src/lang/std/artifactGraph' import { useSingletons } from '@src/lib/boot' import { isModelingResponse } from '@src/lib/kcSdkGuards' @@ -109,13 +108,18 @@ function dataUrlFromSnapshot(contents: string) { } export function HicadManufacturingPanel({ + hideTrigger = false, + initialOpen = false, + onRequestClose, projectName, }: { + hideTrigger?: boolean + initialOpen?: boolean + onRequestClose?: () => void projectName: string }) { - useSignals() const { kclManager } = useSingletons() - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(initialOpen) const [busy, setBusy] = useState(false) const [bodies, setBodies] = useState([]) const [snapshot, setSnapshot] = useState() @@ -131,37 +135,29 @@ export function HicadManufacturingPanel({ const explodeOffsetsRef = useRef([]) const resolvedProjectName = projectName || 'HiCAD 模型' - useEffect(() => { - let active = true - void fetch('/api/site-settings?scope=chat', { - credentials: 'same-origin', - }) - .then((response) => { - if (!response.ok) throw new Error('Unable to load site settings') - return response.json() as Promise> + const loadBrandSettings = async () => { + try { + const response = await fetch('/api/site-settings?scope=chat', { + credentials: 'same-origin', }) - .then((settings) => { - if (!active) return - const name = - typeof settings.siteName === 'string' && settings.siteName.trim() - ? settings.siteName.trim() - : '由壹点AI' - const configuredLogo = - typeof settings.siteLogo === 'string' && settings.siteLogo.trim() - ? settings.siteLogo.trim() - : typeof settings.siteLogoDark === 'string' && - settings.siteLogoDark.trim() - ? settings.siteLogoDark.trim() - : '/api/favicon?surface=hicad' - setBrand({ name, logoUrl: configuredLogo }) - }) - .catch(() => { - // Keep the built-in website branding when settings are unavailable. - }) - return () => { - active = false + if (!response.ok) return + const settings = (await response.json()) as Record + const name = + typeof settings.siteName === 'string' && settings.siteName.trim() + ? settings.siteName.trim() + : '由壹点AI' + const configuredLogo = + typeof settings.siteLogo === 'string' && settings.siteLogo.trim() + ? settings.siteLogo.trim() + : typeof settings.siteLogoDark === 'string' && + settings.siteLogoDark.trim() + ? settings.siteLogoDark.trim() + : '/api/favicon?surface=hicad' + setBrand({ name, logoUrl: configuredLogo }) + } catch { + // Keep the built-in website branding when settings are unavailable. } - }, []) + } const sendModelingCommand = async (cmd: ModelingCmd) => { const response = await kclManager.engineCommandManager.sendSceneCommand({ @@ -304,8 +300,17 @@ export function HicadManufacturingPanel({ } } + // This component is lazy-mounted by the launcher, so automatic analysis only + // starts after the user explicitly opens the engineering tools. + useEffect(() => { + if (!initialOpen) return + void loadBrandSettings() + void analyzeModel() + }, [initialOpen]) + const handleOpen = () => { setOpen(true) + void loadBrandSettings() if (bodies.length === 0) { void analyzeModel() } @@ -450,6 +455,7 @@ export function HicadManufacturingPanel({ }) } setOpen(false) + onRequestClose?.() } finally { setBusy(false) } @@ -457,14 +463,16 @@ export function HicadManufacturingPanel({ return ( <> - + {!hideTrigger && ( + + )} {open && (