feat: merge XCBluePrint 3D visualization into project

This commit is contained in:
2026-03-18 14:22:07 +08:00
parent 5d52c2002e
commit 49bc08b9f7
18 changed files with 3737 additions and 1525 deletions

4458
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,25 +4,38 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.5.0",
"clsx": "^2.1.1",
"lucide-react": "^0.511.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-merge": "^3.5.0"
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwind-merge": "^3.5.0",
"three": "^0.183.2",
"zustand": "^5.0.11"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.4.1",
"@eslint/js": "^9.39.4",
"@tailwindcss/postcss": "^4.2.1",
"@types/node": "^24.12.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/three": "^0.183.1",
"@vitejs/plugin-react": "^6.0.0",
"autoprefixer": "^10.4.21",
"playwright": "^1.58.2",
"postcss": "^8.5.3",
"tailwindcss": "^3.4.17",
"typescript": "~5.8.3",
"vite": "^6.3.5"
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"postcss": "^8.5.8",
"tailwindcss": "^4.2.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.56.1",
"vite": "^8.0.0"
}
}

View File

@@ -1,6 +1,6 @@
export default {
plugins: {
tailwindcss: {},
'@tailwindcss/postcss': {},
autoprefixer: {},
},
}

View File

@@ -1,9 +1,50 @@
import { ApiDocViewer } from './components/ApiDocViewer'
import { config } from './config'
import { useState } from 'react';
import { FileText, Box } from 'lucide-react';
import { ApiDocViewer } from './components/ApiDocViewer';
import BlueprintPage from './components/blueprint/BlueprintPage';
import { config } from './config';
type Page = 'docs' | 'blueprint';
function App() {
document.title = config.projectName
return <ApiDocViewer />
const [currentPage, setCurrentPage] = useState<Page>('docs');
document.title = config.projectName;
return (
<div className="h-screen flex flex-col">
<header className="h-12 bg-zinc-900 border-b border-zinc-800 flex items-center px-4 justify-between">
<h1 className="text-sm font-medium text-white">{config.projectName}</h1>
<nav className="flex gap-1">
<button
onClick={() => setCurrentPage('docs')}
className={`flex items-center gap-2 px-3 py-1.5 rounded text-sm ${
currentPage === 'docs'
? 'bg-zinc-800 text-white'
: 'text-zinc-400 hover:text-white hover:bg-zinc-800/50'
}`}
>
<FileText size={16} />
API
</button>
<button
onClick={() => setCurrentPage('blueprint')}
className={`flex items-center gap-2 px-3 py-1.5 rounded text-sm ${
currentPage === 'blueprint'
? 'bg-zinc-800 text-white'
: 'text-zinc-400 hover:text-white hover:bg-zinc-800/50'
}`}
>
<Box size={16} />
3D
</button>
</nav>
</header>
<main className="flex-1 overflow-hidden">
{currentPage === 'docs' ? <ApiDocViewer /> : <BlueprintPage />}
</main>
</div>
);
}
export default App
export default App;

View File

@@ -1,4 +1,4 @@
import { clsx } from 'clsx'
import clsx from 'clsx'
import type { ParsedDoc, DocTable, DocCodeBlock } from '@/lib/types'
interface DocContentProps {
@@ -88,16 +88,26 @@ interface SectionProps {
}
const Section = ({ section, onReferenceClick }: SectionProps) => {
const HeadingTag = `h${Math.min(section.level + 1, 6)}` as keyof JSX.IntrinsicElements
const level = Math.min(section.level + 1, 6)
const headingClass = clsx(
'font-semibold text-white mb-3',
section.level === 1 ? 'text-xl' : 'text-lg'
)
const renderHeading = () => {
switch (level) {
case 2: return <h2 className={headingClass}>{section.title}</h2>
case 3: return <h3 className={headingClass}>{section.title}</h3>
case 4: return <h4 className={headingClass}>{section.title}</h4>
case 5: return <h5 className={headingClass}>{section.title}</h5>
case 6: return <h6 className={headingClass}>{section.title}</h6>
default: return <h2 className={headingClass}>{section.title}</h2>
}
}
return (
<section className="mb-6">
<HeadingTag className={clsx(
'font-semibold text-white mb-3',
section.level === 1 ? 'text-xl' : 'text-lg'
)}>
{section.title}
</HeadingTag>
{renderHeading()}
<div className="space-y-3">
{section.content.map((item, idx) => {

View File

@@ -0,0 +1,13 @@
import Scene3D from './Scene3D';
import DetailPanel from './DetailPanel';
export default function BlueprintPage() {
return (
<div className="flex h-full">
<div className="flex-1">
<Scene3D />
</div>
<DetailPanel />
</div>
);
}

View File

@@ -0,0 +1,118 @@
import { useBlueprintStore } from '../../store/blueprintStore';
export default function DetailPanel() {
const { blueprint, selectedNode } = useBlueprintStore();
if (!selectedNode) {
return (
<div className="w-72 border-l border-zinc-800 p-4">
<div className="text-xs text-zinc-600">Select a node</div>
</div>
);
}
const isRoot = selectedNode === 'root';
const subsystem = blueprint.subsystems.find(s => s.id === selectedNode);
const module = blueprint.modules.find(m => m.id === selectedNode);
return (
<div className="w-72 border-l border-zinc-800 p-4 overflow-y-auto text-sm">
{isRoot && (
<>
<h2 className="text-base font-medium text-white mb-1">{blueprint.meta.name}</h2>
<div className="text-xs text-zinc-500 mb-4">{blueprint.meta.description}</div>
<div className="space-y-2 text-xs">
<div className="flex justify-between">
<span className="text-zinc-600">Version</span>
<span className="text-zinc-400">{blueprint.meta.version}</span>
</div>
<div className="flex justify-between">
<span className="text-zinc-600">Type</span>
<span className="text-zinc-400">{blueprint.meta.type}</span>
</div>
<div className="flex justify-between">
<span className="text-zinc-600">Runtime</span>
<span className="text-zinc-400">{blueprint.meta.target_runtime}</span>
</div>
<div className="flex justify-between">
<span className="text-zinc-600">Subsystems</span>
<span className="text-zinc-400">{blueprint.subsystems.length}</span>
</div>
<div className="flex justify-between">
<span className="text-zinc-600">Modules</span>
<span className="text-zinc-400">{blueprint.modules.length}</span>
</div>
</div>
</>
)}
{subsystem && (
<>
<h2 className="text-base font-medium text-white mb-4">{subsystem.name}</h2>
<div className="mb-4">
<div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-2">Responsibilities</div>
<ul className="space-y-1">
{subsystem.responsibilities.map((r, i) => (
<li key={i} className="text-xs text-zinc-400">· {r}</li>
))}
</ul>
</div>
<div className="mb-4">
<div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-2">Provides</div>
<div className="flex flex-wrap gap-1">
{subsystem.provides.map((p, i) => (
<span key={i} className="px-1.5 py-0.5 bg-zinc-800 text-zinc-400 text-[10px]">{p}</span>
))}
</div>
</div>
{subsystem.depends_on.length > 0 && (
<div>
<div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-2">Depends On</div>
<div className="flex flex-wrap gap-1">
{subsystem.depends_on.map((d, i) => (
<span key={i} className="px-1.5 py-0.5 bg-zinc-800 text-zinc-400 text-[10px]">{d}</span>
))}
</div>
</div>
)}
</>
)}
{module && (
<>
<h2 className="text-base font-medium text-white mb-4">{module.name}</h2>
<div className="mb-4">
<div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-1">Parent</div>
<span className="text-xs text-zinc-400">{module.parent_subsystem}</span>
</div>
<div className="mb-4">
<div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-1">Responsibility</div>
<p className="text-xs text-zinc-400">{module.responsibility}</p>
</div>
<div>
<div className="text-[10px] font-medium text-zinc-500 uppercase tracking-wider mb-2">Public API</div>
<div className="space-y-2">
{module.public_api.map((api, i) => (
<div key={i} className="bg-zinc-900 p-2">
<div className="text-xs text-zinc-300 font-mono">{api.fn}()</div>
<div className="text-[10px] text-zinc-600 mt-1">
params: {api.params.map(p => p.name).join(', ') || 'none'}
</div>
<div className="text-[10px] text-zinc-600">
returns: {api.returns.type}
</div>
</div>
))}
</div>
</div>
</>
)}
</div>
);
}

View File

@@ -0,0 +1,23 @@
import { Canvas } from '@react-three/fiber';
import { OrbitControls, PerspectiveCamera } from '@react-three/drei';
import { Suspense } from 'react';
import SystemStructure from './SystemStructure';
export default function Scene3D() {
return (
<div className="w-full h-full bg-black">
<Canvas>
<PerspectiveCamera makeDefault position={[12, 10, 12]} fov={50} />
<OrbitControls enablePan enableZoom enableRotate />
<ambientLight intensity={0.8} />
<directionalLight position={[10, 20, 10]} intensity={1.5} />
<pointLight position={[-10, 10, -10]} intensity={0.5} color="#ffffff" />
<Suspense fallback={null}>
<SystemStructure />
</Suspense>
</Canvas>
</div>
);
}

View File

@@ -0,0 +1,311 @@
import { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import { Text, Billboard, QuadraticBezierLine } from '@react-three/drei';
import * as THREE from 'three';
import { useBlueprintStore } from '../../store/blueprintStore';
const NODE_COLOR = '#aaaaaa';
const ROOT_COLOR = '#ffffff';
function splitCamelCase(str: string): string[] {
if (str === str.toUpperCase()) {
return [str];
}
const result: string[] = [];
let current = '';
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (char >= 'A' && char <= 'Z' && current.length > 0) {
result.push(current);
current = char;
} else {
current += char;
}
}
if (current) result.push(current);
return result;
}
function SystemNode({
id,
name,
position,
isRoot = false,
size = 0.8
}: {
id: string;
name: string;
position: [number, number, number];
isRoot?: boolean;
size?: number;
}) {
const meshRef = useRef<THREE.Mesh>(null);
const { selectedNode, setSelectedNode } = useBlueprintStore();
const isSelected = selectedNode === id;
const color = isRoot ? ROOT_COLOR : NODE_COLOR;
useFrame(() => {
if (meshRef.current) {
meshRef.current.rotation.y += 0.005;
}
});
return (
<group position={position}>
<mesh
ref={meshRef}
onClick={(e) => {
e.stopPropagation();
setSelectedNode(id);
}}
onPointerOver={() => document.body.style.cursor = 'pointer'}
onPointerOut={() => document.body.style.cursor = 'auto'}
>
<sphereGeometry args={[size, 32, 32]} />
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={isSelected ? 0.8 : 0.3}
metalness={0.5}
roughness={0.3}
/>
</mesh>
<Billboard follow={true}>
{(() => {
const parts = splitCamelCase(name);
const lineHeight = 0.45;
const startY = size + 0.5 + (parts.length - 1) * lineHeight / 2;
return (
<>
{parts.map((part, i) => (
<Text
key={i}
position={[0, startY - i * lineHeight, 0]}
fontSize={0.35}
color="#ffffff"
anchorX="center"
anchorY="middle"
>
{part}
</Text>
))}
</>
);
})()}
</Billboard>
{isSelected && (
<mesh>
<sphereGeometry args={[size + 0.1, 32, 32]} />
<meshBasicMaterial color={color} transparent opacity={0.3} />
</mesh>
)}
</group>
);
}
function fibonacciSpherePoint(i: number, n: number, radius: number): [number, number, number] {
const phi = Math.acos(1 - 2 * (i + 0.5) / n);
const theta = Math.PI * (1 + Math.sqrt(5)) * i;
return [
radius * Math.sin(phi) * Math.cos(theta),
radius * Math.cos(phi),
radius * Math.sin(phi) * Math.sin(theta),
];
}
function ConnectionLine({
start,
end,
color = '#4a5568',
lineWidth = 1.5
}: {
start: [number, number, number];
end: [number, number, number];
color?: string;
lineWidth?: number;
}) {
const dx = end[0] - start[0];
const dy = end[1] - start[1];
const dz = end[2] - start[2];
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
const midX = (start[0] + end[0]) / 2;
const midY = (start[1] + end[1]) / 2;
const midZ = (start[2] + end[2]) / 2;
const controlOffset = dist * 0.25;
const yDirection = dy > 0 ? 1 : -1;
return (
<QuadraticBezierLine
start={start}
end={end}
mid={[
midX - dz * controlOffset / dist,
midY + yDirection * controlOffset,
midZ + dx * controlOffset / dist,
]}
color={color}
lineWidth={lineWidth}
transparent
opacity={0.6}
/>
);
}
export default function SystemStructure() {
const { blueprint, selectedNode } = useBlueprintStore();
const { subsystems, modules } = blueprint;
const rootPosition: [number, number, number] = [0, 0, 0];
const subsystemPositions = useMemo(() => {
const radius = 8;
const count = subsystems.length;
return subsystems.map((sub, i) => {
const position = fibonacciSpherePoint(i, count, radius);
position[1] *= 0.6;
return {
id: sub.id,
position: position as [number, number, number],
dependCount: sub.depends_on.length,
};
});
}, [subsystems]);
const getSubsystemSize = (dependCount: number) => {
const baseSize = 0.6;
const maxAdd = 0.4;
return baseSize + Math.min(dependCount * 0.15, maxAdd);
};
const modulePositions = useMemo(() => {
const positions: { id: string; name: string; position: [number, number, number]; parentId: string }[] = [];
const moduleRadius = 2.5;
subsystems.forEach((sub) => {
const subModules = modules.filter(m => m.parent_subsystem === sub.id);
const subPos = subsystemPositions.find(p => p.id === sub.id);
if (!subPos) return;
const count = subModules.length || 1;
subModules.forEach((mod, i) => {
const offset = fibonacciSpherePoint(i, count, moduleRadius);
const parentY = subPos.position[1];
const yOffset = parentY > 0 ? 1.2 : -1.2;
positions.push({
id: mod.id,
name: mod.name,
position: [
subPos.position[0] + offset[0],
parentY + yOffset + offset[1] * 0.5,
subPos.position[2] + offset[2],
] as [number, number, number],
parentId: sub.id,
});
});
});
return positions;
}, [subsystems, modules, subsystemPositions]);
const dependencies = useMemo(() => {
const lines: { from: [number, number, number]; to: [number, number, number]; fromId: string; toId: string }[] = [];
subsystems.forEach(sub => {
const fromPos = subsystemPositions.find(p => p.id === sub.id);
if (!fromPos) return;
sub.depends_on.forEach(depId => {
const toPos = subsystemPositions.find(p => p.id === depId);
if (toPos) {
lines.push({
from: fromPos.position,
to: toPos.position,
fromId: sub.id,
toId: depId,
});
}
});
});
return lines;
}, [subsystems, subsystemPositions]);
return (
<group>
<SystemNode
id="root"
name={blueprint.meta.name}
position={rootPosition}
isRoot
size={1.2}
/>
{subsystemPositions.map((sub) => (
<SystemNode
key={sub.id}
id={sub.id}
name={sub.id}
position={sub.position}
size={getSubsystemSize(sub.dependCount)}
/>
))}
{modulePositions.map((mod) => (
<SystemNode
key={mod.id}
id={mod.id}
name={mod.name}
position={mod.position}
size={0.4}
/>
))}
{dependencies.map((dep, i) => {
const isHighlighted = selectedNode && (dep.fromId === selectedNode || dep.toId === selectedNode);
return (
<ConnectionLine
key={i}
start={dep.from}
end={dep.to}
color={isHighlighted ? '#ffffff' : '#666666'}
lineWidth={isHighlighted ? 3 : 1.5}
/>
);
})}
{subsystemPositions.map((sub) => {
const isHighlighted = selectedNode === sub.id || selectedNode === 'root';
return (
<ConnectionLine
key={`root-${sub.id}`}
start={rootPosition}
end={sub.position}
color={isHighlighted ? '#ffffff' : '#666666'}
lineWidth={isHighlighted ? 3 : 1.5}
/>
);
})}
{modulePositions.map((mod) => {
const parent = subsystemPositions.find(p => p.id === mod.parentId);
if (!parent) return null;
const isHighlighted = selectedNode === mod.id || selectedNode === mod.parentId;
return (
<ConnectionLine
key={`mod-${mod.id}`}
start={parent.position}
end={mod.position}
color={isHighlighted ? '#ffffff' : '#666666'}
lineWidth={isHighlighted ? 3 : 1.5}
/>
);
})}
</group>
);
}

180
src/data/sampleData.ts Normal file
View File

@@ -0,0 +1,180 @@
export interface Subsystem {
id: string;
name: string;
responsibilities: string[];
provides: string[];
depends_on: string[];
}
export interface Module {
id: string;
name: string;
parent_subsystem: string;
responsibility: string;
public_api: {
fn: string;
params: { name: string; type: string }[];
returns: { type: string };
}[];
}
export interface SystemMeta {
name: string;
version: string;
type: string;
description: string;
target_runtime: string;
}
export interface BlueprintData {
meta: SystemMeta;
subsystems: Subsystem[];
modules: Module[];
}
export const sampleBlueprint: BlueprintData = {
meta: {
name: 'UnityEngine',
version: '0.1.0',
type: 'game-engine',
description: '轻量级3D游戏引擎支持场景管理、渲染、物理、脚本系统',
target_runtime: 'C++17 / C#'
},
subsystems: [
{
id: 'Core',
name: 'Core',
responsibilities: ['基础数据类型和算法', '内存管理', '平台抽象层'],
provides: ['IAllocator', 'IPlatform', 'IFileSystem'],
depends_on: []
},
{
id: 'Rendering',
name: 'Rendering',
responsibilities: ['渲染管线管理', '渲染资源管理', 'Shader管理', 'Camera管理'],
provides: ['IRenderPipeline', 'IRenderResource', 'IShader', 'ICamera'],
depends_on: ['Core']
},
{
id: 'Physics',
name: 'Physics',
responsibilities: ['物理模拟', '碰撞检测', '刚体/关节系统'],
provides: ['IPhysicsWorld', 'ICollider', 'IRigidbody'],
depends_on: ['Core']
},
{
id: 'Scripting',
name: 'Scripting',
responsibilities: ['脚本生命周期管理', 'C#运行时集成', '组件系统'],
provides: ['IScriptRuntime', 'IMonoBehaviour', 'IComponent'],
depends_on: ['Core']
},
{
id: 'Scene',
name: 'Scene',
responsibilities: ['场景图管理', 'GameObject层级管理', '变换层级'],
provides: ['IScene', 'IGameObject', 'ITransform'],
depends_on: ['Core']
},
{
id: 'Asset',
name: 'Asset',
responsibilities: ['资源加载/卸载', '资源引用计数', '资源格式支持'],
provides: ['IAssetLoader', 'IAssetDatabase'],
depends_on: ['Core']
},
{
id: 'Input',
name: 'Input',
responsibilities: ['输入事件采集', '输入映射'],
provides: ['IInputSystem'],
depends_on: ['Core']
},
{
id: 'Platform',
name: 'Platform',
responsibilities: ['平台特定实现', '窗口管理', '主循环'],
provides: ['IWindow', 'IApplication'],
depends_on: ['Core']
}
],
modules: [
{
id: 'RHI',
name: 'RHI',
parent_subsystem: 'Rendering',
responsibility: '渲染硬件抽象层',
public_api: [
{ fn: 'CreateGraphicsPipeline', params: [{ name: 'desc', type: 'GraphicsPipelineDesc' }], returns: { type: 'IPipeline' } },
{ fn: 'Draw', params: [{ name: 'pipeline', type: 'IPipeline' }, { name: 'mesh', type: 'IMesh' }], returns: { type: 'void' } }
]
},
{
id: 'RenderPipeline',
name: 'RenderPipeline',
parent_subsystem: 'Rendering',
responsibility: '渲染管线调度',
public_api: [
{ fn: 'Render', params: [{ name: 'scene', type: 'IScene' }, { name: 'camera', type: 'ICamera' }], returns: { type: 'void' } }
]
},
{
id: 'ShaderManager',
name: 'ShaderManager',
parent_subsystem: 'Rendering',
responsibility: 'Shader编译和缓存',
public_api: [
{ fn: 'LoadShader', params: [{ name: 'path', type: 'string' }], returns: { type: 'IShader' } }
]
},
{
id: 'PhysicsWorld',
name: 'PhysicsWorld',
parent_subsystem: 'Physics',
responsibility: '物理世界模拟',
public_api: [
{ fn: 'Step', params: [{ name: 'dt', type: 'float' }], returns: { type: 'void' } }
]
},
{
id: 'MonoBehaviour',
name: 'MonoBehaviour',
parent_subsystem: 'Scripting',
responsibility: '脚本组件基类',
public_api: [
{ fn: 'Awake', params: [], returns: { type: 'void' } },
{ fn: 'Start', params: [], returns: { type: 'void' } },
{ fn: 'Update', params: [{ name: 'dt', type: 'float' }], returns: { type: 'void' } }
]
},
{
id: 'Transform',
name: 'Transform',
parent_subsystem: 'Scene',
responsibility: '变换层级管理',
public_api: [
{ fn: 'SetParent', params: [{ name: 'parent', type: 'ITransform' }], returns: { type: 'void' } },
{ fn: 'LocalToWorld', params: [{ name: 'localPos', type: 'vec3' }], returns: { type: 'vec3' } }
]
},
{
id: 'GameObject',
name: 'GameObject',
parent_subsystem: 'Scene',
responsibility: '场景对象管理',
public_api: [
{ fn: 'AddComponent', params: [{ name: 'type', type: 'type_info' }], returns: { type: 'IComponent' } },
{ fn: 'GetComponent', params: [{ name: 'type', type: 'type_info' }], returns: { type: 'IComponent' } }
]
},
{
id: 'AssetLoader',
name: 'AssetLoader',
parent_subsystem: 'Asset',
responsibility: '资源异步加载',
public_api: [
{ fn: 'LoadAsync', params: [{ name: 'path', type: 'string' }], returns: { type: 'AssetFuture' } }
]
}
]
};

View File

@@ -1,6 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
:root {
--sidebar-opacity: 0.8;

View File

@@ -0,0 +1,15 @@
import { create } from 'zustand';
import type { BlueprintData } from '../data/sampleData';
import { sampleBlueprint } from '../data/sampleData';
interface BlueprintStore {
blueprint: BlueprintData;
selectedNode: string | null;
setSelectedNode: (id: string | null) => void;
}
export const useBlueprintStore = create<BlueprintStore>((set) => ({
blueprint: sampleBlueprint,
selectedNode: null,
setSelectedNode: (id) => set({ selectedNode: id }),
}));

View File

@@ -20,6 +20,7 @@
"@/*": ["src/*"]
}
},
"include": ["src"],
"include": ["src", "src/**/*.tsx", "src/**/*.ts"],
"exclude": ["src/docs"],
"references": [{ "path": "./tsconfig.node.json" }]
}

File diff suppressed because one or more lines are too long

1
tsconfig.tsbuildinfo Normal file
View File

@@ -0,0 +1 @@
{"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/apidocviewer.tsx","./src/components/doccontent.tsx","./src/components/doctree.tsx","./src/components/blueprint/blueprintpage.tsx","./src/components/blueprint/detailpanel.tsx","./src/components/blueprint/scene3d.tsx","./src/components/blueprint/systemstructure.tsx","./src/data/sampledata.ts","./src/lib/parser.ts","./src/lib/types.ts","./src/store/blueprintstore.ts"],"version":"5.9.3"}

2
vite.config.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
declare const _default: import("vite").UserConfig;
export default _default;

16
vite.config.js Normal file
View File

@@ -0,0 +1,16 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 3001,
strictPort: false,
},
assetsInclude: ['**/*.md'],
});

View File

@@ -13,4 +13,5 @@ export default defineConfig({
port: 3001,
strictPort: false,
},
assetsInclude: ['**/*.md'],
})